diff --git a/defaults/main.yml b/defaults/main.yml index cd7a12d..7e31feb 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -53,3 +53,29 @@ devture_systemd_service_manager_up_verification_enabled: true # # We can try to measure this gap and adjust our waiting time accordingly, but we currently don't. devture_systemd_service_manager_up_verification_delay_seconds: 15 + +# devture_systemd_service_manager_parallel_service_setup_enabled controls whether systemd services should start/stop asynchronously. +# This could significantly reduce the duration for stopping/starting services depending on the number of services on the target host. +devture_systemd_service_manager_parallel_service_setup_enabled: false + +# devture_systemd_service_manager_parallel_jobs specifies how many Ansible processes can run in parallel for the given task. +# +# Setting this value to 0 will run as many processes as possible at a time. Note that if this value is set to 0, you may +# need to increase your host(s) SSH configuration's `MaxSessions` property to facilitate all the connections that will be made in +# parallel. Additionally, you may need to increase the following parameters if all services cannot start before timing out: +# - ansible.cfg connection `timeout` +# - `devture_systemd_service_manager_up_verification_delay_seconds` +# - `devture_systemd_service_manager_parallel_async_timeout_seconds` +# - `devture_systemd_service_manager_parallel_job_status_retry_limit` +devture_systemd_service_manager_parallel_jobs: 16 + +# devture_systemd_service_manager_parallel_async_timeout_seconds specifies how long to wait before starting the job can timeout. +devture_systemd_service_manager_parallel_async_timeout_seconds: 60 + +# devture_systemd_service_manager_parallel_job_status_retry_limit specifies how many attempts the Ansible will check the status +# of the running jobs before failing the task. +devture_systemd_service_manager_parallel_job_status_retry_limit: 60 + +# devture_systemd_service_manager_parallel_job_status_retry_delay_seconds specifies how long Ansible will wait before it checks +# the status of the running jobs. +devture_systemd_service_manager_parallel_job_status_retry_delay_seconds: 1 diff --git a/tasks/restart_specified.yml b/tasks/restart_specified.yml index d5d5d4b..9681fcf 100644 --- a/tasks/restart_specified.yml +++ b/tasks/restart_specified.yml @@ -22,20 +22,32 @@ - when: devture_systemd_service_manager_services_list_to_work_with | length > 1 block: - - name: Ensure systemd services are stopped - ansible.builtin.service: - name: "{{ item.name }}" - state: stopped - with_items: "{{ devture_systemd_service_manager_services_list_to_work_with | sort (attribute='priority,name', reverse=true) }}" - when: not ansible_check_mode + - when: not devture_systemd_service_manager_parallel_service_setup_enabled + block: + - name: Ensure systemd services are stopped + ansible.builtin.service: + name: "{{ item.name }}" + state: stopped + with_items: "{{ devture_systemd_service_manager_services_list_to_work_with | sort(attribute='priority,name', reverse=true) }}" + when: not ansible_check_mode - - name: Ensure systemd services are started - ansible.builtin.service: - name: "{{ item.name }}" - state: started - enabled: "{{ devture_systemd_service_manager_services_autostart_enabled }}" - with_items: "{{ devture_systemd_service_manager_services_list_to_work_with | sort (attribute='priority,name') }}" - when: not ansible_check_mode + - name: Ensure systemd services are started + ansible.builtin.service: + name: "{{ item.name }}" + state: started + enabled: "{{ devture_systemd_service_manager_services_autostart_enabled }}" + with_items: "{{ devture_systemd_service_manager_services_list_to_work_with | sort(attribute='priority,name') }}" + when: not ansible_check_mode + + - when: devture_systemd_service_manager_parallel_service_setup_enabled + block: + - name: Ensure systemd services are stopped + ansible.builtin.include_tasks: "{{ role_path }}/tasks/stop_specified_async.yml" + when: not ansible_check_mode + + - name: Ensure systemd services are started + ansible.builtin.include_tasks: "{{ role_path }}/tasks/start_specified_async.yml" + when: not ansible_check_mode - when: devture_systemd_service_manager_service_restart_mode == 'one-by-one' block: diff --git a/tasks/start.yml b/tasks/start.yml index 887ea5f..92b7b17 100644 --- a/tasks/start.yml +++ b/tasks/start.yml @@ -6,20 +6,34 @@ - when: devture_systemd_service_manager_service_restart_mode == 'clean-stop-start' block: - - name: Ensure systemd services are stopped - ansible.builtin.service: - name: "{{ item.name }}" - state: stopped - with_items: "{{ devture_systemd_service_manager_services_list | sort (attribute='priority,name', reverse=true) }}" - when: not ansible_check_mode + - when: not devture_systemd_service_manager_parallel_service_setup_enabled + block: + - name: Ensure systemd services are stopped + ansible.builtin.service: + name: "{{ item.name }}" + state: stopped + with_items: "{{ devture_systemd_service_manager_services_list | sort(attribute='priority,name', reverse=true) }}" + when: not ansible_check_mode - - name: Ensure systemd services are started - ansible.builtin.service: - name: "{{ item.name }}" - state: started - enabled: "{{ devture_systemd_service_manager_services_autostart_enabled }}" - with_items: "{{ devture_systemd_service_manager_services_list | sort (attribute='priority,name') }}" - when: not ansible_check_mode + - name: Ensure systemd services are started + ansible.builtin.service: + name: "{{ item.name }}" + state: started + enabled: "{{ devture_systemd_service_manager_services_autostart_enabled }}" + with_items: "{{ devture_systemd_service_manager_services_list | sort(attribute='priority,name') }}" + when: not ansible_check_mode + + - when: devture_systemd_service_manager_parallel_service_setup_enabled + vars: + devture_systemd_service_manager_services_list_to_work_with: "{{ devture_systemd_service_manager_services_list }}" + block: + - name: Ensure systemd services are stopped + ansible.builtin.include_tasks: "{{ role_path }}/tasks/stop_specified_async.yml" + when: not ansible_check_mode + + - name: Ensure systemd services are started + ansible.builtin.include_tasks: "{{ role_path }}/tasks/start_specified_async.yml" + when: not ansible_check_mode - when: devture_systemd_service_manager_service_restart_mode == 'one-by-one' block: diff --git a/tasks/start_specified_async.yml b/tasks/start_specified_async.yml new file mode 100644 index 0000000..b8f182a --- /dev/null +++ b/tasks/start_specified_async.yml @@ -0,0 +1,40 @@ +--- + +# Suppress 'risky-shell-pipe' lint error due to the required fix `set -o pipefail` not being supported by all shells. +# Additionally, echo commands generally should not fail. +- name: Ensure systemd services are started + ansible.builtin.shell: > + echo "{{ devture_systemd_service_manager_services_list_to_work_with | sort(attribute='priority,name') | join(' ', attribute='name') }}" + | + xargs -P {{ devture_systemd_service_manager_parallel_jobs }} -d ' ' -I '{}' + ansible {{ inventory_hostname }} -i {{ inventory_file }} -m service {{ '' if become is undefined else '--become' }} + -a " + name={} + state=started + enabled={{ devture_systemd_service_manager_services_autostart_enabled }} + " + tags: + - skip_ansible_lint + register: "start_service_job_status" + delegate_to: localhost + become: false + async: "{{ devture_systemd_service_manager_parallel_async_timeout_seconds }}" + poll: 0 + changed_when: false + +- name: Check job status for started services + ansible.builtin.async_status: + jid: "{{ start_service_job_status.ansible_job_id }}" + register: start_service_job_status + until: start_service_job_status.finished + retries: "{{ devture_systemd_service_manager_parallel_job_status_retry_limit }}" + delay: "{{ devture_systemd_service_manager_parallel_job_status_retry_delay_seconds }}" + delegate_to: localhost + become: false + +- name: Cleanup async job temp file + ansible.builtin.async_status: + jid: "{{ start_service_job_status.ansible_job_id }}" + mode: "cleanup" + delegate_to: localhost + become: false diff --git a/tasks/stop_specified.yml b/tasks/stop_specified.yml index 479d489..f938c7d 100644 --- a/tasks/stop_specified.yml +++ b/tasks/stop_specified.yml @@ -8,4 +8,9 @@ ansible.builtin.service: name: "{{ item.name }}" state: stopped - with_items: "{{ devture_systemd_service_manager_services_list_to_work_with | sort (attribute='priority,name', reverse=true) }}" + with_items: "{{ devture_systemd_service_manager_services_list_to_work_with | sort(attribute='priority,name', reverse=true) }}" + when: not devture_systemd_service_manager_parallel_service_setup_enabled + +- name: Ensure systemd services are stopped + ansible.builtin.include_tasks: "{{ role_path }}/tasks/stop_specified_async.yml" + when: devture_systemd_service_manager_parallel_service_setup_enabled diff --git a/tasks/stop_specified_async.yml b/tasks/stop_specified_async.yml new file mode 100644 index 0000000..60d7e7a --- /dev/null +++ b/tasks/stop_specified_async.yml @@ -0,0 +1,39 @@ +--- + +# Suppress 'risky-shell-pipe' lint error due to the required fix `set -o pipefail` not being supported by all shells. +# Additionally, echo commands generally should not fail. +- name: Ensure systemd services are stopped + ansible.builtin.shell: > + echo "{{ devture_systemd_service_manager_services_list_to_work_with | sort(attribute='priority,name', reverse=true) | join(' ', attribute='name') }}" + | + xargs -P {{ devture_systemd_service_manager_parallel_jobs }} -d ' ' -I '{}' + ansible {{ inventory_hostname }} -i {{ inventory_file }} -m service {{ '' if become is undefined else '--become' }} + -a " + name={} + state=stopped + " + tags: + - skip_ansible_lint + register: "stop_service_job_status" + delegate_to: localhost + become: false + async: "{{ devture_systemd_service_manager_parallel_async_timeout_seconds }}" + poll: 0 + changed_when: false + +- name: Check job status for stopped services + ansible.builtin.async_status: + jid: "{{ stop_service_job_status.ansible_job_id }}" + register: stop_service_job_status + until: stop_service_job_status.finished + retries: "{{ devture_systemd_service_manager_parallel_job_status_retry_limit }}" + delay: "{{ devture_systemd_service_manager_parallel_job_status_retry_delay_seconds }}" + delegate_to: localhost + become: false + +- name: Cleanup async job temp file + ansible.builtin.async_status: + jid: "{{ stop_service_job_status.ansible_job_id }}" + mode: "cleanup" + delegate_to: localhost + become: false