diff --git a/roles/sap_control/README.md b/roles/sap_control/README.md index 8e513fb..40007d7 100644 --- a/roles/sap_control/README.md +++ b/roles/sap_control/README.md @@ -1,72 +1,192 @@ + # sap_control Ansible Role + + +## Description + +The Ansible role `sap_control` executes predefined that cover range of SAP administration tasks, including: +- Start/Stop/Restart of SAP HANA Database Server. +- Start/Stop/Restart of SAP NetWeaver Application Server. +- Start/Stop/Restart/Update of SAP Netweaver System. + + + +## Disclaimer +> **IMPORTANT:** This role is designed to perform administrative actions on SAP systems, such as starting and stopping instances. +> Misuse of this role, especially in production environments, can lead to system downtime and potential data loss. +> It is crucial to understand the function you are executing and its impact on your SAP landscape. +> Always test in non-production environments before applying to production systems. Use with caution. + + + +## Dependencies +> This is optional dependency, active only when the variable `sap_control_use_sap_system_facts` is set to `true`. +- `community.sap_libs` + - Modules: + - `sap_system_facts` + - This collection is part of main Ansible package. + + + +This Ansible Role assumes that SAP Netweaver and HANA are installed in standard locations. +- The `adm` user exists for each SAP system. +- The `sapcontrol` executable is in the `PATH` of the `adm` user. + - This role validates standard `sapcontrol` location `/usr/sap///exe/sapcontrol`. +- Standard SAP directory structures are used: + - `/usr/sap` and `/sapmnt` for SAP Netweaver + - `/usr/sap` and `/hana/shared` for SAP HANA + + +## Execution + +Primary variable is `sap_control_function` and it drives all logic of this role. +The function names are constructed using the pattern: [`ACTION`]_[`SCOPE`]_[`TARGET`] + +`ACTION`: The operation to perform. +- Process-level: `start`, `stop`, `restart` +- System-level (asynchronous): `startsystem`, `stopsystem`, `restartsystem`, `updatesystem` + +`SCOPE`: The instances the action applies to. +- `all`: Applies to all detected instances of the target type. +- `sap`: Applies to a single instance specified by `sap_control_sid`. + +`TARGET`: The type of SAP system. +- `hana`: SAP HANA instances. +- `nw`: SAP NetWeaver instances. +- `sap`: All SAP instances (both `hana` and `nw`). + +Following functions are available: +| Target and Scope | Start | Stop | Restart | Other | +| --- | --- | --- | --- | --- | +| HANA | start_all_hana
start_sap_hana | stop_all_hana
stop_sap_hana | restart_all_hana
restart_sap_hana | | +| Netweaver | start_all_nw
start_sap_nw | stop_all_nw
stop_sap_nw | restart_all_nw
restart_sap_nw | | +| Combined | start_all_sap | stop_all_sap | restart_all_sap | | +| System | startsystem_all_nw
startsystem_sap_nw | stopsystem_all_nw
stopsystem_sap_nw | restartsystem_all_nw
restartsystem_sap_nw | updatesystem_all_nw
updatesystem_sap_nw | + +### System Functions +`system` functions are not operating SAP systems instance by instance, instead they leave this for `sapcontrol` to decide by utilizing functions like `StartSystem`, `StopSystem` and others. + +This role will asynchronously poll completion of these functions by getting system status (e.g. `GetSystemInstanceList`). +The default polling duration is 600 seconds (60 retries with a 10-second delay) + +For larger systems where this may not be sufficient, you can adjust the duration using the following variables: +- `sap_control_async_retries` +- `sap_control_async_delay` + + +### Execution Flow + +1. Assert and validate all variables. +2. Detect existing SAP System IDs and Instances on host. + - Alternatively collection this information using `community.sap_libs.sap_system_facts` module if the variable `sap_control_use_sap_system_facts` is set to `true`. +3. Prepare list of commands for execution. +4. Check if `sapstartsrv` service is running. + - Service is restarted when required, because it is required for `sapcontrol` commands. +5. Execute `sapcontrol` commands and wait for completion. +6. Execute `cleanipc` command unless the variable `sap_control_cleanipc` is set to `false`. +7. Show output message with current status of processes for each instance. + + +### Example + +Example of starting all SAP Netweaver instances on host(s). +```yaml +--- +- name: Ansible Play for SAP Control + hosts: all + become: true + tasks: + - name: Execute Ansible Role sap_control + ansible.builtin.include_role: + name: community.sap_operations.sap_control + vars: + sap_control_function: start_all_nw +``` -This Ansible Role executes basic SAP administration tasks on Linux operating systems. - -## Ansible Role Overview - -This Ansible Role executes basic SAP administration tasks on Linux operating systems, including: -- Start/Stop/Restart of SAP HANA Database Server -- Start/Stop/Restart of SAP NetWeaver Application Server -- Start/Stop/Restart/Update of SAP Netweaver System -- Multiple Automatic discovery and Start/Stop/Restart of SAP HANA Database Server or SAP NetWeaver Application Server - -## Example execution - -### Example Playbook - +Example of stopping `B01` SAP Netweaver and HANA instances on host(s) using `community.sap_libs.sap_system_facts`, without `cleanipc` execution. ```yaml -- hosts: sap_servers - roles: - - { role: community.sap_operations.sap_control } +--- +- name: Ansible Play for SAP Control + hosts: all + become: true + tasks: + - name: Execute Ansible Role sap_control + ansible.builtin.include_role: + name: community.sap_operations.sap_control + vars: + sap_control_function: stop_all_sap + sap_control_sid: B01 + sap_control_use_sap_system_facts: true + sap_control_cleanipc: false ``` + + +## Testing +This Ansible Role has been tested in following scenarios. +Operating systems: + - SUSE Linux Enterprise Server for SAP applications 15 SP6 and SP7 (SLE4SAP) + - SUSE Linux Enterprise Server for SAP applications 16 (SLE4SAP) -### Example Inputs +SAP Products: +- SAP Netweaver 7.50 and higher +- SAP HANA 2.0 SP04 and higher -- Using restart all - ```yaml - sap_control_function: "restart_all_sap" - ``` -- Using a specific SAP SID - ```yaml - sap_control_function: "stop_sap_hana" - sap_sid: "HDB" - ``` +## License + +Apache 2.0 + -## Ansible Role Requirements and Dependencies +## Maintainers + +- [Marcel Mamula](https://github.com/marcelmamula) + -### Operating System +## Role Variables + +### sap_control_function +- _Type:_ `string` -This role has been tested with RHEL, and is designed for Linux operating systems. +The sapcontrol function to execute on target host. +These are predefined functions defined in variable `__sap_control_function_definitions`. -This role has not been tested and amended for SAP NetWeaver Application Server instantiations on IBM AIX or Windows Server. +### sap_control_sid +- _Type:_ `string` -Assumptions for executing this role include: -- Instances of either SAP HANA Database Server or SAP NetWeaver Application Server are installed to the target host -- Relevent OS Packages for SAP are installed -- Registered OS License and OS Package repositories are available (from the relevant content delivery network of the OS vendor) +The 3-letter SAP System ID (e.g., 'PRD', 'QAS'). +This is required when executing a function that targets a specific SAP instance (e.g., `start_sap_nw`) instead of all instances on the host. -### SAP software instances +### sap_control_command_nowait +- _Type:_ `boolean` +- _Default:_ `false` -This role has been tested with SAP NetWeaver Application Server 7.53 and SAP HANA Database Server 2.0 SPS04 rev 20. +If set to `true`, the role will not wait for start/stop operations to complete. +> **NOTE:** This option should be used with caution, as the role will not verify the final status of the instance. -This role has not been tested with other versions of SAP NetWeaver Application Server, however it should work for all versions above SAP NetWeaver Application Server 7.50. +### sap_control_cleanipc +- _Type:_ `boolean` +- _Default:_ `true` -This role has not been tested with other versions of SAP HANA Database Server, however it should work for all versions above SAP HANA 2.0 SPS00. +If set to `false`, the `cleanipc` command will not be executed after stopping an SAP instance. -Assumptions for executing this role include: -- Installations used default `/usr/sap` and `/sapmnt` directories -- Installations for SAP HANA used default `/hana/shared` directory +### sap_control_use_sap_system_facts +- _Type:_ `boolean` +- _Default:_ `false` -## Ansible Role Variables +Enable this variable to use `community.sap_libs.sap_system_facts` to detect SAP instances. +This can be useful if this role is part of a playbook that expects facts set by that module. +> **NOTE:** This module will not detect instances with `sapstartsrv` stopped. -| **variable** | **info** | **required** | -| :--- |:--- | :--- | -| `SID` | SAP system SID | no, only if you are targetting a single SAP system| -| `nowait` | Default: `false` | no, use only when absolutely sure! This will bypass all waiting and ignore all necessary steps for a graceful stop / start| -| `sap_control_function` | Function to execute:
  • `restart_all_sap`
  • `restart_all_nw`
  • `restart_all_hana`
  • `restart_sap_nw`
  • `restart_sap_hana`
  • `stop_all_sap`
  • `start_all_sap`
  • `stop_all_nw`
  • `start_all_nw`
  • `stop_all_hana`
  • `start_all_hana`
  • `stop_sap_nw`
  • `start_sap_nw`
  • `stop_sap_hana`
  • `start_sap_hana`
  • `restartsystem_all_nw`
  • `updatesystem_all_nw`
  • `startsystem_all_nw`
  • `stopsystem_all_nw`
| yes, only this is required to detect the Instance Number which is used with SAP Host Agent `sapcontrol` CLI


_Note: Executions using `all` will automatically detect any System IDs and corresponding Instance Numbers_ | +### sap_control_async_retries +- _Type:_ `string` or `integer` -## Ansible Role workflow and structure +Overrides the number of retries for asynchronous tasks. +If not set, the role uses the default value defined in the function step (e.g., 60 for `startsystem_all_nw`). +Applies to functions that include `system` in their name. -The following diagram demonstrates the Ansible Role workflow, where the variable `sap_control_function` is set with different values (such as **"restart_all"**) and will perform different sequence of SAP Host Agent `sapcontrol` CLI functions. +### sap_control_async_delay +- _Type:_ `string` or `integer` -![sap_control](/docs/diagrams/workflow_role_sap_control.svg) +Overrides the delay (in seconds) between retries for asynchronous tasks. +If not set, the role uses the default value defined in the function step (e.g., 10 for `startsystem_all_nw`). +Applies to functions that include `system` in their name. + diff --git a/roles/sap_control/defaults/main.yml b/roles/sap_control/defaults/main.yml index 5a07dc4..fd7c3f5 100644 --- a/roles/sap_control/defaults/main.yml +++ b/roles/sap_control/defaults/main.yml @@ -1,163 +1,55 @@ +# SPDX-License-Identifier: Apache-2.0 --- -sap_sid: "initial" -sap_control_function: "initial" -sap_control_name_header: "initial" -nowait: false -sap_control_start: "StartWait 180 2" -sap_control_stop: "StopWait 180 2" -sap_control_startsystem: "StartSystem ALL 180" # function StartSystem waittimeout -sap_control_stopsystem: "StopSystem ALL 180 480" # function StopSystem waittimeout softtimeout -sap_control_restartsystem: "RestartSystem ALL 180 480" # function RestartSystem waittimeout softtimeout -sap_control_updatesystem: "UpdateSystem 180 480 0" # function UpdateSystem waittimeout softtimeout force -sap_control_waitforstopped: "WaitforStopped 180 2" # function WaitforStopped waittimeout delay -sap_control_waitforstarted: "WaitforStarted 180 2" # function WaitforStarted waittimeout delay - -# Parameters to handle async functions in sapcontrol_async.yml - -sap_control_startsystem_waitforasync: - test_function: "GetSystemInstanceList" - retries: 60 - delay: 10 - until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' - until_true: 'GREEN\s*$' - -sap_control_restartsystem_waitforasync: - test_function: "GetSystemInstanceList" - retries: 60 - delay: 10 - until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' - until_true: 'GREEN\s*$' - -sap_control_stopsystem_waitforasync: - test_function: "GetSystemInstanceList" - retries: 60 - delay: 10 - until_false: 'GREEN\s*$|RED\s*$|YELLOW\s*$' - until_true: 'GRAY\s*$' - -sap_control_updatesystem_waitforasync: - test_function: "GetSystemUpdateList" - retries: 60 - delay: 10 - until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$|GREEN\s*$' - -# get_all_sap_sid_dir_nw: "/sapmnt" -# get_all_sap_sid_dir_hana: "/hana/shared" - -# Sort Order to start SAP instances defined by SAP and recommended by SAP Basis Administrators -sap_control_instance_type_sortorder: - - "HDB" - - "ERS" - - "ASCS" - - "SCS" - - "PAS" - - "Java" - - "WebDisp" - -# Functions -sap_control_functions_list: - - restartsystem_all_nw - - updatesystem_all_nw - - startsystem_all_nw - - stopsystem_all_nw - - restartsystem_sap_nw - - updatesystem_sap_nw - - startsystem_sap_nw - - stopsystem_sap_nw - - restart_all_sap - - stop_all_sap - - start_all_sap - - restart_all_nw - - restart_all_hana - - stop_all_nw - - start_all_nw - - stop_all_hana - - start_all_hana - - restart_sap_nw - - restart_sap_hana - - stop_sap_nw - - start_sap_nw - - stop_sap_hana - - start_sap_hana - - -# Functions flow -restartsystem_all_nw_list: - - sap_control_function_current: "nw_restartsystem" - -startsystem_all_nw_list: - - sap_control_function_current: "nw_startsystem" - -stopsystem_all_nw_list: - - sap_control_function_current: "nw_stopsystem" - -updatesystem_all_nw_list: - - sap_control_function_current: "nw_startsystem" - - sap_control_function_current: "nw_updatesystem" - -restartsystem_sap_nw_list: - - sap_control_function_current: "nw_restartsystem" - -updatesystem_sap_nw_list: - - sap_control_function_current: "nw_updatesystem" - -startsystem_sap_nw_list: - - sap_control_function_current: "nw_startsystem" - -stopsystem_sap_nw_list: - - sap_control_function_current: "nw_stopsystem" - -restart_all_sap_list: - - sap_control_function_current: "nw_stop" - - sap_control_function_current: "hana_stop" - - sap_control_function_current: "hana_start" - - sap_control_function_current: "nw_start" - -stop_all_sap_list: - - sap_control_function_current: "nw_stop" - - sap_control_function_current: "hana_stop" - -start_all_sap_list: - - sap_control_function_current: "hana_start" - - sap_control_function_current: "nw_start" - -restart_all_nw_list: - - sap_control_function_current: "nw_stop" - - sap_control_function_current: "nw_start" - -restart_all_hana_list: - - sap_control_function_current: "hana_stop" - - sap_control_function_current: "hana_start" - -stop_all_nw_list: - - sap_control_function_current: "nw_stop" - -start_all_nw_list: - - sap_control_function_current: "nw_start" - -stop_all_hana_list: - - sap_control_function_current: "hana_stop" - -start_all_hana_list: - - sap_control_function_current: "hana_start" - -restart_sap_nw_list: - - sap_control_function_current: "nw_stop" - - sap_control_function_current: "nw_start" - -restart_sap_hana_list: - - sap_control_function_current: "hana_stop" - - sap_control_function_current: "hana_start" - -stop_sap_nw_list: - - sap_control_function_current: "nw_stop" - -start_sap_nw_list: - - sap_control_function_current: "nw_start" - -stop_sap_hana_list: - - sap_control_function_current: "hana_stop" - -start_sap_hana_list: - - sap_control_function_current: "hana_start" +# The 3-letter SAP System ID (e.g., 'PRD', 'QAS'). +# This is required when executing a function that targets a specific SAP instance +# (e.g., 'start_sap_nw') instead of all instances on the host. +sap_control_sid: '' + +# The sapcontrol function to execute on target host. +# These are predefined functions defined in variable '__sap_control_function_definitions'. +# +# The function names are constructed using the pattern: [ACTION]_[SCOPE]_[TARGET] +# [ACTION]: The operation to perform. +# - Process-level: 'start', 'stop', 'restart' +# - System-level (asynchronous): 'startsystem', 'stopsystem', 'restartsystem', 'updatesystem' +# +# [SCOPE]: The instances the action applies to. +# - 'all': Applies to all detected instances of the target type. +# - 'sap': Applies to a single instance specified by 'sap_control_sid'. +# +# [TARGET]: The type of SAP system. +# - 'hana': SAP HANA instances. +# - 'nw': SAP NetWeaver instances. +# - 'sap': All SAP instances (both 'hana' and 'nw'). +# +# Currently available options: +# start_all_hana, stop_all_hana, start_sap_hana, stop_sap_hana, restart_all_hana, restart_sap_hana, +# start_all_nw, stop_all_nw, start_sap_nw, stop_sap_nw, restart_all_nw, restart_sap_nw, +# start_all_sap, stop_all_sap, restart_all_sap, +# startsystem_all_nw, stopsystem_all_nw, startsystem_sap_nw, stopsystem_sap_nw, +# restartsystem_all_nw, restartsystem_sap_nw, updatesystem_all_nw, updatesystem_sap_nw +# +sap_control_function: '' + +# If set to true, the role will not wait for start/stop operations to complete. +# This option should be used with caution, as the role will not verify the final status of the instance. +# sap_control_command_nowait: false + +# If set to false, the 'cleanipc' command will not be executed after stopping an SAP instance. +# sap_control_cleanipc: true + +# Overrides the number of retries for asynchronous tasks. +# If not set, the role uses the default value defined in the function step (e.g., 60 for 'startsystem_all_nw'). +# Applies to functions that include 'system' in their name. +# sap_control_async_retries: '60' + +# Overrides the delay (in seconds) between retries for asynchronous tasks. +# If not set, the role uses the default value defined in the function step (e.g., 10 for 'startsystem_all_nw'). +# Applies to functions that include 'system' in their name. +# sap_control_async_delay: '10' + +# Enable this variable to use 'community.sap_libs.sap_system_facts' to detect SAP instances. +# This can be useful if this role is part of a playbook that expects facts set by that module. +# NOTE: This module will not detect instances with 'sapstartsrv' stopped. +# sap_control_use_sap_system_facts: false diff --git a/roles/sap_control/tasks/actions/async_monitor.yml b/roles/sap_control/tasks/actions/async_monitor.yml new file mode 100644 index 0000000..4f4f467 --- /dev/null +++ b/roles/sap_control/tasks/actions/async_monitor.yml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +- name: Block to wait for status to change if using restart or update + when: + - sap_control_function is match('restart|update') + block: + - name: SAP Control - Get initial system state for instance - {{ command_item['nr'] }} # noqa no-changed-when + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ command_item['nr'] }} + -function {{ command_item['async']['test_function'] | d('GetSystemInstanceList') }} + become: true + become_user: "{{ command_item['user'] }}" + register: __sap_control_register_async_update_before + changed_when: false + + - name: SAP Control - Wait for initial state change for instance - {{ command_item['nr'] }} + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ command_item['nr'] }} + -function {{ command_item['async']['test_function'] | d('GetSystemInstanceList') }} + become: true + become_user: "{{ command_item['user'] }}" + register: __sap_control_register_async_update_after + changed_when: false + retries: "{{ sap_control_async_retries | d(command_item['async']['retries']) | d(0) | int }}" + delay: "{{ sap_control_async_delay | d(command_item['async']['delay']) | d(0) | int }}" + until: > + (__sap_control_register_async_update_after.stdout + | regex_findall('GREEN|YELLOW|GRAY|RED', multiline=True) | sort | join(',')) + != (__sap_control_register_async_update_before.stdout + | regex_findall('GREEN|YELLOW|GRAY|RED', multiline=True) | sort | join(',')) + + +# This is hardcoded 20 second wait as safety measure so we give SAP time to process our commands. +- name: SAP Control - Wait for 20 Seconds to before checking state for instance - {{ command_item['nr'] }} + ansible.builtin.wait_for: + timeout: 20 + +- name: SAP Control - Poll for final system state for instance - {{ command_item['nr'] }} + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ command_item['nr'] }} + -function {{ command_item['async']['test_function'] | d('GetSystemInstanceList') }} + become: true + become_user: "{{ command_item['user'] }}" + register: __sap_control_register_async_command_output + changed_when: false + retries: "{{ sap_control_async_retries | d(command_item['async']['retries']) | d(0) | int }}" + delay: "{{ sap_control_async_delay | d(command_item['async']['delay']) | d(0) | int }}" + until: > + ( command_item['async']['until_false'] is not defined + or __sap_control_register_async_command_output.stdout + | regex_search(command_item['async']['until_false'], multiline=True) is none) + and + (command_item['async']['until_true'] is not defined + or __sap_control_register_async_command_output.stdout + | regex_search(command_item['async']['until_true'], multiline=True) is not none) diff --git a/roles/sap_control/tasks/actions/sapcontrol.yml b/roles/sap_control/tasks/actions/sapcontrol.yml new file mode 100644 index 0000000..5a2d97e --- /dev/null +++ b/roles/sap_control/tasks/actions/sapcontrol.yml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +- name: SAP Control - Show command to be executed for instance - {{ command_item['nr'] }} + ansible.builtin.debug: + msg: | + User: {{ command_item['user'] }} + Command: {{ command_item['command'] }} + +- name: SAP Control - Execute sapcontrol command for instance - {{ command_item['nr'] }} # noqa command-instead-of-shell no-changed-when + ansible.builtin.shell: + cmd: "{{ command_item['command'] }}" + become: true + become_user: "{{ command_item['user'] }}" + register: __sap_control_register_command_output + ignore_errors: true + + +- name: SAP Control - Start asynchronous monitoring for instance - {{ command_item['nr'] }} + ansible.builtin.include_tasks: + file: actions/async_monitor.yml + when: + - command_item['async'] is defined + - command_item['async'] | length > 0 + + +- name: SAP Control - Execute 'cleanipc' command for instance - {{ command_item['nr'] }} + ansible.builtin.shell: + cmd: >- + source ~/.profile && + cleanipc {{ command_item['nr'] }} remove + become: true + become_user: "{{ command_item['user'] }}" + register: __sap_control_register_cleanipc + when: + - sap_control_cleanipc | d(true) + - command_item['name'] is match('stop') + changed_when: >- + (__sap_control_register_cleanipc.stdout_lines + | select('match', '^Number of IPC-Objects.*') + | first + | d('Number of IPC-Objects: 0') + | split(':'))[1] + | trim + | int > 0 + + +- name: SAP Control - Get final status for instance - {{ command_item['nr'] }} + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ command_item['nr'] }} + -function {{ __function }} + become: true + become_user: "{{ command_item['user'] }}" + register: __sap_control_register_final_status + changed_when: false + # We have to ignore errors because sapcontrol does not return correct return codes. + failed_when: false + ignore_errors: true + vars: + __function: >- + {{ 'GetSystemInstanceList' + if command_item['command'] is match('.*system$') + or (sap_control_function.split('_')[0] | lower) is match('.*system$') + else + 'GetProcessList' }} + + +- name: SAP Control - Show final status of instance - {{ command_item['nr'] }} + ansible.builtin.debug: + msg: | + Execution of sapcontrol command was finished. + User: {{ command_item['user'] }} + Command: {{ command_item['command'] }} + + {% if sap_control_command_nowait | d(false) + and (command_item['async'] is not defined or command_item['async'] | length == 0) %} + This role was executed with 'sap_control_command_nowait' set to 'true', + which skipped waiting for command and reported status can be inaccurate. + {% endif %} + + Status: + {{ __sap_control_register_final_status.stdout }} diff --git a/roles/sap_control/tasks/actions/sapstartsrv.yml b/roles/sap_control/tasks/actions/sapstartsrv.yml new file mode 100644 index 0000000..da10c82 --- /dev/null +++ b/roles/sap_control/tasks/actions/sapstartsrv.yml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +- name: SAP Control - Get status of 'sapstartsrv' service for instance - {{ __command_nr }} + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ __command_nr }} + -function GetSystemInstanceList + become: true + become_user: "{{ __command_user }}" + register: __sap_control_register_sapstartsrv_check + ignore_errors: true + changed_when: false + + +- name: Block to restart 'sapstartsrv' + when: >- + 'FAIL' in __sap_control_register_sapstartsrv_check.stdout + or 'NIECONN_REFUSED' in __sap_control_register_sapstartsrv_check.stdout + block: + - name: SAP Control - Stop 'sapstartsrv' service for instance - {{ __command_nr }} # noqa no-changed-when + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ __command_nr }} + -function StopService + become: true + become_user: "{{ __command_user }}" + register: __sap_control_register_sapstartsrv_stop + ignore_errors: true + + - name: SAP Control - Start 'sapstartsrv' service for instance - {{ __command_nr }} # noqa no-changed-when + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ __command_nr }} + -function StartService {{ __command_sid | upper }} + become: true + become_user: "{{ __command_user }}" + register: __sap_control_register_sapstartsrv_start + ignore_errors: true + + - name: SAP Control - Get status of 'sapstartsrv' service for instance - {{ __command_nr }} + ansible.builtin.shell: + cmd: >- + source ~/.profile && + sapcontrol + -nr {{ __command_nr }} + -function GetSystemInstanceList + become: true + become_user: "{{ __command_user }}" + register: __sap_control_register_sapstartsrv_recheck + ignore_errors: true + changed_when: false + + + - name: SAP Control - Fail when 'sapstartsrv' cannot be verified for instance - {{ __command_nr }} + ansible.builtin.fail: + msg: | + FAIL: Unable to verify that 'sapstartsrv' is running. + Please check for issues with service with command: + 'sapcontrol -nr {{ __command_nr }} -function GetSystemInstanceList' + when: >- + 'FAIL' in __sap_control_register_sapstartsrv_recheck.stdout + or 'NIECONN_REFUSED' in __sap_control_register_sapstartsrv_recheck.stdout + + + - name: SAP Control - Wait for 10 seconds for sapstartsrv to initialize for instance - {{ __command_nr }} + ansible.builtin.wait_for: + timeout: 10 + when: + - __sap_control_register_sapstartsrv_stop.changed + or __sap_control_register_sapstartsrv_start.changed diff --git a/roles/sap_control/tasks/assert_variables.yml b/roles/sap_control/tasks/assert_variables.yml new file mode 100644 index 0000000..31bb143 --- /dev/null +++ b/roles/sap_control/tasks/assert_variables.yml @@ -0,0 +1,197 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +- name: SAP Control - Assert that functions definition variable is defined as a dictionary + ansible.builtin.assert: + that: + - __sap_control_function_definitions is defined + - __sap_control_function_definitions is mapping + fail_msg: | + FAIL: The functions definition variable must be a dictionary, + but it is of type '{{ __sap_control_function_definitions | type_debug }}'. + + This variable is predefined in 'vars/main.yml' and it should not be removed. + Variable: '__sap_control_function_definitions' + + +- name: SAP Control - Assert that the variable 'sap_control_function' is defined as string + ansible.builtin.assert: + that: + - sap_control_function is defined + - sap_control_function is string + - sap_control_function | length > 0 + - sap_control_function in __sap_control_function_definitions.keys() + fail_msg: | + {% if sap_control_function is not defined %} + FAIL: The 'sap_control_function' variable must be defined as non-empty string. + {% elif sap_control_function is not string %} + FAIL: The 'sap_control_function' variable must be a string. + {% elif sap_control_function | trim | length == 0 %} + FAIL: The 'sap_control_function' variable cannot be empty. + {% else %} + FAIL: The 'sap_control_function' variable value is not valid. + Value: {{ sap_control_function }} + It must be one of the following: + {{ __sap_control_function_definitions.keys() | join(', ') }} + {% endif %} + + +- name: SAP Control - Set fact with chosen function definition + ansible.builtin.set_fact: + __sap_control_function: "{{ __sap_control_function_definitions[sap_control_function] | d({}) }}" + + +- name: SAP Control - Assert that the functions definition variable contains required keys + ansible.builtin.assert: + that: + # target must be 'all' or 'sid' + - __sap_control_function['target'] is defined + - __sap_control_function['target'] is string + - __sap_control_function['target'] in ['all', 'sid'] + + # steps must be a non-empty list + - __sap_control_function['steps'] is defined + - __sap_control_function['steps'] | type_debug == 'list' + - __sap_control_function['steps'] | length > 0 + + fail_msg: | + FAIL: Validation failed for function '{{ sap_control_function }}'. + Function has to be defined with required keys in '__sap_control_function_definitions'. + Revert your changes in 'vars/main.yml', if you modified it, otherwise raise issue. + + +- name: SAP Control - Assert that steps in functions definition are valid + ansible.builtin.assert: + that: + # Each step must be a dictionary and contain a 'command' key + - command_step_item is mapping + - command_step_item['command'] is defined + - command_step_item['command'] is string + # The command must be defined in __sap_control_command_definitions + - command_step_item['command'] in __sap_control_command_definitions + # The command type must be defined + - command_step_item['type'] is defined + - command_step_item['type'] is string + - command_step_item['type'] in ['nw', 'hana', 'system'] + # The async is optional + - command_step_item['async'] is not defined + or command_step_item['async'] is mapping + fail_msg: | + FAIL: Invalid step found in function '{{ sap_control_function }}'. + + Command: '{{ command_step_item['command'] | d('N/A') }}' + Command must be one of the following: {{ __sap_control_command_definitions.keys() | join(', ') }} + + Type: '{{ command_step_item['type'] | d('N/A') }}' + Type must be one of the following: 'nw', 'hana', 'system'. + quiet: true + loop: "{{ __sap_control_function_definitions[sap_control_function]['steps'] }}" + loop_control: + loop_var: command_step_item + label: "Step Command: {{ command_step_item['command'] | d('N/A') }}" + + +- name: SAP Control - Assert that the variable 'sap_control_sid' is defined as 3 letter string + ansible.builtin.assert: + that: + - sap_control_sid is defined + - sap_control_sid is string + - sap_control_sid | length == 3 + fail_msg: | + {% if sap_control_sid is not defined %} + FAIL: The 'sap_control_sid' variable must be defined as non-empty string. + {% elif sap_control_sid is not string %} + FAIL: The 'sap_control_sid' variable must be a string. + {% else %} + FAIL: The 'sap_control_sid' variable must be 3 letter string + {% endif %} + when: __sap_control_function['target'] == 'sid' + + +- name: SAP Control - Assert that async steps have valid mapping + ansible.builtin.assert: + that: + # The 'test_function' is optional with default to 'GetSystemInstanceList' + - command_async_item['async']['test_function'] is not defined + or (command_async_item['async']['test_function'] is string and command_async_item['async']['test_function'] | length > 0) + + # These keys are optional, but if they exist, they must be integers or strings representing integers. + - command_async_item['async']['retries'] is not defined + or (command_async_item['async']['retries'] is number + or (command_async_item['async']['retries'] is string and command_async_item['async']['retries'] | trim is match('^[0-9]+$'))) + + - command_async_item['async']['delay'] is not defined + or (command_async_item['async']['delay'] is number + or (command_async_item['async']['delay'] is string and command_async_item['async']['delay'] | trim is match('^[0-9]+$'))) + + # At least one 'until' condition must be defined for the async wait. + - (command_async_item['async']['until_true'] is defined + and command_async_item['async']['until_true'] is string + and command_async_item['async']['until_true'] | length > 0) + or (command_async_item['async']['until_false'] is defined + and command_async_item['async']['until_false'] is string + and command_async_item['async']['until_false'] | length > 0) + fail_msg: | + FAIL: Invalid 'async' definition for step '{{ command_async_item['command'] }}' in function '{{ sap_control_function }}'. + - 'test_function' must be defined as string if defined. + - 'retries' and 'delay' must be numbers or strings representing numbers if defined. + - At least one of 'until_true' or 'until_false' must be defined. + quiet: true + loop: "{{ __sap_control_function_definitions[sap_control_function]['steps'] }}" + loop_control: + loop_var: command_async_item + label: "Async Step: {{ command_async_item['command'] }}" + when: command_async_item['async'] is defined and command_async_item['async'] is mapping + + +- name: SAP Control - Assert that the variable 'sap_control_async_retries' is valid + ansible.builtin.assert: + that: + - sap_control_async_retries is number + or (sap_control_async_retries is string + and sap_control_async_retries | length > 0 + and sap_control_async_retries | trim is match('^[0-9]+$')) + fail_msg: | + FAIL: The 'sap_control_async_retries' variable must be defined as number or string representing number. + Value: '{{ sap_control_async_retries }}' of type '{{ sap_control_async_retries | type_debug }}'. + when: sap_control_async_retries is defined + +- name: SAP Control - Assert that the variable 'sap_control_async_delay' is valid + ansible.builtin.assert: + that: + - sap_control_async_delay is number + or (sap_control_async_delay is string + and sap_control_async_delay | length > 0 + and sap_control_async_delay | trim is match('^[0-9]+$')) + fail_msg: | + FAIL: The 'sap_control_async_delay' variable must be defined as number or string representing number. + Value: '{{ sap_control_async_delay }}' of type '{{ sap_control_async_delay | type_debug }}'. + when: sap_control_async_delay is defined + + +- name: SAP Control - Assert that the variable 'sap_control_command_nowait' is boolean + ansible.builtin.assert: + that: + - sap_control_command_nowait is boolean + fail_msg: | + FAIL: The 'sap_control_command_nowait' variable must be defined as boolean. + Value: '{{ sap_control_command_nowait }}' of type '{{ sap_control_command_nowait | type_debug }}'. + when: sap_control_command_nowait is defined + +- name: SAP Control - Assert that the variable 'sap_control_cleanipc' is boolean + ansible.builtin.assert: + that: + - sap_control_cleanipc is boolean + fail_msg: | + FAIL: The 'sap_control_cleanipc' variable must be defined as boolean. + Value: '{{ sap_control_cleanipc }}' of type '{{ sap_control_cleanipc | type_debug }}'. + when: sap_control_cleanipc is defined + +- name: SAP Control - Assert that the variable 'sap_control_use_sap_system_facts' is boolean + ansible.builtin.assert: + that: + - sap_control_use_sap_system_facts is boolean + fail_msg: | + FAIL: The 'sap_control_use_sap_system_facts' variable must be defined as boolean. + Value: '{{ sap_control_use_sap_system_facts }}' of type '{{ sap_control_use_sap_system_facts | type_debug }}'. + when: sap_control_use_sap_system_facts is defined diff --git a/roles/sap_control/tasks/detection/detect_instances.yml b/roles/sap_control/tasks/detection/detect_instances.yml new file mode 100644 index 0000000..1943a63 --- /dev/null +++ b/roles/sap_control/tasks/detection/detect_instances.yml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +- name: SAP Control - Find directories in {{ __sap_control_detect_dir }} + ansible.builtin.find: + paths: "{{ __sap_control_detect_dir }}/{{ sid_item }}" + file_type: directory + patterns: '*' + depth: 1 + register: __sap_control_register_instance_dirs + ignore_errors: true + +# Filter to include only directories matching SAP instance name structure (Type + 2 Digits) +- name: SAP Control - Determine SAP Instances in {{ __sap_control_detect_dir }} + ansible.builtin.set_fact: + __sap_control_fact_potential_instance_dirs: >- + {{ __sap_control_register_instance_dirs.files + | map(attribute='path') | map('basename') + | select('match', '^[A-Z]+\d{2}$') + | list }} + +- name: SAP Control - Validate Instance by checking for the sapcontrol executable + ansible.builtin.stat: + path: "/usr/sap/{{ sid_item }}/{{ instance_dir }}/exe/sapcontrol" + register: __sap_control_register_potential_instance_dirs_stat + loop: "{{ __sap_control_fact_potential_instance_dirs }}" + loop_control: + loop_var: instance_dir + +- name: SAP Control - Create list of Instances with sapcontrol executable present + ansible.builtin.set_fact: + __sap_control_fact_valid_instance_dirs: > + {{ __sap_control_register_potential_instance_dirs_stat.results + | selectattr('stat.exists', 'defined') + | selectattr('stat.exists', 'equalto', true) + | map(attribute='instance_dir') + | list }} + +# Generate output list with identical format as 'community.sap_libs.sap_system_facts'. +# __sap_control_fact_detected_instances: +# - InstanceType: HANA +# NR: '90' +# SID: H01 +# TYPE: HDB +# - InstanceType: NW +# NR: '11' +# SID: B01 +# TYPE: PAS +- name: SAP Control - Add detected instances into system facts + ansible.builtin.set_fact: + __sap_control_fact_detected_instances: + "{{ __sap_control_fact_detected_instances | d([]) + __system_fragment }}" + loop: "{{ __sap_control_fact_valid_instance_dirs }}" + loop_control: + loop_var: instance_item + vars: + __instance_prefix: "{{ instance_item[0] }}" + __type: >- + {{ __sap_control_type_dict[__instance_prefix] + if __instance_prefix in __sap_control_type_dict.keys() + else 'XXX' }} + __system_fragment: + - 'InstanceType': "{{ __sap_control_detect_instance_type }}" + 'NR': "{{ instance_item[-2:] }}" + 'SID': "{{ sid_item }}" + 'TYPE': "{{ __type }}" diff --git a/roles/sap_control/tasks/detection/detect_sap.yml b/roles/sap_control/tasks/detection/detect_sap.yml new file mode 100644 index 0000000..4caadf6 --- /dev/null +++ b/roles/sap_control/tasks/detection/detect_sap.yml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +# This role mimics detection of SAP Instances done by 'community.sap_libs.sap_system_facts'. +# By replicating these steps into native Ansible code, dependency on 'sap_libs' is removed. + +- name: Block for built in detection + when: not sap_control_use_sap_system_facts | d(false) + block: + - name: SAP Control - Gather SAP SIDs on host + ansible.builtin.include_tasks: + file: detection/detect_sids.yml + + - name: SAP Control - Gather SAP Netweaver Instances on host + ansible.builtin.include_tasks: + file: detection/detect_instances.yml + loop: "{{ __sap_control_fact_nw_sids }}" + loop_control: + loop_var: sid_item + vars: + __sap_control_detect_instance_type: 'NW' + __sap_control_detect_dir: '/usr/sap' + + - name: SAP Control - Gather SAP HANA Instances on host + ansible.builtin.include_tasks: + file: detection/detect_instances.yml + loop: "{{ __sap_control_fact_hana_sids }}" + loop_control: + loop_var: sid_item + vars: + __sap_control_detect_instance_type: 'HANA' + __sap_control_detect_dir: '/hana/shared' + +- name: SAP Control - Gather SAP facts using 'sap_system_facts' module + community.sap_libs.sap_system_facts: + when: + - sap_control_use_sap_system_facts | d(false) + +- name: SAP Control - Set fact with final list of instances + ansible.builtin.set_fact: + __sap_control_fact_instances: >- + {{ __sap_control_fact_detected_instances + if not sap_control_use_sap_system_facts | d(false) + else ansible_facts['sap'] }} diff --git a/roles/sap_control/tasks/detection/detect_sids.yml b/roles/sap_control/tasks/detection/detect_sids.yml new file mode 100644 index 0000000..7e22903 --- /dev/null +++ b/roles/sap_control/tasks/detection/detect_sids.yml @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +# Detection follows default SAP directories: +# /hana/shared - Contains HANA SIDs, but they must be present in /usr/sap. +# /sapmnt - Contains Netweaver SIDs, but they must be present in /usr/sap. +# Expected SID format is: 3 capital letters or digits using '^[A-Z][A-Z0-9][A-Z0-9]$' + +- name: SAP Control - Find directories in '/usr/sap' + ansible.builtin.find: + paths: "/usr/sap" + file_type: directory + patterns: '*' + depth: 1 + register: __sap_control_register_usr_sap_dirs + ignore_errors: true + +- name: SAP Control - Find directories in '/sapmnt' + ansible.builtin.find: + paths: "/sapmnt" + file_type: directory + patterns: '*' + depth: 1 + register: __sap_control_register_sapmnt_dirs + ignore_errors: true + +- name: SAP Control - Find directories in '/hana/shared' + ansible.builtin.find: + paths: "/hana/shared" + file_type: directory + patterns: '*' + depth: 1 + register: __sap_control_register_hana_shared_dirs + ignore_errors: true + +- name: SAP Control - Determine SIDs in /usr/sap + ansible.builtin.set_fact: + __sap_control_fact_usr_sap_sids: >- + {{ __sap_control_register_usr_sap_dirs.files + | map(attribute='path') |map('basename') + | select('match', '^[A-Z][A-Z0-9][A-Z0-9]$') + | list }} + + +# Get list of all operating system users. +# This is required before checking existence of .profile. +- name: SAP Control - Get list of available OS users + ansible.builtin.getent: + database: passwd + +- name: SAP Control - Validate SID by checking for .profile file + ansible.builtin.stat: + path: "~/.profile" + become: true + become_user: "{{ sid_item | lower }}adm" + register: __sap_control_register_sid_profile_stat + loop: "{{ __sap_control_fact_usr_sap_sids }}" + loop_control: + loop_var: sid_item + when: "(sid_item | lower ~ 'adm') in ansible_facts['getent_passwd']" + +- name: SAP Control - Determine SIDs in /usr/sap + ansible.builtin.set_fact: + __sap_control_fact_usr_sap_sids_valid: >- + {{ __sap_control_register_sid_profile_stat.results + | selectattr('stat.exists', 'defined') + | selectattr('stat.exists', 'equalto', true) + | map(attribute='sid_item') + | list }} + +- name: SAP Control - Inform about skipped SIDs + ansible.builtin.debug: + msg: | + WARN: Following SIDs were skipped because either: + - OS user 'adm' is missing + - Profile file '~/.profile' is missing + SIDs: {{ __diff | join(', ') }} + when: + - __diff | length > 0 + vars: + __diff: "{{ __sap_control_fact_usr_sap_sids | difference(__sap_control_fact_usr_sap_sids_valid) }}" + + +- name: SAP Control - Determine SAP SIDs in /sapmnt and /hana/shared + ansible.builtin.set_fact: + __sap_control_fact_nw_sids: + "{{ __sap_control_fact_usr_sap_sids_valid | intersect(__nw_sids) }}" + __sap_control_fact_hana_sids: + "{{ __sap_control_fact_usr_sap_sids_valid | intersect(__hana_sids) }}" + vars: + # List of SIDs that need to be validated against /usr/sap using intersect + __nw_sids: >- + {{ __sap_control_register_sapmnt_dirs.files + | map(attribute='path') |map('basename') + | select('match', '^[A-Z][A-Z0-9][A-Z0-9]$') + | list }} + __hana_sids: >- + {{ __sap_control_register_hana_shared_dirs.files + | map(attribute='path') |map('basename') + | select('match', '^[A-Z][A-Z0-9][A-Z0-9]$') + | list }} diff --git a/roles/sap_control/tasks/functions/cleanipc.yml b/roles/sap_control/tasks/functions/cleanipc.yml deleted file mode 100644 index 0a20b5b..0000000 --- a/roles/sap_control/tasks/functions/cleanipc.yml +++ /dev/null @@ -1,11 +0,0 @@ -- name: SAP {{ sap_control_name_header }} - Cleanipc {{ passed_sap_nr }} - ansible.builtin.shell: | - source ~/.profile && cleanipc {{ passed_sap_nr }} remove - args: - executable: /bin/bash - become: true - become_method: sudo - become_user: "{{ passed_sap_sid | lower }}adm" - register: cleanipc - changed_when: - - "'Number of IPC-Objects...........: 0' not in cleanipc.stdout" diff --git a/roles/sap_control/tasks/functions/restart_sapstartsrv.yml b/roles/sap_control/tasks/functions/restart_sapstartsrv.yml deleted file mode 100644 index 73165c1..0000000 --- a/roles/sap_control/tasks/functions/restart_sapstartsrv.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Restart sapstartsrv - -# Get number of sapstartsrv processes running for {{ passed_sap_sid }}-{{ passed_sap_nr }} -- name: SAPstartsrv - Get number of sapstartsrv processes running for {{ passed_sap_sid }}-{{ passed_sap_nr }} - ansible.builtin.shell: | - ps -ef | grep {{ passed_sap_sid }} | grep {{ passed_sap_nr }} | grep sapstartsrv | awk '{ print $8 }' - register: num_of_sapstartsrv_reg - -- name: SAPstartsrv - Set fact for number of sapstartsrv - ansible.builtin.set_fact: - num_of_sapstartsrv: "{{ num_of_sapstartsrv_reg.stdout.split() }}" - -# Stop sapstartsrv -- name: SAPstartsrv - Stop sapstartsrv {{ passed_sap_sid }}-{{ passed_sap_nr }} - ansible.builtin.shell: | - source ~/.profile ; sapcontrol -nr {{ passed_sap_nr }} -function StopService {{ passed_sap_sid }} - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - register: sap_stop_sapstartsrv - loop: "{{ num_of_sapstartsrv }}" - delay: 2 - -# Start sapstartsrv -- name: SAPstartsrv - Start sapstartsrv {{ passed_sap_sid }}-{{ passed_sap_nr }} - ansible.builtin.shell: | - source ~/.profile ; sapcontrol -nr {{ passed_sap_nr }} -function StartService {{ passed_sap_sid }} - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - register: sap_start_sapstartsrv - -- name: SAPstartsrv - Wait for 10 seconds for sapstartsrv to initialize - ansible.builtin.wait_for: - timeout: 10 diff --git a/roles/sap_control/tasks/functions/sapstartsrv.yml b/roles/sap_control/tasks/functions/sapstartsrv.yml deleted file mode 100644 index 9f30959..0000000 --- a/roles/sap_control/tasks/functions/sapstartsrv.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -# This task requires the variables -# passed_sap_sid -# passed_sap_nr - -# Check sapstartsrv -- name: SAPstartsrv - Check sapstartsrv - ansible.builtin.shell: | - source ~/.profile ; sapcontrol -nr {{ passed_sap_nr }} -function GetSystemInstanceList - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - ignore_errors: true - register: check_sapstartsrv - changed_when: - - ('FAIL' in check_sapstartsrv.stdout) - -# Check sapstartsrv -- name: SAPstartsrv - Restart sapstartsrv - ansible.builtin.include_tasks: restart_sapstartsrv.yml - when: - - ('FAIL' in check_sapstartsrv.stdout) diff --git a/roles/sap_control/tasks/main.yml b/roles/sap_control/tasks/main.yml index ffde9fe..a7eaeac 100644 --- a/roles/sap_control/tasks/main.yml +++ b/roles/sap_control/tasks/main.yml @@ -1,133 +1,61 @@ +# SPDX-License-Identifier: Apache-2.0 --- -# Lowercase input -- name: Lowercase input - ansible.builtin.set_fact: - sap_control_function: "{{ sap_control_function | lower }}" -# Check inputs -- name: Check function validity - ansible.builtin.fail: - msg: "Function {{ sap_control_function }} is invalid" - when: - - "sap_control_function not in sap_control_functions_list" - -- name: Check function if defined - ansible.builtin.fail: - msg: "No sap_control_function defined" - when: - - "sap_control_function == 'initial'" +- name: SAP Control - Assert variables + ansible.builtin.include_tasks: + file: assert_variables.yml -# - name: Check function - all and SID -# ansible.builtin.fail: -# msg: "An 'all' function and a 'SID' cant be used at the same time" -# when: -# - (sap_sid != 'initial') and ('all' in sap_control_function) +- name: SAP Control - Gather facts about SAP Instances on the host + ansible.builtin.include_tasks: + file: detection/detect_sap.yml -- name: Check function - all and SID - ansible.builtin.fail: - msg: "Parameter 'sap_sid' is required when not using an 'all' function" +- name: SAP Control - Prepare commands to be executed + ansible.builtin.include_tasks: + file: prepare.yml when: - - (sap_sid == 'initial') and ('all' not in sap_control_function) - -# Set facts when nowait is true -- name: Set facts when nowait is true - ansible.builtin.set_fact: - sap_control_start: "Start" - sap_control_stop: "Stop" - when: nowait | bool - -# sap_control_functions_list: -# - restart_all_sap -# - stop_all_sap -# - start_all_sap -# - restart_all_nw -# - restart_all_hana -# - stop_all_nw -# - start_all_nw -# - stop_all_hana -# - start_all_hana -# - restart_sap_nw -# - restart_sap_hana -# - stop_sap_nw -# - start_sap_nw -# - stop_sap_hana -# - start_sap_hana - -# These facts were only used for the sap_system_facts module, but it doesn't accept sap_facts_param anymore. -# - name: Set function list facts -# ansible.builtin.set_fact: -# sap_control_act_type: "{{ sap_control_function.split('_')[0] | lower }}" -# sap_control_get_type: "{{ sap_control_function.split('_')[1] | lower }}" -# sap_control_sap_type: "{{ sap_control_function.split('_')[2] | lower }}" - -# - name: Set sap_facts_param -# ansible.builtin.set_fact: -# sap_facts_param: "{{ sap_control_get_type }}" - -# - name: Set sap_facts_param -# ansible.builtin.set_fact: -# sap_facts_param: "{{ sap_control_sap_type }}" -# when: -# - "'all' in sap_control_function" -# - "'sap' not in sap_control_sap_type" + - __sap_control_fact_instances is defined + - __sap_control_fact_instances | length > 0 -# # When not all -# - name: Set sap_facts_param -# ansible.builtin.set_fact: -# sap_facts_param: "{{ sap_sid }}" -# when: -# - "'all' not in sap_control_function" -# # Get SAP Info -# - name: Get SAP Info -# vars: -# sap_info_get_function: "get_{{ sap_control_get_type }}_{{ sap_control_sap_type }}" -# ansible.builtin.include_role: -# name: roles/sap_info - -# # Get SAP Info -# - name: Get SAP Info -# vars: -# sap_info_get_function: "get_{{ sap_control_get_type }}_{{ sap_control_sap_type }}" -# ansible.builtin.include_role: -# name: roles/sap_info - -# Get SAP Facts -- name: Run sap_facts module to gather SAP facts - community.sap_libs.sap_system_facts: - # param: "{{ sap_facts_param }}" - register: sap_facts_register - -- name: Debug result from sap_libs.sap_system_facts - ansible.builtin.debug: - msg: "{{ sap_facts_register.ansible_facts.sap }}" - verbosity: 1 - -- name: pause for 10 Seconds - ansible.builtin.wait_for: - timeout: 10 - -# Debugging stuff -- name: Display parameters for runtime - ansible.builtin.debug: - msg: - - "Starting sap_control with the following parameters: " - - "Function: {{ sap_control_function }}" - - "Standard commands:" - - " Start: {{ sap_control_start }}" - - " Stop: {{ sap_control_stop }}" - - "System commands (if applicable):" - - " StartSystem: {{ sap_control_startsystem }}" - - " StopSystem: {{ sap_control_stopsystem }}" - - " RestartSystem: {{ sap_control_restartsystem }}" - - " UpdateSystem: {{ sap_control_updatesystem }}" - - " WaitforStopped: {{ sap_control_waitforstopped }}" - - " WaitforStarted: {{ sap_control_waitforstarted }}" - - "NoWait: {{ nowait }}" - -# Start SAP Control -- name: SAP Control - ansible.builtin.include_tasks: prepare.yml - loop: "{{ vars[sap_control_function + '_list'] }}" - loop_control: - loop_var: function_list +- name: Block to execute only when commands are defined + when: + - __sap_control_fact_instances is defined + - __sap_control_fact_instances | length > 0 + - __sap_control_commands is defined + - __sap_control_commands | length > 0 + block: + - name: SAP Control - Show execution steps + ansible.builtin.debug: + msg: | + The Ansible Role `sap_control` is executed with function: '{{ sap_control_function }}' + + Following commands will now be executed in this order by listed users: + {% for command in __sap_control_commands %} + {{ command.user }}: {{ command.command }} + {% endfor %} + + + # Loop over unique SID + NR combinations, instead of full list. + # This helps with functions like restart, where command list contains duplicates. + - name: SAP Control - Ensure 'sapstartsrv' service is running + ansible.builtin.include_tasks: + file: actions/sapstartsrv.yml + loop: "{{ __sap_control_commands | map(attribute='sid_nr') | list | unique }}" + loop_control: + loop_var: instance_sid_nr_item + label: "{{ __command_sid }}_{{ __command_nr }}" + vars: + # Extraction from loop item is required, because we cannot access + # them directly after selecting unique combination. + __command_sid: "{{ instance_sid_nr_item.split('_')[0] | upper }}" + __command_nr: "{{ instance_sid_nr_item.split('_')[1] }}" + __command_user: "{{ __command_sid | lower }}adm" + + + - name: SAP Control - Execute sapcontrol commands steps + ansible.builtin.include_tasks: + file: actions/sapcontrol.yml + loop: "{{ __sap_control_commands }}" + loop_control: + loop_var: command_item + label: "{{ command_item['sid'] }}_{{ command_item['nr'] }}_{{ command_item['name'] }}" diff --git a/roles/sap_control/tasks/prepare.yml b/roles/sap_control/tasks/prepare.yml index 14262b0..6e8da5a 100644 --- a/roles/sap_control/tasks/prepare.yml +++ b/roles/sap_control/tasks/prepare.yml @@ -1,54 +1,28 @@ +# SPDX-License-Identifier: Apache-2.0 --- -- name: Prepare - Set function control facts - ansible.builtin.set_fact: - sap_type: "{{ function_list.sap_control_function_current.split('_')[0] | lower }}" - funct_type: "{{ function_list.sap_control_function_current.split('_')[1] | lower }}" - sorted_sap_facts: [] - -- name: Prepare - Set header - ansible.builtin.set_fact: - sap_control_name_header: "{{ sap_type | upper }} {{ funct_type | capitalize }}" +- name: SAP Control - Fail if 'sap_control_sid' is not present on host + ansible.builtin.fail: + msg: | + FAIL: The variable 'sap_control_sid' is defined with '{{ sap_control_sid }}', + but this SID is not present on this host. -- name: Prepare - Sort sap_facts_register by sap_control_instance_type_sortorder - ansible.builtin.set_fact: - sorted_sap_facts: "{{ (sorted_sap_facts | default([]) | unique) + (__type_list | difference(sorted_sap_facts | default([]))) }}" - loop: "{{ sap_control_instance_type_sortorder | reverse if funct_type == 'stop' else sap_control_instance_type_sortorder }}" - vars: - __type_list: "{{ sap_facts_register.ansible_facts.sap | selectattr('TYPE', 'equalto', item) | list }}" + Detected SIDs: {{ __sap_control_fact_instances | map(attribute='SID') | list | unique | join(', ') }} when: - - __type_list | length > 0 + - __sap_control_function['target'] == 'sid' + - __sap_control_fact_instances | selectattr('SID', 'eq', sap_control_sid | upper)| list | length == 0 -- name: Prepare - Filter sorted_sap_facts by sap_sid +- name: SAP Control - Prepare instance list filtered by SID ansible.builtin.set_fact: - sorted_sap_facts: "{{ sorted_sap_facts | default([]) - | selectattr('SID', 'eq', sap_sid | upper) - | list }}" - when: - - not 'all' in sap_control_function - -- name: Prepare - SAP Control - vars: - sap_control_execute_sid: "{{ item.SID }}" - sap_control_execute_type: "{{ item.TYPE }}" - sap_control_execute_instance_nr: "{{ item.NR }}" - sap_control_execute_instance_type: "{{ item.InstanceType }}" - ansible.builtin.include_tasks: "sapcontrol.yml" - loop: "{{ sorted_sap_facts }}" - when: - - item.InstanceType | lower == sap_type | lower - - not funct_type is match('.*system') + __sap_control_fact_instances: >- + {{ __sap_control_fact_instances + | selectattr('SID', 'eq', sap_control_sid | upper) + | list }} + when: __sap_control_function['target'] == 'sid' -- name: Prepare - SAP Control for system functions - vars: - sap_control_execute_sid: "{{ item.SID }}" - sap_control_execute_type: "{{ item.Type }}" - sap_control_execute_instance_nr: "{{ item.NR }}" - sap_control_execute_instance_type: "{{ item.InstanceType }}" - ansible.builtin.include_tasks: "sapcontrol.yml" - loop: "{{ sorted_sap_facts }}" - when: - - item.InstanceType | lower == sap_type | lower - - funct_type is match('.*system') - - item.TYPE | lower == 'ascs' - or item.TYPE | lower == 'scs' +- name: SAP Control - Prepare commands to be executed + ansible.builtin.include_tasks: + file: prepare_commands.yml + loop: "{{ __sap_control_function['steps'] }}" + loop_control: + loop_var: command_step_item diff --git a/roles/sap_control/tasks/prepare_commands.yml b/roles/sap_control/tasks/prepare_commands.yml new file mode 100644 index 0000000..142851e --- /dev/null +++ b/roles/sap_control/tasks/prepare_commands.yml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +# This step is important for functions like 'restart' where start order would be wrong. +- name: SAP Control - Set fact with empty sort list for step {{ command_step_item['command'] }} + ansible.builtin.set_fact: + __sap_control_fact_instances_sorted: [] + +- name: SAP Control - Prepare sorted lists of SAP instances for command {{ command_step_item['command'] }} + ansible.builtin.set_fact: + __sap_control_fact_instances_sorted: >- + {{ (__sap_control_fact_instances_sorted | unique) + + (__sort_fragment | difference(__sap_control_fact_instances_sorted | d([]))) }} + loop: >- + {{ __sap_control_execution_order['start'] + if command_step_item['command'] in ['start', 'startwait', 'startsystem', 'updatesystem', 'waitforstarted'] + else __sap_control_execution_order['stop'] + }} + loop_control: + loop_var: sort_item + vars: + __sort_fragment: "{{ __sap_control_fact_instances | selectattr('TYPE', 'equalto', sort_item) | list }}" + + +- name: SAP Control - Set fact with type specific instances for step {{ command_step_item['command'] }} + ansible.builtin.set_fact: + __sap_control_fact_instances_type_specific: >- + {{ __sap_control_fact_instances_sorted | selectattr('InstanceType', 'equalto', command_step_item['type'] | upper) | list }} + + +# Resulting dictionary is complex variable that contains extra details. +# These parameters are used in consequent tasks to avoid calling original lists again. +- name: Block for non-System functions + when: + - not (command_step_item['command'] is match('.*system$') + and (sap_control_function.split('_')[0] | lower) is match('.*system$')) + block: + - name: SAP Control - Generate commands without system functions + ansible.builtin.set_fact: + __sap_control_commands: >- + {{ __sap_control_commands | d([]) + __command_dict }} + loop: "{{ __sap_control_fact_instances_type_specific }}" + loop_control: + loop_var: instance_item + vars: + __command_dict: + - name: "{{ command_step_item['command'] }}" + user: "{{ instance_item['SID'] | lower }}adm" + command: "{{ __command_fragment }}" + sid: "{{ instance_item['SID'] }}" + nr: "{{ instance_item['NR'] }}" + sid_nr: "{{ instance_item['SID'] }}_{{ instance_item['NR'] }}" + async: "{{ command_step_item['async'] | d({}) }}" + + __command_fragment: >- + source ~/.profile && + sapcontrol + -nr {{ instance_item['NR'] }} + -function {{ __sap_control_command_definitions[__command] }} + + __command: >- + {{ 'start' + if sap_control_command_nowait | d(false) and command_step_item['command'] == 'startwait' + else ('stop' + if sap_control_command_nowait | d(false) and command_step_item['command'] == 'stopwait' + else command_step_item['command']) }} + + +- name: Block for System functions + when: + - command_step_item['command'] is match('.*system$') + or (sap_control_function.split('_')[0] | lower) is match('.*system$') + block: + + - name: SAP Control - Generate commands with system functions + ansible.builtin.set_fact: + __sap_control_commands: >- + {{ __sap_control_commands | d([]) + __command_dict }} + loop: "{{ __sap_control_fact_instances_type_specific | map(attribute='SID') | list | unique }}" + loop_control: + loop_var: system_sid_item + vars: + __instance_sid_dict: >- + {{ __sap_control_fact_instances_type_specific | selectattr('SID', 'equalto', system_sid_item) | list | first }} + + __command_dict: + - name: "{{ command_step_item['command'] }}" + user: "{{ system_sid_item | lower }}adm" + command: "{{ __command_fragment }}" + sid: "{{ __instance_sid_dict['SID'] }}" + nr: "{{ __instance_sid_dict['NR'] }}" + sid_nr: "{{ __instance_sid_dict['SID'] }}_{{ __instance_sid_dict['NR'] }}" + async: "{{ command_step_item['async'] | d({}) }}" + + __command_fragment: >- + source ~/.profile && + sapcontrol + -nr {{ __instance_sid_dict['NR'] }} + -function {{ __sap_control_command_definitions[command_step_item['command']] }} diff --git a/roles/sap_control/tasks/sapcontrol.yml b/roles/sap_control/tasks/sapcontrol.yml deleted file mode 100644 index aa181a2..0000000 --- a/roles/sap_control/tasks/sapcontrol.yml +++ /dev/null @@ -1,38 +0,0 @@ -# sap_control_execute_sid: "{{ item.SID }}" -# sap_control_execute_type: "{{ item.Type }}" -# sap_control_execute_instance_nr: "{{ item.InstanceNumber }}" -# sap_control_execute_instance_type: "{{ item.InstanceType }}" -- name: set up facts - ansible.builtin.set_fact: - passed_sap_nr: "{{ sap_control_execute_instance_nr }}" - passed_sap_sid: "{{ sap_control_execute_sid }}" - -# Check sapstartsrv -- name: SAP {{ sap_control_name_header }} - sapstartsrv - ansible.builtin.include_tasks: functions/sapstartsrv.yml - -# Execute sapcontrol -- name: SAP {{ sap_control_name_header }} - Executing sapcontrol -nr {{ passed_sap_nr }} -function {{ vars['sap_control_' + funct_type] }} - ansible.builtin.shell: | - source ~/.profile && sapcontrol -nr {{ passed_sap_nr }} -function {{ vars['sap_control_' + funct_type] }} - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - register: sapcontrol_status - failed_when: "'FAIL' in sapcontrol_status.stdout" - -# Include sapcontrol async tasks -- name: SAP {{ sap_control_name_header }} - Include async tasks - vars: - async_function_dict: "{{ vars['sap_control_' + funct_type + '_waitforasync'] }}" - ansible.builtin.include_tasks: sapcontrol_async.yml - when: - - funct_type is match('.*system') - -# Cleanipc -- name: SAP {{ sap_control_name_header }} - Cleanipc - ansible.builtin.include_tasks: functions/cleanipc.yml - when: - - "'nw' in sap_type" - - "'stop' in funct_type" diff --git a/roles/sap_control/tasks/sapcontrol_async.yml b/roles/sap_control/tasks/sapcontrol_async.yml deleted file mode 100644 index a704c18..0000000 --- a/roles/sap_control/tasks/sapcontrol_async.yml +++ /dev/null @@ -1,55 +0,0 @@ ---- -- name: Wait for status to change if using restart or update - when: funct_type is match('restart|update') - block: - - name: SAP {{ sap_control_name_header }} - Getting current system state - ansible.builtin.shell: | - source ~/.profile && sapcontrol -nr {{ passed_sap_nr }} -function {{ async_function_dict.test_function }} - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - register: initial_test_function_result - - - name: SAP {{ sap_control_name_header }} - Waiting for state to change before polling - ansible.builtin.shell: | - source ~/.profile && sapcontrol -nr {{ passed_sap_nr }} -function {{ async_function_dict.test_function }} - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - register: wait_for_change_result - retries: "{{ async_function_dict.retries | default(0) | int }}" - delay: "{{ async_function_dict.delay | default(0) | int }}" - until: > - (wait_for_change_result.stdout | regex_findall('GREEN|YELLOW|GRAY|RED', multiline=True) | sort | join(',')) - != (initial_test_function_result.stdout | regex_findall('GREEN|YELLOW|GRAY|RED', multiline=True) | sort | join(',')) - -- name: Pause for 20 Seconds to ensure the async function is started - ansible.builtin.wait_for: - timeout: 20 - -- name: SAP {{ sap_control_name_header }} - Checking if Async action is over by executing sapcontrol -nr {{ passed_sap_nr }} -function {{ async_function_dict.test_function }} - ansible.builtin.shell: | - source ~/.profile && sapcontrol -nr {{ passed_sap_nr }} -function {{ async_function_dict.test_function }} - args: - executable: /bin/bash - become: true - become_user: "{{ passed_sap_sid | lower }}adm" - register: test_function_result - retries: "{{ async_function_dict.retries | default(0) | int }}" - delay: "{{ async_function_dict.delay | default(0) | int }}" - until: > - (async_function_dict.until_false is not defined - or async_function_dict.until_false is defined - and test_function_result.stdout | regex_search(async_function_dict.until_false, multiline=True) is none) and - (async_function_dict.until_true is not defined or - async_function_dict.until_true is defined - and test_function_result.stdout | regex_search(async_function_dict.until_true, multiline=True) is not none) - -- name: Debug stdout - ansible.builtin.debug: - msg: | - Async function {{ async_function_dict.test_function }} for SAP SID {{ passed_sap_sid }} - is done with result: - {{ test_function_result.stdout }} diff --git a/roles/sap_control/vars/main.yml b/roles/sap_control/vars/main.yml new file mode 100644 index 0000000..7adf2aa --- /dev/null +++ b/roles/sap_control/vars/main.yml @@ -0,0 +1,311 @@ +# SPDX-License-Identifier: Apache-2.0 +--- + +# Defines the base sapcontrol commands and their default parameters. +# These are the low-level building blocks for the functions defined in '__sap_control_function_definitions'. +# The format generally follows: sapcontrol -function +__sap_control_command_definitions: + start: "Start" + stop: "Stop" + startwait: "StartWait 180 2" # -function StartWait + stopwait: "StopWait 180 2" # -function StopWait + startsystem: "StartSystem ALL 180" # -function StartSystem ALL + stopsystem: "StopSystem ALL 180 480" # -function StopSystem ALL + restartsystem: "RestartSystem ALL 180 480" # -function RestartSystem ALL + updatesystem: "UpdateSystem 180 480 0" # -function UpdateSystem + waitforstopped: "WaitforStopped 180 2" # -function WaitforStopped + waitforstarted: "WaitforStarted 180 2" # -function WaitforStarted + + +# This dictionary orchestrates the high-level functions exposed to the user via the 'sap_control_function' variable. +# Each key is a user-callable function name. +# Each function definition contains: +# - description: A human-readable explanation of what the function does. +# - target: 'all' (for all instances of a type) or 'sid' (for a specific instance). +# - steps: A list of commands from '__sap_control_command_definitions' to execute in order. +# - async (optional): Defines parameters for asynchronous tasks, which poll for a specific state. +# NOTE: Adding new function might require updating some tasks that validate values. +# Example: __sap_control_fact_instances_sorted checks for ['start', 'startwait', 'startsystem', 'updatesystem', 'waitforstarted'] +__sap_control_function_definitions: + # SAP HANA functions - Single actions + start_all_hana: + description: "Starts all SAP HANA instances on the host." + target: all + steps: + - command: startwait + type: hana + + stop_all_hana: + description: "Stops all SAP HANA instances on the host." + target: all + steps: + - command: stopwait + type: hana + + start_sap_hana: + description: "Starts SID specific SAP HANA instance on the host." + target: sid + steps: + - command: startwait + type: hana + + stop_sap_hana: + description: "Stops SID specific SAP HANA instance on the host." + target: sid + steps: + - command: stopwait + type: hana + + # SAP HANA functions - Composite actions + restart_all_hana: + description: "Restarts all SAP HANA instances on the host." + target: all + steps: + - command: stopwait + type: hana + - command: startwait + type: hana + + restart_sap_hana: + description: "Restarts SID specific SAP HANA instance on the host." + target: sid + steps: + - command: stopwait + type: hana + - command: startwait + type: hana + + # SAP Netweaver functions - Single actions + start_all_nw: + description: "Starts all SAP Netweaver instances on the host." + target: all + steps: + - command: startwait + type: nw + + stop_all_nw: + description: "Stops all SAP Netweaver instances on the host." + target: all + steps: + - command: stopwait + type: nw + + start_sap_nw: + description: "Starts SID specific SAP Netweaver instance on the host." + target: sid + steps: + - command: startwait + type: nw + + stop_sap_nw: + description: "Stops SID specific SAP Netweaver instance on the host." + target: sid + steps: + - command: stopwait + type: nw + + # SAP Netweaver functions - Composite actions + restart_all_nw: + description: "Restarts all SAP Netweaver instances on the host." + target: all + steps: + - command: stopwait + type: nw + - command: startwait + type: nw + + restart_sap_nw: + description: "Restarts SID specific SAP Netweaver instance on the host." + target: sid + steps: + - command: stopwait + type: nw + - command: startwait + type: nw + + # SAP System functions - Composite actions + start_all_sap: + description: "Starts all SAP System instances on the host." + target: all + steps: + - command: startwait + type: hana + - command: startwait + type: nw + + + stop_all_sap: + description: "Stops all SAP System instances on the host." + target: all + steps: + - command: stopwait + type: nw + - command: stopwait + type: hana + + restart_all_sap: + description: "Restarts all SAP System instances on the host." + target: all + steps: + - command: stopwait + type: nw + - command: stopwait + type: hana + - command: startwait + type: hana + - command: startwait + type: nw + + # System-level functions - Single action + startsystem_all_nw: + description: "Starts all NetWeaver systems on the host using StartSystem." + target: all + steps: + - command: startsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GREEN\s*$' + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' + + stopsystem_all_nw: + description: "Stops all NetWeaver systems on the host using StopSystem." + target: all + steps: + - command: stopsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GRAY\s*$' + until_false: 'GREEN\s*$|RED\s*$|YELLOW\s*$' + + startsystem_sap_nw: + description: "Starts SID specific NetWeaver systems on the host using StartSystem." + target: sid + steps: + - command: startsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GREEN\s*$' + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' + + stopsystem_sap_nw: + description: "Stops SID specific NetWeaver systems on the host using StopSystem." + target: sid + steps: + - command: stopsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GRAY\s*$' + until_false: 'GREEN\s*$|RED\s*$|YELLOW\s*$' + + restartsystem_all_nw: + description: "Restarts all NetWeaver systems on the host using StopSystem." + target: all + steps: + - command: restartsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GREEN\s*$' + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' + + restartsystem_sap_nw: + description: "Restarts SID specific NetWeaver systems on the host using StopSystem." + target: sid + steps: + - command: restartsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GREEN\s*$' + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' + + # System-level functions - Composite actions + updatesystem_all_nw: + description: "Starts and update all NetWeaver systems on the host using StopSystem." + target: all + steps: + - command: startsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GREEN\s*$' + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' + - command: restartsystem + type: nw + async: + test_function: "GetSystemUpdateList" + retries: 60 + delay: 10 + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$|GREEN\s*$' + + updatesystem_sap_nw: + description: "Starts and update SID specific NetWeaver systems on the host using StopSystem." + target: sid + steps: + - command: startsystem + type: nw + async: + test_function: "GetSystemInstanceList" + retries: 60 + delay: 10 + until_true: 'GREEN\s*$' + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$' + - command: restartsystem + type: nw + async: + test_function: "GetSystemUpdateList" + retries: 60 + delay: 10 + until_false: 'GRAY\s*$|RED\s*$|YELLOW\s*$|GREEN\s*$' + + +# Defines the execution order for starting and stopping different SAP instance types. +# This ensures dependencies are handled correctly (e.g., the database starts before application servers). +__sap_control_execution_order: + start: + - "HDB" + - "ERS" + - "ASCS" + - "SCS" + - "PAS" + - "Java" + - "WebDisp" + stop: + - "WebDisp" + - "Java" + - "PAS" + - "SCS" + - "ASCS" + - "ERS" + - "HDB" + + +# Maps the first letter of an SAP instance directory name to a standardized, human-readable type. +# This is used to identify the type of an instance (e.g., a directory named 'DVEBMGS00' starts with 'D', mapping to 'PAS'). +__sap_control_type_dict: + # Key is the first letter of the instance directory name, Value is the desired type name + "A": "ASCS" # ASCS## -> ASCS (ABAP Central Services) + "D": "PAS" # D## (DVEBMGS##) -> PAS (Primary Application Server) or Dialog Instance + "W": "WebDisp" # W## -> WebDispatcher + "J": "Java" # J## (J##, JEM##) -> Java Application Server + "S": "SCS" # SCS## -> SCS (Java Central Services) + "E": "ERS" # ERS## -> ERS (Enque Replication Server) + "H": "HDB" # HDB## (for HANA DB) - Added for completeness