1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

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.
This commit is contained in:
Daniel Moore 2017-08-29 07:01:52 -04:00 committed by ansibot
parent 549ef0f45d
commit c342866016

View file

@ -35,6 +35,14 @@ options:
- Name of the HAProxy backend pool. - Name of the HAProxy backend pool.
required: false required: false
default: auto-detected 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: host:
description: description:
- Name of the backend host to change. - Name of the backend host to change.
@ -44,7 +52,8 @@ options:
description: description:
- When disabling a server, immediately terminate all the sessions attached - When disabling a server, immediately terminate all the sessions attached
to the specified server. This can be used to terminate long-running 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 required: false
default: false default: false
socket: socket:
@ -122,6 +131,19 @@ EXAMPLES = '''
backend: www backend: www
wait: yes 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 # disable backend server in 'www' backend pool and drop open sessions to it
- haproxy: - haproxy:
state: disabled state: disabled
@ -219,6 +241,7 @@ class HAProxy(object):
self.wait = self.module.params['wait'] self.wait = self.module.params['wait']
self.wait_retries = self.module.params['wait_retries'] self.wait_retries = self.module.params['wait_retries']
self.wait_interval = self.module.params['wait_interval'] self.wait_interval = self.module.params['wait_interval']
self.drain = self.module.params['drain']
self.command_results = {} self.command_results = {}
def execute(self, cmd, timeout=200, capture_output=True): def execute(self, cmd, timeout=200, capture_output=True):
@ -308,7 +331,7 @@ class HAProxy(object):
r = csv.DictReader(data.splitlines()) r = csv.DictReader(data.splitlines())
state = tuple( state = tuple(
map( 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) 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 # 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: if state[0]['status'] == status:
return True if not self.drain or (state[0]['scur'] == '0' and state == 'MAINT'):
return True
else: else:
time.sleep(self.wait_interval) time.sleep(self.wait_interval)
@ -354,7 +378,7 @@ class HAProxy(object):
cmd += "; shutdown sessions server $pxname/$svname" cmd += "; shutdown sessions server $pxname/$svname"
self.execute_for_backends(cmd, backend, host, 'MAINT') 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. Drain action, sets the server to DRAIN mode.
In this mode mode, the server will not accept any new connections 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) # check if haproxy version suppots DRAIN state (starting with 1.5)
if haproxy_version and (1, 5) <= haproxy_version: if haproxy_version and (1, 5) <= haproxy_version:
cmd = "set server $pxname/$svname state drain" 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): def act(self):
""" """
@ -378,6 +402,8 @@ class HAProxy(object):
# toggle enable/disbale server # toggle enable/disbale server
if self.state == 'enabled': if self.state == 'enabled':
self.enabled(self.host, self.backend, self.weight) 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': elif self.state == 'disabled':
self.disabled(self.host, self.backend, self.shutdown_sessions) self.disabled(self.host, self.backend, self.shutdown_sessions)
elif self.state == 'drain': elif self.state == 'drain':
@ -413,6 +439,7 @@ def main():
wait=dict(required=False, default=False, type='bool'), wait=dict(required=False, default=False, type='bool'),
wait_retries=dict(required=False, default=WAIT_RETRIES, type='int'), wait_retries=dict(required=False, default=WAIT_RETRIES, type='int'),
wait_interval=dict(required=False, default=WAIT_INTERVAL, type='int'), wait_interval=dict(required=False, default=WAIT_INTERVAL, type='int'),
drain=dict(default=False, type='bool'),
), ),
) )