diff --git a/web_infrastructure/supervisorctl b/web_infrastructure/supervisorctl index 564368af5f4..a53a93d22ee 100644 --- a/web_infrastructure/supervisorctl +++ b/web_infrastructure/supervisorctl @@ -23,70 +23,74 @@ import os DOCUMENTATION = ''' --- module: supervisorctl -short_description: Manage the state of a program or group of programs running via Supervisord +short_description: Manage the state of a program or group of programs running via supervisord description: - - Manage the state of a program or group of programs running via I(Supervisord) + - Manage the state of a program or group of programs running via supervisord version_added: "0.7" options: name: description: - - The name of the I(supervisord) program/process to manage + - The name of the supervisord program/group to manage. It will be taken as group name when it end with a colon I(:). required: true default: null config: description: - - configuration file path, passed as -c to supervisorctl + - configuration file path, passed as -c to supervisorctl. required: false default: null version_added: "1.3" server_url: description: - - URL on which supervisord server is listening, passed as -s to supervisorctl + - URL on which supervisord server is listening, passed as -s to supervisorctl. required: false default: null version_added: "1.3" username: description: - - username to use for authentication with server, passed as -u to supervisorctl + - username to use for authentication with server, passed as -u to supervisorctl. required: false default: null version_added: "1.3" password: description: - - password to use for authentication with server, passed as -p to supervisorctl + - password to use for authentication with server, passed as -p to supervisorctl. required: false default: null version_added: "1.3" state: description: - - The state of service + - The desired state of program/group. Affected programs' name will be returned in I(affected) field of the result. required: true default: null choices: [ "present", "started", "stopped", "restarted" ] supervisorctl_path: description: - - Path to supervisorctl executable to use + - Path to supervisorctl executable to use. required: false default: null version_added: "1.4" -requirements: - - supervisorctl -requirements: [ ] -author: Matt Wright +notes: + - When C(state) = I(present), will call C(supervisorctl reread) then call C(supervisorctl add) if the program/group is not exists. + - When C(state) = I(restarted), will call C(supervisorctl update) then call C(supervisorctl restart). +requirements: [ "supervisorctl" ] +author: Matt Wright, Aaron Wang ''' EXAMPLES = ''' # Manage the state of program to be in 'started' state. - supervisorctl: name=my_app state=started +# Manage the state of program group to be in 'started' state. +- supervisorctl: name='my_apps:' state=started + # Restart my_app, reading supervisorctl configuration from a specified file. - supervisorctl: name=my_app state=restarted config=/var/opt/my_project/supervisord.conf # Restart my_app, connecting to supervisord with credentials and server URL. - supervisorctl: name=my_app state=restarted username=test password=testpass server_url=http://localhost:9001 - ''' + def main(): arg_spec = dict( name=dict(required=True), @@ -101,6 +105,10 @@ def main(): module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True) name = module.params['name'] + is_group = False + if name.endswith(':'): + is_group = True + name = name.rstrip(':') state = module.params['state'] config = module.params.get('config') server_url = module.params.get('server_url') @@ -111,11 +119,12 @@ def main(): if supervisorctl_path: supervisorctl_path = os.path.expanduser(supervisorctl_path) if os.path.exists(supervisorctl_path) and module.is_executable(supervisorctl_path): - supervisorctl_args = [ supervisorctl_path ] + supervisorctl_args = [supervisorctl_path] else: - module.fail_json(msg="Provided path to supervisorctl does not exist or isn't executable: %s" % supervisorctl_path) + module.fail_json( + msg="Provided path to supervisorctl does not exist or isn't executable: %s" % supervisorctl_path) else: - supervisorctl_args = [ module.get_bin_path('supervisorctl', True) ] + supervisorctl_args = [module.get_bin_path('supervisorctl', True)] if config: supervisorctl_args.extend(['-c', os.path.expanduser(config)]) @@ -133,61 +142,76 @@ def main(): args.append(name) return module.run_command(args, **kwargs) - rc, out, err = run_supervisorctl('status') - present = name in out + def get_matched_processes(): + matched = [] + rc, out, err = run_supervisorctl('status') + for line in out.splitlines(): + # One status line may look like one of these two: + # process not in group: + # echo_date_lonely RUNNING pid 7680, uptime 13:22:18 + # process in group: + # echo_date_group:echo_date_00 RUNNING pid 7681, uptime 13:22:18 + fields = [field for field in line.split(' ') if field != ''] + process_name = fields[0] + status = fields[1] + + if is_group: + # If there is ':', this process must be in a group. + if ':' in process_name: + group = process_name.split(':')[0] + if group != name: + continue + else: + continue + else: + if process_name != name: + continue + + matched.append((process_name, status)) + return matched + + def take_action_on_processes(processes, status_filter, action, expected_result): + to_take_action_on = [] + for process_name, status in processes: + if status_filter(status): + to_take_action_on.append(process_name) + + if len(to_take_action_on) == 0: + module.exit_json(changed=False, name=name, state=state) + if module.check_mode: + module.exit_json(changed=True) + for process_name in to_take_action_on: + rc, out, err = run_supervisorctl(action, process_name) + if '%s: %s' % (process_name, expected_result) not in out: + module.fail_json(msg=out) + + module.exit_json(changed=True, name=name, state=state, affected=to_take_action_on) + + if state == 'restarted': + rc, out, err = run_supervisorctl('update') + processes = get_matched_processes() + take_action_on_processes(processes, lambda s: True, 'restart', 'started') + + processes = get_matched_processes() if state == 'present': - if not present: - if module.check_mode: - module.exit_json(changed=True) - run_supervisorctl('reread', check_rc=True) - rc, out, err = run_supervisorctl('add', name) + if len(processes) > 0: + module.exit_json(changed=False, name=name, state=state) - if '%s: added process group' % name in out: - module.exit_json(changed=True, name=name, state=state) - else: - module.fail_json(msg=out, name=name, state=state) - - module.exit_json(changed=False, name=name, state=state) - - rc, out, err = run_supervisorctl('status', name) - running = 'RUNNING' in out or '(already running)' in out - - if running and state == 'started': - module.exit_json(changed=False, name=name, state=state) - - if running and state == 'stopped': if module.check_mode: module.exit_json(changed=True) - rc, out, err = run_supervisorctl('stop', name) - - if '%s: stopped' % name in out: + run_supervisorctl('reread', check_rc=True) + rc, out, err = run_supervisorctl('add', name) + if '%s: added process group' % name in out: module.exit_json(changed=True, name=name, state=state) + else: + module.fail_json(msg=out, name=name, state=state) - module.fail_json(msg=out) + if state == 'started': + take_action_on_processes(processes, lambda s: s != 'RUNNING', 'start', 'started') - elif state == 'restarted': - if module.check_mode: - module.exit_json(changed=True) - rc, out, err = run_supervisorctl('update', name) - rc, out, err = run_supervisorctl('restart', name) - - if '%s: started' % name in out: - module.exit_json(changed=True, name=name, state=state) - - module.fail_json(msg=out) - - elif not running and state == 'started': - if module.check_mode: - module.exit_json(changed=True) - rc, out, err = run_supervisorctl('start',name) - - if '%s: started' % name in out: - module.exit_json(changed=True, name=name, state=state) - - module.fail_json(msg=out) - - module.exit_json(changed=False, name=name, state=state) + if state == 'stopped': + take_action_on_processes(processes, lambda s: s == 'RUNNING', 'stop', 'stopped') # import module snippets from ansible.module_utils.basic import *