refactor and test

This commit is contained in:
Simon Kelly 2020-10-14 16:52:22 +02:00
parent 159f38f4f2
commit 89dbb918a0
2 changed files with 229 additions and 116 deletions

View file

@ -46,6 +46,139 @@ import re
from ansible.module_utils.basic import AnsibleModule
STATE_COMMAND_MAP = {
'stopped': 'stop',
'started': 'start',
'monitored': 'monitor',
'unmonitored': 'unmonitor',
'restarted': 'restart'
}
class Monit(object):
def __init__(self, module, monit_bin_path, service_name, timeout):
self.module = module
self.monit_bin_path = monit_bin_path
self.process_name = service_name
self.timeout = timeout
self._monit_version = None
self._sleep_time = 5
def monit_version(self):
if self._monit_version is None:
rc, out, err = self.module.run_command('%s -V' % self.monit_bin_path, check_rc=True)
version_line = out.split('\n')[0]
version = re.search(r"[0-9]+\.[0-9]+", version_line).group().split('.')
# Use only major and minor even if there are more these should be enough
self._monit_version = int(version[0]), int(version[1])
return self._monit_version
def is_version_higher_than_5_18(self):
return self.monit_version() > (5, 18)
@property
def summary_command(self):
return 'summary -B' if self.is_version_higher_than_5_18() else 'summary'
def parse(self, parts):
if self.is_version_higher_than_5_18():
return self.parse_current(parts)
else:
return self.parse_older_versions(parts)
def parse_older_versions(self, parts):
if len(parts) > 2 and parts[0].lower() == 'process' and parts[1] == "'%s'" % self.process_name:
return ' '.join(parts[2:]).lower()
else:
return ''
def parse_current(self, parts):
if len(parts) > 2 and parts[2].lower() == 'process' and parts[0] == self.process_name:
return ''.join(parts[1]).lower()
else:
return ''
def get_status(self):
"""Return the status of the process in monit, or the empty string if not present."""
rc, out, err = self.module.run_command('%s %s' % (self.monit_bin_path, self.summary_command), check_rc=True)
for line in out.split('\n'):
# Sample output lines:
# Process 'name' Running
# Process 'name' Running - restart pending
parts = self.parse(line.split())
if parts != '':
return parts
return ''
def is_process_present(self):
return self.get_status() != ''
def is_process_running(self):
return 'running' in self.get_status()
def run_command(self, command):
"""Runs a monit command, and returns the new status."""
return self.module.run_command('%s %s %s' % (self.monit_bin_path, command, self.process_name), check_rc=True)
def wait_for_monit_to_stop_pending(self, state):
"""Fails this run if there is no status or it's pending/initializing for timeout"""
timeout_time = time.time() + self.timeout
running_status = self.get_status()
while running_status == '' or 'pending' in running_status or 'initializing' in running_status:
if time.time() >= timeout_time:
self.module.fail_json(
msg='waited too long for "pending", or "initiating" status to go away ({0})'.format(
running_status
),
state=state
)
if self._sleep_time:
time.sleep(self._sleep_time)
running_status = self.get_status()
def reload(self):
rc, out, err = self.module.run_command('%s reload' % self.monit_bin_path)
if rc != 0:
self.module.fail_json(msg='monit reload failed', stdout=out, stderr=err)
self.wait_for_monit_to_stop_pending('reloaded')
self.module.exit_json(changed=True, name=self.process_name, state='reloaded')
def present(self):
self.run_command('reload')
if not self.is_process_present():
self.wait_for_monit_to_stop_pending('present')
self.module.exit_json(changed=True, name=self.process_name, state='present')
def change_state(self, state, expected_status, status_contains=None, invert_expected=None):
self.run_command(STATE_COMMAND_MAP[state])
status = self.get_status()
status_match = status in expected_status
if invert_expected:
status_match = not status_match
if status_match or (status_contains and status_contains in status):
self.module.exit_json(changed=True, name=self.process_name, state=state)
self.module.fail_json(msg='%s process not %s' % (self.process_name, state), status=status)
def stop(self):
self.change_state('stopped', ['not monitored'], 'stop pending')
def unmonitor(self):
self.change_state('unmonitored', ['not monitored'], 'unmonitor pending')
def restart(self):
self.change_state('restarted', ['initializing', 'running'], 'restart pending')
def start(self):
self.change_state('started', ['initializing', 'running'], 'start pending')
def monitor(self):
self.change_state('monitored', ['not monitored'], invert_expected=True)
def main():
arg_spec = dict(
name=dict(required=True),
@ -59,145 +192,52 @@ def main():
state = module.params['state']
timeout = module.params['timeout']
MONIT = module.get_bin_path('monit', True)
monit = Monit(module, module.get_bin_path('monit', True), name, timeout)
def monit_version():
rc, out, err = module.run_command('%s -V' % MONIT, check_rc=True)
version_line = out.split('\n')[0]
version = re.search(r"[0-9]+\.[0-9]+", version_line).group().split('.')
# Use only major and minor even if there are more these should be enough
return int(version[0]), int(version[1])
def is_version_higher_than_5_18():
return (MONIT_MAJOR_VERSION, MONIT_MINOR_VERSION) > (5, 18)
def parse(parts):
if is_version_higher_than_5_18():
return parse_current(parts)
else:
return parse_older_versions(parts)
def parse_older_versions(parts):
if len(parts) > 2 and parts[0].lower() == 'process' and parts[1] == "'%s'" % name:
return ' '.join(parts[2:]).lower()
else:
return ''
def parse_current(parts):
if len(parts) > 2 and parts[2].lower() == 'process' and parts[0] == name:
return ''.join(parts[1]).lower()
else:
return ''
def get_status():
"""Return the status of the process in monit, or the empty string if not present."""
rc, out, err = module.run_command('%s %s' % (MONIT, SUMMARY_COMMAND), check_rc=True)
for line in out.split('\n'):
# Sample output lines:
# Process 'name' Running
# Process 'name' Running - restart pending
parts = parse(line.split())
if parts != '':
return parts
return ''
def run_command(command):
"""Runs a monit command, and returns the new status."""
module.run_command('%s %s %s' % (MONIT, command, name), check_rc=True)
return get_status()
def wait_for_monit_to_stop_pending():
"""Fails this run if there is no status or it's pending/initializing for timeout"""
timeout_time = time.time() + timeout
sleep_time = 5
running_status = get_status()
while running_status == '' or 'pending' in running_status or 'initializing' in running_status:
if time.time() >= timeout_time:
module.fail_json(
msg='waited too long for "pending", or "initiating" status to go away ({0})'.format(
running_status
),
state=state
)
time.sleep(sleep_time)
running_status = get_status()
MONIT_MAJOR_VERSION, MONIT_MINOR_VERSION = monit_version()
SUMMARY_COMMAND = ('summary', 'summary -B')[is_version_higher_than_5_18()]
if state == 'reloaded':
def exit_if_check_mode():
if module.check_mode:
module.exit_json(changed=True)
rc, out, err = module.run_command('%s reload' % MONIT)
if rc != 0:
module.fail_json(msg='monit reload failed', stdout=out, stderr=err)
wait_for_monit_to_stop_pending()
module.exit_json(changed=True, name=name, state=state)
present = get_status() != ''
if state == 'reloaded':
exit_if_check_mode()
monit.reload()
present = monit.is_process_present()
if not present and not state == 'present':
module.fail_json(msg='%s process not presently configured with monit' % name, name=name, state=state)
if state == 'present':
if not present:
if module.check_mode:
module.exit_json(changed=True)
status = run_command('reload')
if status == '':
wait_for_monit_to_stop_pending()
module.exit_json(changed=True, name=name, state=state)
module.exit_json(changed=False, name=name, state=state)
if present:
module.exit_json(changed=False, name=name, state=state)
exit_if_check_mode()
monit.present()
wait_for_monit_to_stop_pending()
running = 'running' in get_status()
monit.wait_for_monit_to_stop_pending(state)
running = monit.is_process_running()
if running and state in ['started', 'monitored']:
module.exit_json(changed=False, name=name, state=state)
if running and state == 'stopped':
if module.check_mode:
module.exit_json(changed=True)
status = run_command('stop')
if status in ['not monitored'] or 'stop pending' in status:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg='%s process not stopped' % name, status=status)
exit_if_check_mode()
monit.stop()
if running and state == 'unmonitored':
if module.check_mode:
module.exit_json(changed=True)
status = run_command('unmonitor')
if status in ['not monitored'] or 'unmonitor pending' in status:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg='%s process not unmonitored' % name, status=status)
exit_if_check_mode()
monit.unmonitor()
elif state == 'restarted':
if module.check_mode:
module.exit_json(changed=True)
status = run_command('restart')
if status in ['initializing', 'running'] or 'restart pending' in status:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg='%s process not restarted' % name, status=status)
exit_if_check_mode()
monit.restart()
elif not running and state == 'started':
if module.check_mode:
module.exit_json(changed=True)
status = run_command('start')
if status in ['initializing', 'running'] or 'start pending' in status:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg='%s process not started' % name, status=status)
exit_if_check_mode()
monit.start()
elif not running and state == 'monitored':
if module.check_mode:
module.exit_json(changed=True)
status = run_command('monitor')
if status not in ['not monitored']:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg='%s process not monitored' % name, status=status)
exit_if_check_mode()
monit.monitor()
module.exit_json(changed=False, name=name, state=state)