diff --git a/plugins/modules/sonic_image_management.py b/plugins/modules/sonic_image_management.py new file mode 100644 index 000000000..131ba869b --- /dev/null +++ b/plugins/modules/sonic_image_management.py @@ -0,0 +1,434 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for sonic_image_management +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: sonic_image_management +version_added: '2.4.0' +short_description: Manage installation of Enterprise SONiC image, software patch and firmware updater +description: + - Manage installation of Enterprise SONiC image, software patch and firmware updater. +author: 'Arun Saravanan Balachandran (@ArunSaravananBalachandran)' + +options: + image: + description: + - Manage installation of Enterprise SONiC image. + type: dict + suboptions: + command: + description: + - Specifies the image manangement operation to be performed. + - C(install) - Install image specified by I(path). + - C(cancel) - Cancel image installation. + - C(remove) - Remove image specified by I(name). + - C(set-default) - Set the image specified by I(name) as default boot image. + - C(get-list) - Retrieve list of installed images. + - C(get-status) - Retrieve image installation status. + type: str + choices: + - install + - cancel + - remove + - set-default + - get-list + - get-status + required: true + path: + description: + - When I(command=install), specifies the path of the image to be installed. + - Path can be a file in the device (file://filepath) or URL (http:// or https://). + type: str + name: + description: + - When I(command=remove) or I(command=set-default), specifies the name of the image. + - When I(command=remove), name can be specified as C(all) to remove all images which are not current or next. + type: str + patch: + description: + - Manage installation of software patch. + type: dict + suboptions: + command: + description: + - Specifies the patch manangement operation to be performed. + - C(install) - Install patch specified by I(path). + - C(rollback) - Remove an installed patch specified by I(name). + - C(get-history) - Retrieve history of patches applied/rolled back. + - C(get-list) - Retrieve list of installed patches. + - C(get-status) - Retrieve patch installation/removal status. + type: str + choices: + - install + - rollback + - get-history + - get-list + - get-status + required: true + path: + description: + - When I(command=install), specifies the path of the patch to be installed. + - Path can be a file in the device (file://filepath) or URL (http:// or https://). + type: str + name: + description: + - When I(command=rollback), specifies the name of the patch. + type: str + firmware: + description: + - Manage installation of Firmware updater + type: dict + suboptions: + command: + description: + - Specifies the firmware updater manangement operation to be performed. + - C(install) - Stage firmware updater specified by I(path). + - C(cancel) - Cancel a pending firmware updater. + - C(get-list) - Retrieve details of pending firmware updater and result of installed firmware updater. + - C(get-status) - Retrieve firmware updater staging status. + type: str + choices: + - install + - cancel + - get-list + - get-status + required: true + path: + description: + - When I(command=install), specifies the path of the firmware updater to be staged. + - Path can be a file in the device (file://filepath) or URL (http:// or https://). + type: str +""" + +EXAMPLES = """ + +- name: Install Enterprise SONiC image + dellemc.enterprise_sonic.sonic_image_management: + image: + command: install + path: 'file://home/admin/sonic.bin' + +- name: Get image installation status + dellemc.enterprise_sonic.sonic_image_management: + image: + command: get-status + +- name: Get list of installed images + dellemc.enterprise_sonic.sonic_image_management: + image: + command: get-list + +- name: Stage a firmware updater + dellemc.enterprise_sonic.sonic_image_management: + firmware: + command: install + path: 'file://home/admin/onie-update-full.bin' + +""" + +RETURN = """ +status: + description: Status of the operation performed. + returned: when I(command) is not C(get-status), C(get-list) and C(get-history) + type: str + sample: SUCCESS +info: + description: Details returned by the specified get operation. + returned: when I(command=get-status) or I(command=get-list) or I(command=get-history) + type: dict + sample: > + { + "file-download-speed" : "106200", + "file-progress" : 100, + "file-size" : "1304997870", + "file-transfer-bytes" : "1304997870", + "install-end-time" : "1695714740", + "install-start-time" : "1695714698", + "install-status" : "INSTALL_STATE_SUCCESS", + "install-status-detail" : "Image install success", + "operation-status" : "GLOBAL_STATE_SUCCESS", + "transfer-end-time" : "1695714669", + "transfer-start-time" : "1695714657", + "transfer-status" : "TRANSFER_STATE_SUCCESS", + "transfer-status-detail" : "DOWNLOADING IMAGE" + } +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) + + +def validate_and_retrieve_params(module, warnings): + """Validates the module parameters""" + params = {} + for category in ('image', 'patch', 'firmware'): + if module.params.get(category) and module.params[category].get('command'): + if params.get('category') is None: + params['category'] = category + params.update(module.params[category]) + else: + module.fail_json(msg="Only one image management operation can be performed at a time") + + if module.check_mode and not params['command'].startswith('get-'): + module.fail_json(msg='Only get commands are supported while using check mode, but {0} was provided'.format(params['command'])) + + if params['command'] == 'install': + if not params.get('path'): + module.fail_json(msg="{0} -> path is required when {0} -> command = install".format(params['category'])) + if params.get('name'): + warnings.append("{0} -> name is ignored when {0} -> command = install".format(params['category'])) + elif params['command'] in ('remove', 'set-default', 'rollback'): + if not params.get('name'): + module.fail_json(msg="{0} -> name is required when {0} -> command = {1}".format(params['category'], params['command'])) + if params.get('path'): + warnings.append("{0} -> path is ignored when {0} -> command = {1}".format(params['category'], params['command'])) + + return params + + +def execute_command(module, params, result): + """Executes the specified command and updates the result""" + command_map = { + 'image': { + 'install': { + 'path': 'operations/openconfig-image-management:image-install', + 'status': 'Check image -> command = get-status for image install progress' + }, + 'cancel': { + 'path': 'operations/openconfig-image-management:image-install-cancel' + }, + 'remove': { + 'path': 'operations/openconfig-image-management:image-remove' + }, + 'set-default': { + 'path': 'operations/openconfig-image-management:image-default' + }, + 'get-status': { + 'path': 'data/openconfig-image-management:image-management/install/state', + 'response_key': 'openconfig-image-management:state' + }, + 'get-list': { + 'path': 'data/openconfig-image-management:image-management', + 'response_key': 'openconfig-image-management:image-management' + } + }, + 'patch': { + 'install': { + 'path': 'operations/openconfig-image-management:do-patch-install', + 'status': 'Check patch -> command = get-status for patch install progress' + }, + 'rollback': { + 'path': 'operations/openconfig-image-management:do-patch-rollback', + 'status': 'Check patch -> command = get-status for patch rollback progress' + }, + 'get-history': { + 'path': 'data/openconfig-image-management:patch-management/patch-history', + 'response_key': 'openconfig-image-management:patch-history' + }, + 'get-status': { + 'path': 'data/openconfig-image-management:patch-management/patch-install', + 'response_key': 'openconfig-image-management:patch-install' + }, + 'get-list': { + 'path': 'data/openconfig-image-management:patch-management/patch-list', + 'response_key': 'openconfig-image-management:patch-list' + } + }, + 'firmware': { + 'install': { + 'path': 'operations/openconfig-image-management:do-fwpkg-install', + 'status': 'Check firmware -> command = get-status for firmware package staging progress' + }, + 'cancel': { + 'path': 'operations/openconfig-image-management:do-fwpkg-install-cancel' + }, + 'get-list': { + 'path': 'data/openconfig-image-management:fwpkg-management', + 'response_key': 'openconfig-image-management:fwpkg-management' + }, + 'get-status': { + 'path': 'data/openconfig-image-management:fwpkg-management/fwpkg-install', + 'response_key': 'openconfig-image-management:fwpkg-install' + } + } + } + + path = command_map[params['category']][params['command']]['path'] + if params['command'].startswith('get-'): + method = 'GET' + request = [{'path': path, 'method': method}] + + try: + response = edit_config(module, to_request(module, request)) + except ConnectionError as exc: + module.fail_json(msg=str(exc), code=exc.code) + + info = {} + response = response[0][1].get(command_map[params['category']][params['command']]['response_key']) + if response: + if params['category'] == 'image': + if params['command'] == 'get-list': + if response.get('global') and response['global'].get('state'): + if response['global']['state'].get('current'): + info['current'] = response['global']['state']['current'] + if response['global']['state'].get('next-boot'): + info['next'] = response['global']['state']['next-boot'] + if response.get('images') and response['images'].get('image'): + info['available'] = [] + for element in response['images']['image']: + if element.get('image-name'): + info['available'].append(element['image-name']) + + elif params['command'] == 'get-status': + keys = list(response.keys()) + info.update(response) + install_status = info.get('install-status', 'IDLE') + transfer_status = info.get('transfer-status', 'IDLE') + for key in keys: + if ((key.startswith(('file', 'transfer')) and 'IDLE' in transfer_status) + or (key.startswith('install') and 'IDLE' in install_status)): + del info[key] + + elif params['category'] == 'patch': + if params['command'] in ('get-history', 'get-list'): + info_key = params['command'].split('-')[1] + if response.get('patch'): + patches = sorted(response['patch'], key=lambda item: (item['patch-time']), reverse=True) + info[info_key] = [] + for patch in patches: + if patch.get('state'): + info[info_key].append(patch['state']) + + elif params['command'] == 'get-status': + install_state = response.get('install-state', {}) + download_state = response.get('download-state', {}) + if install_state.get('trigger') == 'install' and 'IDLE' not in download_state.get('transfer-status', 'IDLE'): + info.update(download_state) + + for oper_type in ('install', 'rollback', 'recovery'): + if 'IDLE' not in install_state.get('{0}-status'.format(oper_type), 'IDLE'): + for key in install_state.keys(): + if key.startswith(oper_type): + info[key] = install_state[key] + + elif params['category'] == 'firmware': + if params['command'] == 'get-list': + for info_key in ('pending', 'result'): + key = 'fwpkg-{0}'.format(info_key) + if response.get(key) and response[key].get('fwpkg'): + info[info_key] = [] + for entry in response[key]['fwpkg']: + info[info_key].append(entry['state']) + + elif params['command'] == 'get-status': + if response.get('download-state') and 'IDLE' not in response['download-state'].get('transfer-status', 'IDLE'): + info.update(response['download-state']) + if response.get('stage-state') and 'IDLE' not in response['stage-state'].get('stage-status', 'IDLE'): + info.update(response['stage-state']) + + result['info'] = info + + else: + method = 'POST' + payload = {'openconfig-image-management:input': {}} + if params['category'] == 'image': + if params['command'] == 'install': + payload['openconfig-image-management:input'] = {'image-name': params['path']} + elif (params['command'] == 'remove' and params['name'] != 'all') or params['command'] == 'set-default': + payload['openconfig-image-management:input'] = {'image-name': params['name']} + elif params['category'] == 'patch': + if params['command'] == 'install': + payload['openconfig-image-management:input'] = {'patch-name': params['path'], 'skip-image-check': ''} + elif params['command'] == 'rollback': + payload['openconfig-image-management:input'] = {'patch-name': params['name']} + elif params['category'] == 'firmware': + if params['command'] == 'install': + payload['openconfig-image-management:input'] = {'fwpkg-name': params['path']} + + request = [{'path': path, 'method': method, 'data': payload}] + try: + response = edit_config(module, to_request(module, request)) + except ConnectionError as exc: + module.fail_json(msg=str(exc), code=exc.code) + + status = '' + response = response[0][1].get('openconfig-image-management:output') + if response: + if response['status'] != 0: + status = response['status-detail'] + else: + status = command_map[params['category']][params['command']].get('status', response['status-detail']) + + result['status'] = status + + +def main(): + """ + Main entry point for module execution + """ + argument_spec = { + 'image': { + 'type': 'dict', + 'options': { + 'command': { + 'type': 'str', + 'required': True, + 'choices': ['install', 'cancel', 'remove', 'set-default', 'get-list', 'get-status'] + }, + 'name': {'type': 'str'}, + 'path': {'type': 'str'} + } + }, + 'patch': { + 'type': 'dict', + 'options': { + 'command': { + 'type': 'str', + 'required': True, + 'choices': ['install', 'rollback', 'get-history', 'get-list', 'get-status'] + }, + 'name': {'type': 'str'}, + 'path': {'type': 'str'} + } + }, + 'firmware': { + 'type': 'dict', + 'options': { + 'command': { + 'type': 'str', + 'required': True, + 'choices': ['install', 'cancel', 'get-list', 'get-status'] + }, + 'path': {'type': 'str'} + } + } + } + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + warnings = [] + result = {'changed': False, 'warnings': warnings} + + params = validate_and_retrieve_params(module, warnings) + execute_command(module, params, result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/regression/roles/sonic_image_management/defaults/main.yml b/tests/regression/roles/sonic_image_management/defaults/main.yml new file mode 100644 index 000000000..9862ff62e --- /dev/null +++ b/tests/regression/roles/sonic_image_management/defaults/main.yml @@ -0,0 +1,3 @@ +--- +ansible_connection: httpapi +module_name: image_management diff --git a/tests/regression/roles/sonic_image_management/meta/main.yml b/tests/regression/roles/sonic_image_management/meta/main.yml new file mode 100644 index 000000000..d0ceaf6f5 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/meta/main.yml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_image_management/tasks/firmware_cancel.yml b/tests/regression/roles/sonic_image_management/tasks/firmware_cancel.yml new file mode 100644 index 000000000..36a08b853 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/firmware_cancel.yml @@ -0,0 +1,22 @@ +--- +- name: Test case - firmware cancel + dellemc.enterprise_sonic.sonic_image_management: + firmware: + command: 'cancel' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.status is defined + - result.status == 'SUCCESS' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'firmware_cancel' + test_case_input: + firmware: + command: 'cancel' diff --git a/tests/regression/roles/sonic_image_management/tasks/firmware_get_list.yml b/tests/regression/roles/sonic_image_management/tasks/firmware_get_list.yml new file mode 100644 index 000000000..87578ffff --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/firmware_get_list.yml @@ -0,0 +1,21 @@ +--- +- name: Test case - firmware get-result + dellemc.enterprise_sonic.sonic_image_management: + firmware: + command: 'get-list' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'firmware_get_list' + test_case_input: + firmware: + command: 'get-list' diff --git a/tests/regression/roles/sonic_image_management/tasks/firmware_get_status.yml b/tests/regression/roles/sonic_image_management/tasks/firmware_get_status.yml new file mode 100644 index 000000000..f28832023 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/firmware_get_status.yml @@ -0,0 +1,21 @@ +--- +- name: Test case - firmware get-status + dellemc.enterprise_sonic.sonic_image_management: + firmware: + command: 'get-status' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'firmware_get_status' + test_case_input: + firmware: + command: 'get-status' diff --git a/tests/regression/roles/sonic_image_management/tasks/firmware_install.yml b/tests/regression/roles/sonic_image_management/tasks/firmware_install.yml new file mode 100644 index 000000000..4d691615c --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/firmware_install.yml @@ -0,0 +1,30 @@ +--- +- name: Test case - firmware install + dellemc.enterprise_sonic.sonic_image_management: + firmware: + command: 'install' + path: 'file://tmp/test.bin' + register: result + ignore_errors: yes + +- ansible.builtin.set_fact: + result_msg: "{{ result.msg | from_yaml }}" + when: result.msg is defined + +- ansible.builtin.assert: + that: + - result.failed == true + - result.msg is defined + - result_msg['code'] == 400 + - result_msg['ietf-restconf:errors']['error'][0]['error-type'] == 'application' + - result_msg['ietf-restconf:errors']['error'][0]['error-tag'] == 'invalid-value' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'firmware_install' + test_case_input: + firmware: + command: 'install' + path: 'file://tmp/test.bin' diff --git a/tests/regression/roles/sonic_image_management/tasks/image_cancel.yml b/tests/regression/roles/sonic_image_management/tasks/image_cancel.yml new file mode 100644 index 000000000..9c609af57 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_cancel.yml @@ -0,0 +1,22 @@ +--- +- name: Test case - image cancel + dellemc.enterprise_sonic.sonic_image_management: + image: + command: 'cancel' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.status is defined + - result.status == 'SUCCESS' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'image_cancel' + test_case_input: + image: + command: 'cancel' diff --git a/tests/regression/roles/sonic_image_management/tasks/image_get_list.yml b/tests/regression/roles/sonic_image_management/tasks/image_get_list.yml new file mode 100644 index 000000000..f922164bf --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_get_list.yml @@ -0,0 +1,24 @@ +--- +- name: Test case - image get-list + dellemc.enterprise_sonic.sonic_image_management: + image: + command: 'get-list' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + - result.info.available is defined + - result.info.current is defined and result.info.current in result.info.available + - result.info.next is defined and result.info.next in result.info.available + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'image_get_list' + test_case_input: + image: + command: 'get-list' diff --git a/tests/regression/roles/sonic_image_management/tasks/image_get_status.yml b/tests/regression/roles/sonic_image_management/tasks/image_get_status.yml new file mode 100644 index 000000000..d253e69f6 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_get_status.yml @@ -0,0 +1,21 @@ +--- +- name: Test case - image get-status + dellemc.enterprise_sonic.sonic_image_management: + image: + command: 'get-status' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'image_get_status' + test_case_input: + image: + command: 'get-status' diff --git a/tests/regression/roles/sonic_image_management/tasks/image_install.yml b/tests/regression/roles/sonic_image_management/tasks/image_install.yml new file mode 100644 index 000000000..0c51b61aa --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_install.yml @@ -0,0 +1,30 @@ +--- +- name: Test case - image install + dellemc.enterprise_sonic.sonic_image_management: + image: + command: 'install' + path: 'file://tmp/test.bin' + register: result + ignore_errors: yes + +- ansible.builtin.set_fact: + result_msg: "{{ result.msg | from_yaml }}" + when: result.msg is defined + +- ansible.builtin.assert: + that: + - result.failed == true + - result.msg is defined + - result_msg['code'] == 400 + - result_msg['ietf-restconf:errors']['error'][0]['error-type'] == 'application' + - result_msg['ietf-restconf:errors']['error'][0]['error-tag'] == 'invalid-value' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'image_install' + test_case_input: + image: + command: 'install' + path: 'file://tmp/test.bin' diff --git a/tests/regression/roles/sonic_image_management/tasks/image_management.test.facts.report.yml b/tests/regression/roles/sonic_image_management/tasks/image_management.test.facts.report.yml new file mode 100644 index 000000000..71fb56236 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_management.test.facts.report.yml @@ -0,0 +1,9 @@ +- ansible.builtin.set_fact: + ansible_facts: + test_reports: "{{ ansible_facts['test_reports'] | default({}) | combine({module_name: {test_case_name: { + 'status': 'Passed' if (assert_result.failed == false) else 'Failed', + 'module_stderr': result.module_stderr | default(result.msg | default('No Error')), + 'configs': test_case_input | default('Not defined'), + 'result_info': result.info | default('N/A'), + 'result_status': result.status | default('N/A'), + }}}, recursive=True) }}" diff --git a/tests/regression/roles/sonic_image_management/tasks/image_remove.yml b/tests/regression/roles/sonic_image_management/tasks/image_remove.yml new file mode 100644 index 000000000..58c97651e --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_remove.yml @@ -0,0 +1,30 @@ +--- +- name: Test case - image remove - current image + dellemc.enterprise_sonic.sonic_image_management: + image: + command: 'remove' + name: '{{ current_image_name }}' + register: result + ignore_errors: yes + +- ansible.builtin.set_fact: + result_msg: "{{ result.msg | from_yaml }}" + when: result.msg is defined + +- ansible.builtin.assert: + that: + - result.failed == true + - result.msg is defined + - result_msg['code'] == 400 + - result_msg['ietf-restconf:errors']['error'][0]['error-type'] == 'application' + - result_msg['ietf-restconf:errors']['error'][0]['error-tag'] == 'invalid-value' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'image_remove' + test_case_input: + image: + command: 'remove' + name: '{{ current_image_name }}' diff --git a/tests/regression/roles/sonic_image_management/tasks/image_set_default.yml b/tests/regression/roles/sonic_image_management/tasks/image_set_default.yml new file mode 100644 index 000000000..c6e3a2ace --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/image_set_default.yml @@ -0,0 +1,24 @@ +--- +- name: Test case - image set-default - current image + dellemc.enterprise_sonic.sonic_image_management: + image: + command: 'set-default' + name: '{{ current_image_name }}' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.status is defined + - result.status == 'SUCCESS' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'image_set_default' + test_case_input: + image: + command: 'set-default' + name: '{{ current_image_name }}' diff --git a/tests/regression/roles/sonic_image_management/tasks/main.yml b/tests/regression/roles/sonic_image_management/tasks/main.yml new file mode 100644 index 000000000..beb7fa3ba --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- ansible.builtin.debug: + msg: "sonic_image_management Test started ..." + +- name: Preparations for image_management + ansible.builtin.include_tasks: preparation_tests.yml + +- ansible.builtin.include_tasks: image_install.yml +- ansible.builtin.include_tasks: image_cancel.yml +- ansible.builtin.include_tasks: image_remove.yml +- ansible.builtin.include_tasks: image_set_default.yml +- ansible.builtin.include_tasks: image_get_list.yml +- ansible.builtin.include_tasks: image_get_status.yml + +- ansible.builtin.include_tasks: patch_install.yml +- ansible.builtin.include_tasks: patch_rollback.yml +- ansible.builtin.include_tasks: patch_get_history.yml +- ansible.builtin.include_tasks: patch_get_list.yml +- ansible.builtin.include_tasks: patch_get_status.yml + +- ansible.builtin.include_tasks: firmware_install.yml +- ansible.builtin.include_tasks: firmware_cancel.yml +- ansible.builtin.include_tasks: firmware_get_list.yml +- ansible.builtin.include_tasks: firmware_get_status.yml + +- name: Display all variables/facts known for a host + ansible.builtin.debug: + var: hostvars[inventory_hostname].ansible_facts.test_reports diff --git a/tests/regression/roles/sonic_image_management/tasks/patch_get_history.yml b/tests/regression/roles/sonic_image_management/tasks/patch_get_history.yml new file mode 100644 index 000000000..9b87f424a --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/patch_get_history.yml @@ -0,0 +1,21 @@ +--- +- name: Test case - patch get-history + dellemc.enterprise_sonic.sonic_image_management: + patch: + command: 'get-history' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'patch_get_history' + test_case_input: + patch: + command: 'get-history' diff --git a/tests/regression/roles/sonic_image_management/tasks/patch_get_list.yml b/tests/regression/roles/sonic_image_management/tasks/patch_get_list.yml new file mode 100644 index 000000000..0be1118a4 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/patch_get_list.yml @@ -0,0 +1,21 @@ +--- +- name: Test case - patch get-list + dellemc.enterprise_sonic.sonic_image_management: + patch: + command: 'get-list' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'patch_get_list' + test_case_input: + patch: + command: 'get-list' diff --git a/tests/regression/roles/sonic_image_management/tasks/patch_get_status.yml b/tests/regression/roles/sonic_image_management/tasks/patch_get_status.yml new file mode 100644 index 000000000..edf5311bf --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/patch_get_status.yml @@ -0,0 +1,21 @@ +--- +- name: Test case - patch get-status + dellemc.enterprise_sonic.sonic_image_management: + patch: + command: 'get-status' + register: result + ignore_errors: yes + +- ansible.builtin.assert: + that: + - result.failed == false + - result.info is defined + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'patch_get_status' + test_case_input: + patch: + command: 'get-status' diff --git a/tests/regression/roles/sonic_image_management/tasks/patch_install.yml b/tests/regression/roles/sonic_image_management/tasks/patch_install.yml new file mode 100644 index 000000000..3cf7ae02e --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/patch_install.yml @@ -0,0 +1,30 @@ +--- +- name: Test case - patch install + dellemc.enterprise_sonic.sonic_image_management: + patch: + command: 'install' + path: 'file://tmp/test.patch' + register: result + ignore_errors: yes + +- ansible.builtin.set_fact: + result_msg: "{{ result.msg | from_yaml }}" + when: result.msg is defined + +- ansible.builtin.assert: + that: + - result.failed == true + - result.msg is defined + - result_msg['code'] == 400 + - result_msg['ietf-restconf:errors']['error'][0]['error-type'] == 'application' + - result_msg['ietf-restconf:errors']['error'][0]['error-tag'] == 'invalid-value' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'patch_install' + test_case_input: + patch: + command: 'install' + path: 'file://tmp/test.patch' diff --git a/tests/regression/roles/sonic_image_management/tasks/patch_rollback.yml b/tests/regression/roles/sonic_image_management/tasks/patch_rollback.yml new file mode 100644 index 000000000..6980f5f69 --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/patch_rollback.yml @@ -0,0 +1,30 @@ +--- +- name: Test case - patch rollback + dellemc.enterprise_sonic.sonic_image_management: + patch: + command: 'rollback' + name: 'test.patch' + register: result + ignore_errors: yes + +- ansible.builtin.set_fact: + result_msg: "{{ result.msg | from_yaml }}" + when: result.msg is defined + +- ansible.builtin.assert: + that: + - result.failed == true + - result.msg is defined + - result_msg['code'] == 400 + - result_msg['ietf-restconf:errors']['error'][0]['error-type'] == 'application' + - result_msg['ietf-restconf:errors']['error'][0]['error-tag'] == 'invalid-value' + register: assert_result + ignore_errors: yes + +- ansible.builtin.include_tasks: image_management.test.facts.report.yml + vars: + test_case_name: 'patch_rollback' + test_case_input: + patch: + command: 'rollback' + path: 'test.patch' diff --git a/tests/regression/roles/sonic_image_management/tasks/preparation_tests.yml b/tests/regression/roles/sonic_image_management/tasks/preparation_tests.yml new file mode 100644 index 000000000..1af488cae --- /dev/null +++ b/tests/regression/roles/sonic_image_management/tasks/preparation_tests.yml @@ -0,0 +1,9 @@ +--- +- name: Get current image name + dellemc.enterprise_sonic.sonic_image_management: + image: + command: get-list + register: image_list + +- ansible.builtin.set_fact: + current_image_name: '{{ image_list.info.current }}' diff --git a/tests/regression/roles/test_reports/templates/regression_html_report.j2 b/tests/regression/roles/test_reports/templates/regression_html_report.j2 index 6937eb8a9..f028a6d0f 100644 --- a/tests/regression/roles/test_reports/templates/regression_html_report.j2 +++ b/tests/regression/roles/test_reports/templates/regression_html_report.j2 @@ -242,9 +242,14 @@ color: red;
Input: {{ test_data.configs | default('Template Error') | to_nice_json(indent=3) }}
info: {{ test_data.result_info | default('Template Error') | to_nice_json(indent=3) }}
status: {{ test_data.result_status | default('Template Error') }}
Commands: {{ test_data.commands | default('Template Error') | to_nice_json(indent=3) }}
Before: {{ test_data.before | default('Template Error') | to_nice_json(indent=3) }}
After: {{ test_data.after | default('Template Error') | to_nice_json(indent=3) }}
Error: {{ test_data.module_stderr | default('Template Error') | to_nice_json(indent=3) }}
Input: {{ test_data.configs | default('Template Error') | to_nice_json(indent=3) }}
info: {{ test_data.result_info | default('Template Error') | to_nice_json(indent=3) }}
status: {{ test_data.result_status | default('Template Error') }}
Commands: {{ test_data.commands | default('Template Error') | to_nice_json(indent=3) }}
Before: {{ test_data.before | default('Template Error') | to_nice_json(indent=3) }}
After: {{ test_data.after | default('Template Error') | to_nice_json(indent=3) }}
Error: {{ test_data.module_stderr | default('Template Error') | to_nice_json(indent=3) }}