From c34286601693822c97186a8d65e15db5ec7d6a3e Mon Sep 17 00:00:00 2001 From: Daniel Moore Date: Tue, 29 Aug 2017 07:01:52 -0400 Subject: [PATCH] HAProxy: add support for draining connections in maintenace mode (#25887) * HAProxy: Support waiting for nodes to drain before maint mode A common task when working with a HAProxy-managed node is to first "drain" it and then place it into maintenance mode (to be repaired or redeployed). (Draining such a node consists of preventing new connections from being established while waiting for active sessions to expire/close.) This commit creates a new `drain' parameter for Ansible's HAProxy module, which, when set to `yes` in conjunction with `state: disabled` and `wait: yes`, causes the module to attempt to set a node to drain, wait for it to finish draining, and put the node into maintenance mode. The action is recorded as a success if these steps are completed before a maximum wait timeout is reached. Implements: https://github.com/ansible/ansible-modules-extras/issues/521 Acknowledgments: This is based on user krislindgren's pull request #21420 * Correct how drain option is handled Previously the `drain` parameter would have no effect, since the `disabled` state would still immediately place backend servers into maintenance mode. --- lib/ansible/modules/net_tools/haproxy.py | 37 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/ansible/modules/net_tools/haproxy.py b/lib/ansible/modules/net_tools/haproxy.py index 6209192251..35756f3219 100644 --- a/lib/ansible/modules/net_tools/haproxy.py +++ b/lib/ansible/modules/net_tools/haproxy.py @@ -35,6 +35,14 @@ options: - Name of the HAProxy backend pool. required: false default: auto-detected + drain: + description: + - Wait until the server has no active connections or until the timeout + determined by wait_interval and wait_retries is reached. Continue only + after the status changes to 'MAINT'. This overrides the + shutdown_sessions option. + default: false + version_added: "2.4" host: description: - Name of the backend host to change. @@ -44,7 +52,8 @@ options: description: - When disabling a server, immediately terminate all the sessions attached to the specified server. This can be used to terminate long-running - sessions after a server is put into maintenance mode. + sessions after a server is put into maintenance mode. Overridden by the + drain option. required: false default: false socket: @@ -122,6 +131,19 @@ EXAMPLES = ''' backend: www wait: yes +# Place server in drain mode, providing a socket file. Then check the server's +# status every minute to see if it changes to maintenance mode, continuing if it +# does in an hour and failing otherwise. +- haproxy: + state: disabled + host: '{{ inventory_hostname }}' + socket: /var/run/haproxy.sock + backend: www + wait: yes + drain: yes + wait_interval: 1 + wait_retries: 60 + # disable backend server in 'www' backend pool and drop open sessions to it - haproxy: state: disabled @@ -219,6 +241,7 @@ class HAProxy(object): self.wait = self.module.params['wait'] self.wait_retries = self.module.params['wait_retries'] self.wait_interval = self.module.params['wait_interval'] + self.drain = self.module.params['drain'] self.command_results = {} def execute(self, cmd, timeout=200, capture_output=True): @@ -308,7 +331,7 @@ class HAProxy(object): r = csv.DictReader(data.splitlines()) state = tuple( map( - lambda d: {'status': d['status'], 'weight': d['weight']}, + lambda d: {'status': d['status'], 'weight': d['weight'], 'scur': d['scur']}, filter(lambda d: (pxname is None or d['pxname'] == pxname) and d['svname'] == svname, r) ) ) @@ -326,7 +349,8 @@ class HAProxy(object): # We can assume there will only be 1 element in state because both svname and pxname are always set when we get here if state[0]['status'] == status: - return True + if not self.drain or (state[0]['scur'] == '0' and state == 'MAINT'): + return True else: time.sleep(self.wait_interval) @@ -354,7 +378,7 @@ class HAProxy(object): cmd += "; shutdown sessions server $pxname/$svname" self.execute_for_backends(cmd, backend, host, 'MAINT') - def drain(self, host, backend): + def drain(self, host, backend, status='DRAIN'): """ Drain action, sets the server to DRAIN mode. In this mode mode, the server will not accept any new connections @@ -365,7 +389,7 @@ class HAProxy(object): # check if haproxy version suppots DRAIN state (starting with 1.5) if haproxy_version and (1, 5) <= haproxy_version: cmd = "set server $pxname/$svname state drain" - self.execute_for_backends(cmd, backend, host, 'DRAIN') + self.execute_for_backends(cmd, backend, host, status) def act(self): """ @@ -378,6 +402,8 @@ class HAProxy(object): # toggle enable/disbale server if self.state == 'enabled': self.enabled(self.host, self.backend, self.weight) + elif self.state == 'disabled' and self.drain: + self.drain(self.host, self.backend, status='MAINT') elif self.state == 'disabled': self.disabled(self.host, self.backend, self.shutdown_sessions) elif self.state == 'drain': @@ -413,6 +439,7 @@ def main(): wait=dict(required=False, default=False, type='bool'), wait_retries=dict(required=False, default=WAIT_RETRIES, type='int'), wait_interval=dict(required=False, default=WAIT_INTERVAL, type='int'), + drain=dict(default=False, type='bool'), ), )