1""" 2Support for haproxy 3 4.. versionadded:: 2014.7.0 5""" 6 7 8import logging 9import os 10import stat 11import time 12 13try: 14 import haproxy.cmds # pylint: disable=no-name-in-module 15 import haproxy.conn # pylint: disable=no-name-in-module 16 17 HAS_HAPROXY = True 18except ImportError: 19 HAS_HAPROXY = False 20 21log = logging.getLogger(__name__) 22 23__virtualname__ = "haproxy" 24 25# Default socket location 26DEFAULT_SOCKET_URL = "/var/run/haproxy.sock" 27 28# Numeric fields returned by stats 29FIELD_NUMERIC = ["weight", "bin", "bout"] 30# Field specifying the actual server name 31FIELD_NODE_NAME = "name" 32 33 34def __virtual__(): 35 """ 36 Only load the module if haproxyctl is installed 37 """ 38 if HAS_HAPROXY: 39 return __virtualname__ 40 return ( 41 False, 42 "The haproxyconn execution module cannot be loaded: haproxyctl module not" 43 " available", 44 ) 45 46 47def _get_conn(socket=DEFAULT_SOCKET_URL): 48 """ 49 Get connection to haproxy socket. 50 """ 51 assert os.path.exists(socket), "{} does not exist.".format(socket) 52 issock = os.stat(socket).st_mode 53 assert stat.S_ISSOCK(issock), "{} is not a socket.".format(socket) 54 ha_conn = haproxy.conn.HaPConn(socket) 55 return ha_conn 56 57 58def list_servers(backend, socket=DEFAULT_SOCKET_URL, objectify=False): 59 """ 60 List servers in haproxy backend. 61 62 backend 63 haproxy backend 64 65 socket 66 haproxy stats socket, default ``/var/run/haproxy.sock`` 67 68 CLI Example: 69 70 .. code-block:: bash 71 72 salt '*' haproxy.list_servers mysql 73 """ 74 ha_conn = _get_conn(socket) 75 ha_cmd = haproxy.cmds.listServers(backend=backend) 76 return ha_conn.sendCmd(ha_cmd, objectify=objectify) 77 78 79def wait_state(backend, server, value="up", timeout=60 * 5, socket=DEFAULT_SOCKET_URL): 80 """ 81 82 Wait for a specific server state 83 84 backend 85 haproxy backend 86 87 server 88 targeted server 89 90 value 91 state value 92 93 timeout 94 timeout before giving up state value, default 5 min 95 96 socket 97 haproxy stats socket, default ``/var/run/haproxy.sock`` 98 99 CLI Example: 100 101 .. code-block:: bash 102 103 salt '*' haproxy.wait_state mysql server01 up 60 104 """ 105 t = time.time() + timeout 106 while time.time() < t: 107 if ( 108 get_backend(backend=backend, socket=socket)[server]["status"].lower() 109 == value.lower() 110 ): 111 return True 112 return False 113 114 115def get_backend(backend, socket=DEFAULT_SOCKET_URL): 116 """ 117 118 Receive information about a specific backend. 119 120 backend 121 haproxy backend 122 123 socket 124 haproxy stats socket, default ``/var/run/haproxy.sock`` 125 126 CLI Example: 127 128 .. code-block:: bash 129 130 salt '*' haproxy.get_backend mysql 131 """ 132 133 backend_data = ( 134 list_servers(backend=backend, socket=socket).replace("\n", " ").split(" ") 135 ) 136 result = {} 137 138 # Convert given string to Integer 139 def num(s): 140 try: 141 return int(s) 142 except ValueError: 143 return s 144 145 for data in backend_data: 146 # Check if field or server name 147 if ":" in data: 148 active_field = data.replace(":", "").lower() 149 continue 150 elif active_field.lower() == FIELD_NODE_NAME: 151 active_server = data 152 result[active_server] = {} 153 continue 154 # Format and set returned field data to active server 155 if active_field in FIELD_NUMERIC: 156 if data == "": 157 result[active_server][active_field] = 0 158 else: 159 result[active_server][active_field] = num(data) 160 else: 161 result[active_server][active_field] = data 162 163 return result 164 165 166def enable_server(name, backend, socket=DEFAULT_SOCKET_URL): 167 """ 168 Enable Server in haproxy 169 170 name 171 Server to enable 172 173 backend 174 haproxy backend, or all backends if "*" is supplied 175 176 socket 177 haproxy stats socket, default ``/var/run/haproxy.sock`` 178 179 CLI Example: 180 181 .. code-block:: bash 182 183 salt '*' haproxy.enable_server web1.example.com www 184 """ 185 186 if backend == "*": 187 backends = show_backends(socket=socket).split("\n") 188 else: 189 backends = [backend] 190 191 results = {} 192 for backend in backends: 193 ha_conn = _get_conn(socket) 194 ha_cmd = haproxy.cmds.enableServer(server=name, backend=backend) 195 ha_conn.sendCmd(ha_cmd) 196 results[backend] = list_servers(backend, socket=socket) 197 198 return results 199 200 201def disable_server(name, backend, socket=DEFAULT_SOCKET_URL): 202 """ 203 Disable server in haproxy. 204 205 name 206 Server to disable 207 208 backend 209 haproxy backend, or all backends if "*" is supplied 210 211 socket 212 haproxy stats socket, default ``/var/run/haproxy.sock`` 213 214 CLI Example: 215 216 .. code-block:: bash 217 218 salt '*' haproxy.disable_server db1.example.com mysql 219 """ 220 221 if backend == "*": 222 backends = show_backends(socket=socket).split("\n") 223 else: 224 backends = [backend] 225 226 results = {} 227 for backend in backends: 228 ha_conn = _get_conn(socket) 229 ha_cmd = haproxy.cmds.disableServer(server=name, backend=backend) 230 ha_conn.sendCmd(ha_cmd) 231 results[backend] = list_servers(backend, socket=socket) 232 233 return results 234 235 236def get_weight(name, backend, socket=DEFAULT_SOCKET_URL): 237 """ 238 Get server weight 239 240 name 241 Server name 242 243 backend 244 haproxy backend 245 246 socket 247 haproxy stats socket, default ``/var/run/haproxy.sock`` 248 249 CLI Example: 250 251 .. code-block:: bash 252 253 salt '*' haproxy.get_weight web1.example.com www 254 """ 255 ha_conn = _get_conn(socket) 256 ha_cmd = haproxy.cmds.getWeight(server=name, backend=backend) 257 return ha_conn.sendCmd(ha_cmd) 258 259 260def set_weight(name, backend, weight=0, socket=DEFAULT_SOCKET_URL): 261 """ 262 Set server weight 263 264 name 265 Server name 266 267 backend 268 haproxy backend 269 270 weight 271 Server Weight 272 273 socket 274 haproxy stats socket, default ``/var/run/haproxy.sock`` 275 276 CLI Example: 277 278 .. code-block:: bash 279 280 salt '*' haproxy.set_weight web1.example.com www 13 281 """ 282 ha_conn = _get_conn(socket) 283 ha_cmd = haproxy.cmds.getWeight(server=name, backend=backend, weight=weight) 284 ha_conn.sendCmd(ha_cmd) 285 return get_weight(name, backend, socket=socket) 286 287 288def set_state(name, backend, state, socket=DEFAULT_SOCKET_URL): 289 """ 290 Force a server's administrative state to a new state. This can be useful to 291 disable load balancing and/or any traffic to a server. Setting the state to 292 "ready" puts the server in normal mode, and the command is the equivalent of 293 the "enable server" command. Setting the state to "maint" disables any traffic 294 to the server as well as any health checks. This is the equivalent of the 295 "disable server" command. Setting the mode to "drain" only removes the server 296 from load balancing but still allows it to be checked and to accept new 297 persistent connections. Changes are propagated to tracking servers if any. 298 299 name 300 Server name 301 302 backend 303 haproxy backend 304 305 state 306 A string of the state to set. Must be 'ready', 'drain', or 'maint' 307 308 socket 309 haproxy stats socket, default ``/var/run/haproxy.sock`` 310 311 CLI Example: 312 313 .. code-block:: bash 314 315 salt '*' haproxy.set_state my_proxy_server my_backend ready 316 317 """ 318 # Pulling this in from the latest 0.5 release which is not yet in PyPi. 319 # https://github.com/neurogeek/haproxyctl 320 class setServerState(haproxy.cmds.Cmd): 321 """Set server state command.""" 322 323 cmdTxt = "set server %(backend)s/%(server)s state %(value)s\r\n" 324 p_args = ["backend", "server", "value"] 325 helpTxt = "Force a server's administrative state to a new state." 326 327 ha_conn = _get_conn(socket) 328 ha_cmd = setServerState(server=name, backend=backend, value=state) 329 return ha_conn.sendCmd(ha_cmd) 330 331 332def show_frontends(socket=DEFAULT_SOCKET_URL): 333 """ 334 Show HaProxy frontends 335 336 socket 337 haproxy stats socket, default ``/var/run/haproxy.sock`` 338 339 CLI Example: 340 341 .. code-block:: bash 342 343 salt '*' haproxy.show_frontends 344 """ 345 ha_conn = _get_conn(socket) 346 ha_cmd = haproxy.cmds.showFrontends() 347 return ha_conn.sendCmd(ha_cmd) 348 349 350def list_frontends(socket=DEFAULT_SOCKET_URL): 351 """ 352 353 List HaProxy frontends 354 355 socket 356 haproxy stats socket, default ``/var/run/haproxy.sock`` 357 358 CLI Example: 359 360 .. code-block:: bash 361 362 salt '*' haproxy.list_frontends 363 """ 364 return show_frontends(socket=socket).split("\n") 365 366 367def show_backends(socket=DEFAULT_SOCKET_URL): 368 """ 369 Show HaProxy Backends 370 371 socket 372 haproxy stats socket, default ``/var/run/haproxy.sock`` 373 374 CLI Example: 375 376 .. code-block:: bash 377 378 salt '*' haproxy.show_backends 379 """ 380 ha_conn = _get_conn(socket) 381 ha_cmd = haproxy.cmds.showBackends() 382 return ha_conn.sendCmd(ha_cmd) 383 384 385def list_backends(servers=True, socket=DEFAULT_SOCKET_URL): 386 """ 387 388 List HaProxy Backends 389 390 socket 391 haproxy stats socket, default ``/var/run/haproxy.sock`` 392 393 servers 394 list backends with servers 395 396 CLI Example: 397 398 .. code-block:: bash 399 400 salt '*' haproxy.list_backends 401 """ 402 if not servers: 403 return show_backends(socket=socket).split("\n") 404 else: 405 result = {} 406 for backend in list_backends(servers=False, socket=socket): 407 result[backend] = get_backend(backend=backend, socket=socket) 408 return result 409 410 411def get_sessions(name, backend, socket=DEFAULT_SOCKET_URL): 412 """ 413 .. versionadded:: 2016.11.0 414 415 Get number of current sessions on server in backend (scur) 416 417 name 418 Server name 419 420 backend 421 haproxy backend 422 423 socket 424 haproxy stats socket, default ``/var/run/haproxy.sock`` 425 426 CLI Example: 427 428 .. code-block:: bash 429 430 salt '*' haproxy.get_sessions web1.example.com www 431 """ 432 433 class getStats(haproxy.cmds.Cmd): 434 p_args = ["backend", "server"] 435 cmdTxt = "show stat\r\n" 436 helpText = "Fetch all statistics" 437 438 ha_conn = _get_conn(socket) 439 ha_cmd = getStats(server=name, backend=backend) 440 result = ha_conn.sendCmd(ha_cmd) 441 for line in result.split("\n"): 442 if line.startswith(backend): 443 outCols = line.split(",") 444 if outCols[1] == name: 445 return outCols[4] 446