From 3667197efb7b18ec842efd504785965911f8ac4b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 5 Jun 2024 18:18:34 +0200 Subject: Adding upstream version 10.0.0+dfsg. Signed-off-by: Daniel Baumann --- .../plugins/module_utils/test_idrac_redfish.py | 6 +- .../tests/unit/plugins/module_utils/test_ome.py | 4 +- .../unit/plugins/module_utils/test_redfish.py | 6 +- .../plugins/module_utils/test_session_utils.py | 415 +++++++ .../unit/plugins/modules/test_idrac_diagnostics.py | 1057 ++++++++++++++++++ .../tests/unit/plugins/modules/test_idrac_reset.py | 639 ++++++++++- .../unit/plugins/modules/test_idrac_session.py | 590 ++++++++++ .../plugins/modules/test_idrac_storage_volume.py | 1178 ++++++++++++++++++++ .../tests/unit/plugins/modules/test_idrac_user.py | 379 +++---- .../test_ome_application_console_preferences.py | 8 +- .../test_ome_device_local_access_configuration.py | 18 +- .../plugins/modules/test_ome_device_location.py | 38 +- .../modules/test_ome_device_mgmt_network.py | 4 +- .../modules/test_ome_device_power_settings.py | 155 ++- .../modules/test_ome_device_quick_deploy.py | 4 +- .../tests/unit/plugins/modules/test_ome_devices.py | 8 +- .../plugins/modules/test_redfish_storage_volume.py | 161 ++- 17 files changed, 4180 insertions(+), 490 deletions(-) create mode 100644 ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_session_utils.py create mode 100644 ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_diagnostics.py create mode 100644 ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_session.py create mode 100644 ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_storage_volume.py (limited to 'ansible_collections/dellemc/openmanage/tests/unit/plugins') diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_idrac_redfish.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_idrac_redfish.py index fc3b3543d..8d83057e9 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_idrac_redfish.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_idrac_redfish.py @@ -44,7 +44,7 @@ class TestIdracRedfishRest(object): @pytest.fixture def module_params(self): - module_parameters = {'idrac_ip': '192.168.0.1', 'idrac_user': 'username', + module_parameters = {'idrac_ip': 'xxx.xxx.x.x', 'idrac_user': 'username', 'idrac_password': 'password', 'idrac_port': '443'} return module_parameters @@ -125,7 +125,7 @@ class TestIdracRedfishRest(object): ]) def test_build_url(self, query_params, mocker, idrac_redfish_object): """builds complete url""" - base_uri = 'https://192.168.0.1:443/api' + base_uri = 'https://xxx.xxx.x.x:443/api' path = "/AccountService/Accounts" mocker.patch(MODULE_UTIL_PATH + 'idrac_redfish.iDRACRedfishAPI._get_url', return_value=base_uri + path) @@ -137,7 +137,7 @@ class TestIdracRedfishRest(object): def test_build_url_none(self, mocker, idrac_redfish_object): """builds complete url""" - base_uri = 'https://192.168.0.1:443/api' + base_uri = 'https://xxx.xxx.x.x:443/api' mocker.patch(MODULE_UTIL_PATH + 'redfish.Redfish._get_base_url', return_value=base_uri) url = idrac_redfish_object._build_url("", None) diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_ome.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_ome.py index 93892a744..60c5341a1 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_ome.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_ome.py @@ -47,7 +47,7 @@ class TestOMERest(object): @pytest.fixture def module_params(self): - module_parameters = {'hostname': '192.168.0.1', 'username': 'username', + module_parameters = {'hostname': 'xxx.xxx.x.x', 'username': 'username', 'password': 'password', "port": 443} return module_parameters @@ -150,7 +150,7 @@ class TestOMERest(object): ]) def test_build_url(self, query_param, mocker, module_params): """builds complete url""" - base_uri = 'https://192.168.0.1:443/api' + base_uri = 'https://xxx.xxx.x.x:443/api' path = "AccountService/Accounts" mocker.patch(MODULE_UTIL_PATH + 'ome.RestOME._get_base_url', return_value=base_uri) diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_redfish.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_redfish.py index 2e092af15..1dd3ab8b4 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_redfish.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_redfish.py @@ -39,7 +39,7 @@ class TestRedfishRest(object): @pytest.fixture def module_params(self): - module_parameters = {'baseuri': '192.168.0.1:443', 'username': 'username', + module_parameters = {'baseuri': 'xxx.xxx.x.x:443', 'username': 'username', 'password': 'password'} return module_parameters @@ -120,7 +120,7 @@ class TestRedfishRest(object): ]) def test_build_url(self, query_params, mocker, redfish_object): """builds complete url""" - base_uri = 'https://192.168.0.1:443/api' + base_uri = 'https://xxx.xxx.x.x:443/api' path = "/AccountService/Accounts" mocker.patch(MODULE_UTIL_PATH + 'redfish.Redfish._get_base_url', return_value=base_uri) @@ -132,7 +132,7 @@ class TestRedfishRest(object): def test_build_url_none(self, mocker, redfish_object): """builds complete url""" - base_uri = 'https://192.168.0.1:443/api' + base_uri = 'https://xxx.xxx.x.x:443/api' mocker.patch(MODULE_UTIL_PATH + 'redfish.Redfish._get_base_url', return_value=base_uri) url = redfish_object._build_url("", None) diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_session_utils.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_session_utils.py new file mode 100644 index 000000000..c53c81b01 --- /dev/null +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/module_utils/test_session_utils.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- + +# +# Dell OpenManage Ansible Modules +# Version 9.2.0 +# Copyright (C) 2024 Dell Inc. + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# All rights reserved. Dell, EMC, and other trademarks are trademarks of Dell Inc. or its subsidiaries. +# Other trademarks may be trademarks of their respective owners. +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import os +import json +import pytest +from mock import MagicMock +from ansible.module_utils.urls import SSLValidationError +from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError +from ansible_collections.dellemc.openmanage.plugins.module_utils.session_utils import SessionAPI, OpenURLResponse + +MODULE_UTIL_PATH = 'ansible_collections.dellemc.openmanage.plugins.module_utils.' +OPEN_URL = 'session_utils.open_url' +TEST_PATH = "/testpath" +INVOKE_REQUEST = 'session_utils.SessionAPI.invoke_request' +JOB_COMPLETE = 'session_utils.SessionAPI.wait_for_job_complete' +API_TASK = '/api/tasks' +SLEEP_TIME = 'session_utils.time.sleep' + + +class TestSessionRest(object): + """ + Main class for testing the SessionUtils class. + """ + @pytest.fixture + def mock_response(self): + """ + Returns a MagicMock object representing a mock HTTP response. + + The mock response has the following properties: + - `getcode()` method returns 200 + - `headers` property is a dictionary containing the headers of the response + - `getheaders()` method returns the same dictionary as `headers` + - `read()` method returns a JSON string representing a dictionary with a "value" key and + "data" as its value + + :return: A MagicMock object representing a mock HTTP response. + :rtype: MagicMock + """ + mock_response = MagicMock() + mock_response.getcode.return_value = 200 + mock_response.headers = mock_response.getheaders.return_value = { + 'X-Auth-Token': 'token_id'} + mock_response.read.return_value = json.dumps({"value": "data"}) + return mock_response + + @pytest.fixture + def module_params(self): + """ + Fixture that returns a dictionary containing module parameters. + + :return: A dictionary with the following keys: + - 'hostname': The hostname of the module. + - 'username': The username for authentication. + - 'password': The password for authentication. + - 'port': The port number for the module. + """ + module_parameters = {'hostname': 'xxx.xxx.x.x', 'username': 'username', + 'password': 'password', 'port': '443'} + return module_parameters + + @pytest.fixture + def session_utils_object(self, module_params): + """ + Creates a SessionAPI object using the provided `module_params` and returns it. + + :param module_params: A dictionary containing the parameters for the SessionAPI object. + :type module_params: dict + :return: A SessionAPI object. + :rtype: SessionAPI + """ + session_utils_obj = SessionAPI(module_params) + return session_utils_obj + + def test_invoke_request_with_session(self, mock_response, mocker, module_params): + """ + Test the invoke_request method of the SessionAPI class with a session. + + Args: + mock_response (MagicMock): A mocked response object. + mocker (MockerFixture): A fixture for mocking objects. + module_params (dict): The parameters for the module. + + Returns: + None + + Assertions: + - Asserts that the response status code is 200. + - Asserts that the response JSON data is {"value": "data"}. + - Asserts that the response success attribute is True. + """ + mocker.patch(MODULE_UTIL_PATH + OPEN_URL, + return_value=mock_response) + obj = SessionAPI(module_params) + response = obj.invoke_request(TEST_PATH, "GET") + assert response.status_code == 200 + assert response.json_data == {"value": "data"} + assert response.success is True + + def test_invoke_request_without_session(self, mock_response, mocker): + """ + Test the `invoke_request` method of the `SessionAPI` class without using a session. + + This test case mocks the `open_url` function from the `MODULE_UTIL_PATH` module to return a + mock response. + It then creates an instance of the `SessionAPI` class with mock module parameters. + The `invoke_request` method is called with a test path and a GET method. + The test asserts that the response status code is 200, the response JSON data is + {"value": "data"}, + and the response success flag is True. + + Parameters: + - mock_response (MagicMock): A mock response object to be returned by the `open_url` + function. + - mocker (MockerFixture): A fixture provided by the pytest library for mocking + functions. + + Returns: + None + """ + mocker.patch(MODULE_UTIL_PATH + OPEN_URL, + return_value=mock_response) + module_params = {'hostname': 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX', 'username': + 'username', + 'password': 'password', "port": '443'} + obj = SessionAPI(module_params) + response = obj.invoke_request(TEST_PATH, "GET") + assert response.status_code == 200 + assert response.json_data == {"value": "data"} + assert response.success is True + + def test_invoke_request_without_session_with_header(self, mock_response, mocker, + module_params): + """ + Test the `invoke_request` method of the `SessionAPI` class when a session is not used and a + header is provided. + + This test method mocks the `open_url` function from the `module_utils` module to return a + mock response object. It then creates an instance of the `SessionAPI` class with the + provided `module_params`. The `invoke_request` method is called with a test path, a request + method of "POST", and a headers dictionary containing a single key-value pair. + + The test asserts that the response status code is 200, the response JSON data is + `{"value": "data"}`, and the response success flag is `True`. + + Parameters: + - `mock_response` (MagicMock): A mock response object to be returned by the `open_url` + function. + - `mocker` (MockerFixture): A fixture for patching and mocking objects. + - `module_params` (dict): A dictionary containing the module parameters. + + Returns: + None + """ + mocker.patch(MODULE_UTIL_PATH + OPEN_URL, + return_value=mock_response) + obj = SessionAPI(module_params) + response = obj.invoke_request(TEST_PATH, "POST", headers={"application": "octstream"}) + assert response.status_code == 200 + assert response.json_data == {"value": "data"} + assert response.success is True + + @pytest.mark.parametrize("exc", [URLError, SSLValidationError, ConnectionError]) + def test_invoke_request_error_case_handling(self, exc, mocker, module_params): + """ + Test the error handling in the `invoke_request` method of the `SessionAPI` class. + + This function tests the handling of different types of exceptions that can occur during an + HTTP request. It uses the `pytest.mark.parametrize` decorator to run the test multiple + times with different exception types. The test mocks the `open_url` method of the + `SessionAPI` class to raise the specified exception. It then asserts that the correct + exception is raised when calling the `invoke_request` method. + + Args: + exc (Exception): The exception type to test. + mocker (MockerFixture): The mocker fixture used for mocking dependencies. + module_params (dict): The parameters for the `SessionAPI` object. + + Raises: + exc: The specified exception type if it is raised during the `invoke_request` call. + """ + mocker.patch(MODULE_UTIL_PATH + OPEN_URL, + side_effect=exc("test")) + with pytest.raises(exc): + obj = SessionAPI(module_params) + obj.invoke_request(TEST_PATH, "GET") + + def test_invoke_request_http_error_handling(self, mock_response, mocker, module_params): + """ + Test the HTTP error handling in the `invoke_request` method of the `SessionAPI` class. + + Args: + mock_response (Mock): A mock object representing the response from the HTTP request. + mocker (MockerFixture): A fixture for mocking objects. + module_params (dict): The parameters for the module. + + Raises: + HTTPError: If an HTTP error occurs during the invocation of the request. + + Returns: + None + """ + open_url_mock = mocker.patch(MODULE_UTIL_PATH + OPEN_URL, + return_value=mock_response) + open_url_mock.side_effect = HTTPError('https://testhost.com/', 400, + 'Bad Request Error', {}, None) + with pytest.raises(HTTPError): + obj = SessionAPI(module_params) + obj.invoke_request(TEST_PATH, "GET") + + @pytest.mark.parametrize("query_params", [ + {"inp": {"$filter": "UserName eq 'admin'"}, + "out": "%24filter=UserName+eq+%27admin%27"}, + {"inp": {"$top": 1, "$skip": 2, "$filter": "JobType/Id eq 8"}, "out": + "%24top=1&%24skip=2&%24filter=JobType%2FId+eq+8"}, + {"inp": {"$top": 1, "$skip": 3}, "out": "%24top=1&%24skip=3"} + ]) + def test_build_url(self, query_params, mocker, session_utils_object): + """ + builds complete url + """ + base_uri = 'https://xxx.xxx.x.x:443/api' + path = "/AccountService/Accounts" + mocker.patch(MODULE_UTIL_PATH + 'session_utils.SessionAPI._get_url', + return_value=base_uri + path) + inp = query_params["inp"] + out = query_params["out"] + url = session_utils_object._build_url( + path, query_param=inp) + assert url == base_uri + path + "?" + out + + def test_build_url_none(self, mocker, session_utils_object): + """ + builds complete url + """ + base_uri = 'https://xxx.xxx.x.x:443/api' + mocker.patch(MODULE_UTIL_PATH + 'redfish.Redfish._get_base_url', + return_value=base_uri) + url = session_utils_object._build_url("", None) + assert url == "" + + def test_invalid_json_openurlresp(self): + """ + Test the behavior when an invalid JSON string is passed to the `OpenURLResponse` object. + + This test case creates an instance of the `OpenURLResponse` class with an empty dictionary + as the initial data. + Then, it sets the `body` attribute of the object to an invalid JSON string. + Finally, it asserts that calling the `json_data` attribute raises a `ValueError` with the + message "Unable to parse json". + + Parameters: + self (TestCase): The current test case instance. + + Returns: + None + """ + obj = OpenURLResponse({}) + obj.body = 'invalid json' + with pytest.raises(ValueError) as e: + obj.json_data + assert e.value.args[0] == "Unable to parse json" + + def test_reason(self): + """ + Test the `reason` property of the `OpenURLResponse` class. + + This test case mocks the `read` method of the `obj` object to return an empty JSON string. + It then creates an instance of the `OpenURLResponse` class with the mocked `obj` object. + The `reason` property of the `OpenURLResponse` instance is then accessed and stored in the + `reason_ret` variable. Finally, the test asserts that the value of `reason_ret` is equal to + the expected value of "returning reason". + + Parameters: + self (TestCase): The test case object. + + Returns: + None + """ + def mock_read(): + return "{}" + obj = MagicMock() + obj.reason = "returning reason" + obj.read = mock_read + ourl = OpenURLResponse(obj) + reason_ret = ourl.reason + assert reason_ret == "returning reason" + + def test_requests_ca_bundle_set(self, mocker, mock_response, session_utils_object): + """ + Test if the `REQUESTS_CA_BUNDLE` environment variable is set correctly. + + This function tests if the `REQUESTS_CA_BUNDLE` environment variable is set to the expected + value. It does this by setting the environment variable to a specific path, patching the + `invoke_request` method of the `session_utils_object` to return a mock response, and then + calling the `_get_omam_ca_env` method of the `session_utils_object`. Finally, it asserts + that the result of the `_get_omam_ca_env` method is equal to the expected path. + + Parameters: + - mocker (MockerFixture): A fixture provided by the pytest library used to patch the + `invoke_request` method. + - mock_response (Mock): A mock object representing the response returned by the + `invoke_request` method. + - session_utils_object (SessionUtils): An instance of the `SessionUtils` class. + + Returns: + None + """ + os.environ["REQUESTS_CA_BUNDLE"] = "/path/to/requests_ca_bundle.pem" + mocker.patch(MODULE_UTIL_PATH + INVOKE_REQUEST, + return_value=mock_response) + result = session_utils_object._get_omam_ca_env() + assert result == "/path/to/requests_ca_bundle.pem" + del os.environ["REQUESTS_CA_BUNDLE"] + + def test_curl_ca_bundle_set(self, mocker, mock_response, session_utils_object): + """ + Test the functionality of the `curl_ca_bundle_set` method. + + This test case verifies that the `curl_ca_bundle_set` method correctly sets the + `CURL_CA_BUNDLE` environment variable and retrieves the value using the `_get_omam_ca_env` + method. + + Parameters: + - mocker (MockerFixture): A fixture provided by the pytest-mock library used to patch + the `invoke_request` method. + - mock_response (MagicMock): A mock object representing the response returned by the + `invoke_request` method. + - session_utils_object (SessionUtils): An instance of the `SessionUtils` class. + + Returns: + None + + Raises: + AssertionError: If the retrieved value from `_get_omam_ca_env` does not match the + expected value. + + Note: + - The test case sets the `CURL_CA_BUNDLE` environment variable to + "/path/to/curl_ca_bundle.pem" before executing the test. + - The test case deletes the `CURL_CA_BUNDLE` environment variable after the test is + completed. + """ + os.environ["CURL_CA_BUNDLE"] = "/path/to/curl_ca_bundle.pem" + mocker.patch(MODULE_UTIL_PATH + INVOKE_REQUEST, + return_value=mock_response) + result = session_utils_object._get_omam_ca_env() + assert result == "/path/to/curl_ca_bundle.pem" + del os.environ["CURL_CA_BUNDLE"] + + def test_omam_ca_bundle_set(self, mocker, mock_response, session_utils_object): + """ + Test the functionality of the `_get_omam_ca_env` method in the `SessionUtils` class. + + This test case verifies that the `_get_omam_ca_env` method correctly retrieves the value of + the `OMAM_CA_BUNDLE` environment variable and returns it. + + Parameters: + - mocker (MockerFixture): A fixture provided by the pytest library used for mocking + objects. + - mock_response (MagicMock): A mock object representing the response returned by the + `invoke_request` method. + - session_utils_object (SessionUtils): An instance of the `SessionUtils` class. + + Returns: + None + + Raises: + AssertionError: If the returned value from `_get_omam_ca_env` does not match the + expected value. + + Side Effects: + - Sets the value of the `OMAM_CA_BUNDLE` environment variable to + "/path/to/omam_ca_bundle.pem". + - Deletes the `OMAM_CA_BUNDLE` environment variable after the test case is complete. + """ + os.environ["OMAM_CA_BUNDLE"] = "/path/to/omam_ca_bundle.pem" + mocker.patch(MODULE_UTIL_PATH + INVOKE_REQUEST, + return_value=mock_response) + result = session_utils_object._get_omam_ca_env() + assert result == "/path/to/omam_ca_bundle.pem" + del os.environ["OMAM_CA_BUNDLE"] + + def test_no_env_variable_set(self, mocker, mock_response, session_utils_object): + """ + Test the case when no environment variable is set. + + Args: + mocker (MockerFixture): The mocker fixture used to mock functions and objects. + mock_response (MagicMock): The mock response object used to simulate API responses. + session_utils_object (SessionUtils): The SessionUtils object under test. + + Returns: + None + + Asserts: + - The result of the _get_omam_ca_env() method is None. + """ + mocker.patch(MODULE_UTIL_PATH + INVOKE_REQUEST, + return_value=mock_response) + result = session_utils_object._get_omam_ca_env() + assert result is None diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_diagnostics.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_diagnostics.py new file mode 100644 index 000000000..987ff83d2 --- /dev/null +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_diagnostics.py @@ -0,0 +1,1057 @@ +# -*- coding: utf-8 -*- + +# +# Dell OpenManage Ansible Modules +# Version 9.0.0 +# Copyright (C) 2024 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) +# + +from __future__ import absolute_import, division, print_function + +from io import StringIO +import json +import tempfile + +import pytest +from urllib.error import HTTPError, URLError +from ansible.module_utils.urls import ConnectionError, SSLValidationError +from ansible.module_utils._text import to_text +from ansible_collections.dellemc.openmanage.plugins.modules import idrac_diagnostics +from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule +from mock import MagicMock +from ansible_collections.dellemc.openmanage.plugins.modules.idrac_diagnostics import main + +MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.idrac_diagnostics.' +MODULE_UTILS_PATH = 'ansible_collections.dellemc.openmanage.plugins.module_utils.utils.' + +SUCCESS_EXPORT_MSG = "Successfully exported the diagnostics." +FAILURE_EXPORT_MSG = "Unable to copy the ePSA Diagnostics results file to the network share." +SUCCESS_RUN_MSG = "Successfully ran the diagnostics operation." +SUCCESS_RUN_AND_EXPORT_MSG = "Successfully ran and exported the diagnostics." +RUNNING_RUN_MSG = "Successfully triggered the job to run diagnostics." +ALREADY_RUN_MSG = "The diagnostics job is already present." +INVALID_DIRECTORY_MSG = "Provided directory path '{path}' is not valid." +NO_OPERATION_SKIP_MSG = "The operation is skipped." +INSUFFICIENT_DIRECTORY_PERMISSION_MSG = "Provided directory path '{path}' is not writable. " \ + "Please check if the directory has appropriate permissions" +UNSUPPORTED_FIRMWARE_MSG = "iDRAC firmware version is not supported." +TIMEOUT_NEGATIVE_OR_ZERO_MSG = "The parameter `job_wait_timeout` value cannot be negative or zero." +WAIT_TIMEOUT_MSG = "The job is not complete after {0} seconds." +START_TIME = "The specified scheduled time occurs in the past, " \ + "provide a future time to schedule the job." +INVALID_TIME = "The specified date and time `{0}` to schedule the diagnostics is not valid. Enter a valid date and time." +END_START_TIME = "The end time `{0}` to schedule the diagnostics must be greater than the start time `{1}`." +CHANGES_FOUND_MSG = "Changes found to be applied." +NO_FILE = "The diagnostics file does not exist." + +PROXY_SERVER = "proxy.example.com" +PAYLOAD_FUNC = "Diagnostics.get_payload_details" +VALIDATE_TIME_FUNC = "RunDiagnostics._RunDiagnostics__validate_time" +EXPORT_FUNC = "ExportDiagnostics._ExportDiagnostics__export_diagnostics" +RUN_EXEC_FUNC = "RunDiagnostics.execute" +MESSAGE_EXTENDED = "@Message.ExtendedInfo" +DIAGS_ODATA = "/DiagnosticsService" +REDFISH = "/redfish/v1" +REDFISH_DIAGNOSTICS_URL = "/redfish/v1/diagnostics" +REDFISH_BASE_API = '/redfish/v1/api' +MANAGER_URI_ONE = "/redfish/v1/managers/1" +API_ONE = "/local/action" +EXPORT_URL_MOCK = '/redfish/v1/export_diagnostics' +RUN_URL_MOCK = '/redfish/v1/import_diagnostics' +API_INVOKE_MOCKER = "iDRACRedfishAPI.invoke_request" +ODATA = "@odata.id" +DIAGS_FILE_NAME = 'test_diagnostics.txt' +SHARE_NAME = tempfile.gettempdir() +IP = "X.X.X.X" +HTTPS_PATH = "https://testhost.com" +HTTP_ERROR = "http error message" +APPLICATION_JSON = "application/json" + + +class TestDiagnostics(FakeAnsibleModule): + module = idrac_diagnostics + + @pytest.fixture + def idrac_diagnostics_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_diagnostics_mock(self, mocker, idrac_diagnostics_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_diagnostics_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_diagnostics_mock + return idrac_conn_mock + + def test_execute(self, idrac_default_args, idrac_connection_diagnostics_mock): + obj = MagicMock() + diagnostics_obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, obj) + diagnostics_obj.execute() + + def test_get_payload_details(self, idrac_connection_diagnostics_mock): + obj = MagicMock() + diags_obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, obj) + # Scenario 1: With all values + obj.params.get.return_value = { + 'ip_address': IP, + 'share_name': 'my_share', + 'username': 'my_user', + 'password': 'my_password', + 'file_name': DIAGS_FILE_NAME, + 'share_type': 'http', + 'ignore_certificate_warning': 'on', + 'proxy_support': 'parameters_proxy', + 'proxy_type': 'socks', + 'proxy_server': PROXY_SERVER, + 'proxy_port': 8080, + 'proxy_username': 'my_username', + 'proxy_password': 'my_password' + } + result = diags_obj.get_payload_details() + expected_result = { + 'IPAddress': IP, + 'ShareName': 'my_share', + 'UserName': 'my_user', + 'Password': 'my_password', + 'FileName': DIAGS_FILE_NAME, + 'ShareType': 'HTTP', + 'IgnoreCertWarning': 'On', + 'ProxySupport': 'ParametersProxy', + 'ProxyType': 'SOCKS', + 'ProxyServer': PROXY_SERVER, + 'ProxyPort': '8080', + 'ProxyUname': 'my_username', + 'ProxyPasswd': 'my_password' + } + assert result == expected_result + + # Scenario 2: With no proxy values + obj.params.get.return_value = { + 'ip_address': IP, + 'share_name': 'my_share', + 'username': 'my_user', + 'password': 'my_password', + 'file_name': DIAGS_FILE_NAME, + 'share_type': 'http', + 'ignore_certificate_warning': 'on' + } + result = diags_obj.get_payload_details() + expected_result = { + 'IPAddress': IP, + 'ShareName': 'my_share', + 'UserName': 'my_user', + 'Password': 'my_password', + 'FileName': DIAGS_FILE_NAME, + 'ShareType': 'HTTP', + 'IgnoreCertWarning': 'On' + } + assert result == expected_result + + # Scenario 3: With no proxy username and password values + obj.params.get.return_value = { + 'ip_address': IP, + 'share_name': 'my_share', + 'username': 'my_user', + 'password': 'my_password', + 'file_name': DIAGS_FILE_NAME, + 'share_type': 'http', + 'ignore_certificate_warning': 'on', + 'proxy_support': 'parameters_proxy', + 'proxy_type': 'socks', + 'proxy_server': PROXY_SERVER, + 'proxy_port': 8080 + } + result = diags_obj.get_payload_details() + expected_result = { + 'IPAddress': IP, + 'ShareName': 'my_share', + 'UserName': 'my_user', + 'Password': 'my_password', + 'FileName': DIAGS_FILE_NAME, + 'ShareType': 'HTTP', + 'IgnoreCertWarning': 'On', + 'ProxySupport': 'ParametersProxy', + 'ProxyType': 'SOCKS', + 'ProxyServer': PROXY_SERVER, + 'ProxyPort': '8080' + } + assert result == expected_result + + def test_network_share(self, idrac_connection_diagnostics_mock, idrac_default_args, mocker): + # Scenario 1: ShareType is LOCAL and directory is invalid + payload = {"FileName": DIAGS_FILE_NAME, "ShareType": "LOCAL", "ShareName": "my_share"} + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value=payload) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + diagnostics_obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + diagnostics_obj.test_network_share() + assert exc.value.args[0] == INVALID_DIRECTORY_MSG.format(path="my_share") + + # Scenario 2: ShareType is LOCAL and directory is not writable + payload = {"FileName": DIAGS_FILE_NAME, "ShareType": "HTTP", "ShareName": SHARE_NAME} + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value=payload) + mocker.patch(MODULE_PATH + "Diagnostics.get_test_network_share_url", return_value=API_ONE) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + diagnostics_obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, f_module) + ob = diagnostics_obj.test_network_share() + assert ob is None + + # Scenario 3: ShareType is not LOCAL + obj = MagicMock() + payload = {"FileName": DIAGS_FILE_NAME, "ShareType": "HTTP", "ShareName": "my_share"} + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value=payload) + mocker.patch(MODULE_PATH + "Diagnostics.get_test_network_share_url", return_value=API_ONE) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + diagnostics_obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, f_module) + diagnostics_obj.test_network_share() + + # Scenario 4: HTTP Error + payload = {"FileName": DIAGS_FILE_NAME, "ShareType": "HTTP", "ShareName": "my_share"} + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value=payload) + json_str = to_text(json.dumps({"error": {MESSAGE_EXTENDED: [ + { + 'MessageId': "123", + "Message": "Error" + } + ]}})) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + side_effect=HTTPError(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + diagnostics_obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + diagnostics_obj.test_network_share() + assert exc.value.args[0] == 'Error' + + def test_get_test_network_share_url(self, idrac_connection_diagnostics_mock, idrac_default_args, mocker): + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + return_value={"Links": {"Oem": {"Dell": {"DellLCService": {ODATA: DIAGS_ODATA}}}}, + "Actions": {"#DellLCService.TestNetworkShare": {"target": API_ONE}}}) + + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, f_module) + resp = obj.get_test_network_share_url() + assert resp == API_ONE + + # Scenario 2: for error message + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, "Error")) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + obj = self.module.Diagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + obj.get_test_network_share_url() + assert exc.value.args[0] == "Error" + + +class TestRunDiagnostics(FakeAnsibleModule): + module = idrac_diagnostics + + @pytest.fixture + def idrac_diagnostics_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_diagnostics_mock(self, mocker, idrac_diagnostics_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_diagnostics_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_diagnostics_mock + return idrac_conn_mock + + def test_execute(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + # Scenario 1: JobState is completed + job = {"JobState": "Completed"} + mocker.patch(MODULE_PATH + "Diagnostics.test_network_share", return_value=None) + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__get_run_diagnostics_url", return_value=None) + mocker.patch(MODULE_PATH + "RunDiagnostics.check_diagnostics_jobs", return_value=None) + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__run_diagnostics", return_value=obj) + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__perform_job_wait", return_value=job) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + msg, job_status, file_path = run_diagnostics_obj.execute() + assert msg == SUCCESS_RUN_MSG + assert job_status == job + assert file_path is None + + # Scenario 2: JobState is scheduled + job = {"JobState": "Scheduled"} + idrac_default_args.update({'export': True}) + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__perform_job_wait", return_value=job) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + msg, job_status, file_path = run_diagnostics_obj.execute() + assert msg == RUNNING_RUN_MSG + assert job_status == job + assert file_path is None + + def test_run_diagnostics(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__get_run_diagnostics_url", return_value=API_ONE) + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__validate_time_format", return_value=True) + mocker.patch(MODULE_PATH + VALIDATE_TIME_FUNC, return_value=True) + mocker.patch(MODULE_PATH + "RunDiagnostics._RunDiagnostics__validate_end_time", return_value=True) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + + # Scenario 1: With start and end time + run_params = { + 'run_mode': 'express', + 'reboot_type': 'power_cycle', + 'scheduled_start_time': '20240715235959', + 'scheduled_end_time': '20250715235959' + } + idrac_default_args.update(run_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + status = run_diagnostics_obj._RunDiagnostics__run_diagnostics() + assert status == obj + + # Scenario 2: Without time + run_params = { + 'run_mode': 'express', + 'reboot_type': 'force' + } + idrac_default_args.update(run_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + status = run_diagnostics_obj._RunDiagnostics__run_diagnostics() + assert status == obj + + # Scenario 3: With start and end time as empty + run_params = { + 'run_mode': 'express', + 'reboot_type': 'power_cycle', + 'scheduled_start_time': '', + 'scheduled_end_time': '' + } + idrac_default_args.update(run_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + status = run_diagnostics_obj._RunDiagnostics__run_diagnostics() + assert status == obj + + # Scenario 4: With start time + run_params = { + 'run_mode': 'express', + 'reboot_type': 'power_cycle', + 'scheduled_start_time': '20200715235959' + } + mocker.patch(MODULE_PATH + VALIDATE_TIME_FUNC, return_value=False) + idrac_default_args.update(run_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + status = run_diagnostics_obj._RunDiagnostics__run_diagnostics() + assert status == obj + + # Scenario 5: With end time + run_params = { + 'run_mode': 'express', + 'reboot_type': 'power_cycle', + 'scheduled_end_time': '20200715235959' + } + mocker.patch(MODULE_PATH + VALIDATE_TIME_FUNC, return_value=False) + idrac_default_args.update(run_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + status = run_diagnostics_obj._RunDiagnostics__run_diagnostics() + assert status == obj + + def test_get_run_diagnostics_url(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + # Scenario 1: With url + mocker.patch(MODULE_PATH + "get_dynamic_uri", + return_value={"Links": {"Oem": {"Dell": {"DellLCService": {ODATA: DIAGS_ODATA}}}}, + "Actions": {"#DellLCService.RunePSADiagnostics": {"target": API_ONE}}}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + run_diagnostics_obj._RunDiagnostics__get_run_diagnostics_url() + assert run_diagnostics_obj.run_url == API_ONE + + # Scenario 2: When url is empty for Links + mocker.patch(MODULE_PATH + "get_dynamic_uri", + return_value={"Links": {}}) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__get_run_diagnostics_url() + assert exc.value.args[0] == UNSUPPORTED_FIRMWARE_MSG + + # Scenario 3: For error message + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, "error")) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__get_run_diagnostics_url() + assert exc.value.args[0] == "error" + + def test_check_diagnostics_jobs(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + temp_list = {"Members": [{"Id": "JID_123", "JobType": "RemoteDiagnostics", "JobState": "New"}]} + obj.json_data = temp_list + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + + # Scenario 1: Check mode with job id + with pytest.raises(Exception) as exc: + run_diagnostics_obj.check_diagnostics_jobs() + assert exc.value.args[0] == ALREADY_RUN_MSG + + # Scenario 2: Check mode without job id + temp_list = {"Members": [{"Id": "", "JobType": "Test", "JobState": "New"}]} + obj.json_data = temp_list + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj.check_diagnostics_jobs() + assert exc.value.args[0] == CHANGES_FOUND_MSG + + # Scenario 3: Normal mode with job id + temp_list = {"Members": [{"Id": "666", "JobType": "RemoteDiagnostics", "JobState": "New"}]} + obj.json_data = temp_list + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj.check_diagnostics_jobs() + assert exc.value.args[0] == ALREADY_RUN_MSG + + # Scenario 4: Normal mode without job id + temp_list = {"Members": [{"Id": "", "JobType": "RemoteDiagnostics", "JobState": "New"}]} + obj.json_data = temp_list + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + resp = run_diagnostics_obj.check_diagnostics_jobs() + assert resp is None + + def test_validate_job_timeout(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + # Scenario 1: Negative timeout + idrac_default_args.update({'job_wait': True, 'job_wait_timeout': -120}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__validate_job_timeout() + assert exc.value.args[0] == TIMEOUT_NEGATIVE_OR_ZERO_MSG + + # Scenario 2: Valid timeout + idrac_default_args.update({'job_wait': True, 'job_wait_timeout': 120}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + resp = run_diagnostics_obj._RunDiagnostics__validate_job_timeout() + assert resp is None + + def test_validate_time_format(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + idrac_default_args.update({'time': "20250715235959"}) + # Scenario 1: Time with offset + time = "2024-09-14T05:59:35-05:00" + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + formatted_time = run_diagnostics_obj._RunDiagnostics__validate_time_format(time) + assert formatted_time == "20240914055935" + + # Scenario 2: Time without offset + time = "20250715235959" + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + formatted_time = run_diagnostics_obj._RunDiagnostics__validate_time_format(time) + assert formatted_time == "20250715235959" + + # Scenario 3: Invalid time + time = "2025" + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__validate_time_format(time) + assert exc.value.args[0] == INVALID_TIME.format(time) + + def test_validate_time(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + resp = ("2024-09-14T05:59:35-05:00", "-05:00") + mocker.patch(MODULE_PATH + "get_current_time", return_value=resp) + + # Scenario 1: Future time + idrac_default_args.update({'time': "20250715235959"}) + time = idrac_default_args['time'] + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + assert run_diagnostics_obj._RunDiagnostics__validate_time(time) is True + + # Scenario 2: Past time + idrac_default_args.update({'time': "20230715235959"}) + time = idrac_default_args['time'] + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__validate_time(time) + assert exc.value.args[0] == START_TIME + + def test_validate_end_time(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + # Scenario 1: start_time less than end_time + start_time = "20230715235959" + end_time = "20240715235959" + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + assert run_diagnostics_obj._RunDiagnostics__validate_end_time(start_time, end_time) is True + + # Scenario 2: start_time greater than end_time + start_time = "20250715235959" + end_time = "20240715235959" + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__validate_end_time(start_time, end_time) + assert exc.value.args[0] == END_START_TIME.format(end_time, start_time) + + def test_perform_job_wait(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + # Scenario 1: When JobState is completed + obj = MagicMock() + obj.headers = {'Location': REDFISH_BASE_API} + obj.json_data = {'JobState': 'Completed'} + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + mocker.patch(MODULE_PATH + "idrac_redfish_job_tracking", + return_value=(False, 'msg', obj.json_data, 120)) + idrac_default_args.update({'job_wait': True, 'job_wait_timeout': 1200}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + job_dict = run_diagnostics_obj._RunDiagnostics__perform_job_wait(obj) + assert job_dict == obj.json_data + + # Scenario 2: When wait time is less + obj = MagicMock() + obj.headers = {'Location': REDFISH_BASE_API} + obj.json_data = {'JobState': 'Scheduled'} + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + mocker.patch(MODULE_PATH + "idrac_redfish_job_tracking", + return_value=(False, 'msg', obj.json_data, 120)) + idrac_default_args.update({'job_wait': True, 'job_wait_timeout': 10}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__perform_job_wait(obj) + assert exc.value.args[0] == WAIT_TIMEOUT_MSG.format(10) + + # Scenario 3: When JobState is Failed + obj = MagicMock() + obj.headers = {'Location': REDFISH_BASE_API} + obj.json_data = {'JobState': 'Failed', 'Message': 'Job Failed'} + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + mocker.patch(MODULE_PATH + "idrac_redfish_job_tracking", + return_value=(True, 'msg', obj.json_data, 120)) + idrac_default_args.update({'job_wait': True, 'job_wait_timeout': 1200}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + run_diagnostics_obj._RunDiagnostics__perform_job_wait(obj) + assert exc.value.args[0] == 'Job Failed' + + # Scenario 4: When job_wait is False + obj = MagicMock() + obj.headers = {'Location': REDFISH_BASE_API} + obj.json_data = {'JobState': 'Scheduled'} + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + mocker.patch(MODULE_PATH + "idrac_redfish_job_tracking", + return_value=(True, 'msg', obj.json_data, 120)) + idrac_default_args.update({'job_wait': False, 'job_wait_timeout': 1200}) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + job_dict = run_diagnostics_obj._RunDiagnostics__perform_job_wait(obj) + assert job_dict == obj.json_data + + # Scenario 5: When there's no job uri + obj = MagicMock() + obj.headers = {'Location': ''} + idrac_default_args.update({'job_wait': False, 'job_wait_timeout': 1200}) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_diagnostics_obj = self.module.RunDiagnostics(idrac_connection_diagnostics_mock, f_module) + job_dict = run_diagnostics_obj._RunDiagnostics__perform_job_wait(obj) + assert job_dict == {} + + +class TestExportDiagnostics(FakeAnsibleModule): + module = idrac_diagnostics + + @pytest.fixture + def idrac_diagnostics_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_diagnostics_mock(self, mocker, idrac_diagnostics_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_diagnostics_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_diagnostics_mock + return idrac_conn_mock + + def test_execute(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.headers = {"Location": REDFISH} + obj.status_code = 200 + obj.share_name = SHARE_NAME + obj.file_name = DIAGS_FILE_NAME + mocker.patch(MODULE_PATH + "Diagnostics.test_network_share", return_value=None) + mocker.patch(MODULE_PATH + "ExportDiagnostics._ExportDiagnostics__get_export_diagnostics_url", return_value=None) + mocker.patch(MODULE_PATH + "ExportDiagnostics._ExportDiagnostics__export_diagnostics_local", return_value=obj) + + # Scenario 1: share_type = local + export_params = {'share_parameters': {'share_type': "local"}} + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + msg, job_status, file_path = export_diagnostics_obj.execute() + assert msg == SUCCESS_EXPORT_MSG + assert job_status == {} + assert file_path == 'None/None' + + # Scenario 2: share_type = nfs + job = {"JobState": "Completed"} + export_params = {'share_parameters': {'share_type': "nfs"}} + mocker.patch(MODULE_PATH + "ExportDiagnostics._ExportDiagnostics__export_diagnostics_nfs", return_value=obj) + mocker.patch(MODULE_PATH + "ExportDiagnostics.get_job_status", return_value=job) + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + msg, job_status, file_path = export_diagnostics_obj.execute() + assert msg == SUCCESS_EXPORT_MSG + assert job_status == job + assert file_path == 'None/None' + + # Scenario 3: Check mode + obj.status = 400 + mocker.patch(MODULE_PATH + "ExportDiagnostics.perform_check_mode", return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + export_diagnostics_obj.execute() + + def test_export_diagnostics_local(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + export_params = { + 'share_parameters': { + 'share_name': SHARE_NAME, + 'file_name': DIAGS_FILE_NAME + } + } + obj = MagicMock() + obj.status = 200 + obj.headers = {'Location': REDFISH_BASE_API} + obj.filename = DIAGS_FILE_NAME + mocker.patch(MODULE_PATH + 'ExportDiagnostics._ExportDiagnostics__export_diagnostics', return_value=obj) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception): + export_diagnostics_obj._ExportDiagnostics__export_diagnostics_local() + + def test_export_diagnostics_http(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value=None) + mocker.patch(MODULE_PATH + EXPORT_FUNC, return_value=obj) + # Scenario 1: With ipv4 + export_params = { + 'share_parameters': { + 'ip_address': IP, + 'file_name': 'test_diags', + 'share_type': 'http', + 'share_name': 'myshare' + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics_http() + assert result == obj + + # Scenario 2: With ipv6 + export_params = { + 'share_parameters': { + 'ip_address': 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX', + 'file_name': 'test_diags', + 'share_type': 'http', + 'share_name': 'myshare' + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics_http() + assert result == obj + + def test_export_diagnostics_cifs(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value={}) + mocker.patch(MODULE_PATH + EXPORT_FUNC, return_value=obj) + # Scenario 1: With workgroup + export_params = { + 'share_parameters': { + 'file_name': 'test_diags', + 'share_type': 'cifs', + 'share_name': 'myshare', + 'ignore_certificate_warning': 'off', + 'workgroup': 'myworkgroup' + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics_cifs() + assert result == obj + + # Scenario 2: Without workgroup + export_params = { + 'share_parameters': { + 'file_name': 'test_diags', + 'share_type': 'cifs', + 'share_name': 'myshare', + 'ignore_certificate_warning': 'off' + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics_cifs() + assert result == obj + + def test_export_diagnostics_nfs(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value={"UserName": "user", "Password": "password"}) + mocker.patch(MODULE_PATH + EXPORT_FUNC, return_value=obj) + export_params = { + 'share_parameters': { + 'share_name': 'share', + 'share_type': 'nfs', + 'ignore_certificate_warning': 'off' + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics_nfs() + assert result == obj + + def test_get_export_diagnostics_url(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + export_params = { + 'share_parameters': { + 'file_name': DIAGS_FILE_NAME, + 'share_type': 'local', + 'ignore_certificate_warning': 'off' + } + } + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, None)) + # Scenario 1: With url + mocker.patch(MODULE_PATH + "get_dynamic_uri", + return_value={"Links": {"Oem": {"Dell": {"DellLCService": {ODATA: DIAGS_ODATA}}}}, + "Actions": {"#DellLCService.ExportePSADiagnosticsResult": {"target": API_ONE}}}) + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + export_diagnostics_obj._ExportDiagnostics__get_export_diagnostics_url() + assert export_diagnostics_obj.export_url == API_ONE + + # Scenario 2: When url is empty + mocker.patch(MODULE_PATH + "get_dynamic_uri", + return_value={"Links": {}}) + with pytest.raises(Exception) as exc: + export_diagnostics_obj._ExportDiagnostics__get_export_diagnostics_url() + assert exc.value.args[0] == UNSUPPORTED_FIRMWARE_MSG + + # Scenario 3: For error message + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, "error")) + with pytest.raises(Exception) as exc: + export_diagnostics_obj._ExportDiagnostics__get_export_diagnostics_url() + assert exc.value.args[0] == "error" + + def test_export_diagnostics(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + payload = mocker.patch(MODULE_PATH + PAYLOAD_FUNC, return_value={}) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + mocker.patch(MODULE_PATH + "ExportDiagnostics._ExportDiagnostics__get_export_diagnostics_url", return_value=API_ONE) + # Scenario 1: With file name + export_params = { + 'share_parameters': { + 'file_name': DIAGS_FILE_NAME + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics(payload) + assert result == obj + + # Scenario 2: Without file name + export_params = { + 'idrac_ip': IP, + 'share_parameters': { + 'file_name': '' + } + } + idrac_default_args.update(export_params) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + result = export_diagnostics_obj._ExportDiagnostics__export_diagnostics(payload) + assert result == obj + + def test_get_job_status_success(self, mocker, idrac_diagnostics_mock): + obj = self.get_module_mock() + diagnostics_job_response_mock = mocker.MagicMock() + diagnostics_job_response_mock.headers.get.return_value = "HTTPS_PATH/job_tracking/12345" + mocker.patch(MODULE_PATH + "remove_key", return_value={"job_details": "mocked_job_details"}) + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", return_value=[MANAGER_URI_ONE]) + obj_under_test = self.module.ExportDiagnostics(idrac_diagnostics_mock, obj) + mocker.patch(MODULE_PATH + "idrac_redfish_job_tracking", return_value=(False, "mocked_message", {"job_details": "mocked_job_details"}, 0)) + result = obj_under_test.get_job_status(diagnostics_job_response_mock) + assert result == {"job_details": "mocked_job_details"} + + def test_get_job_status_failure(self, mocker, idrac_diagnostics_mock): + obj = self.get_module_mock() + diagnostics_job_response_mock = mocker.MagicMock() + diagnostics_job_response_mock.headers.get.return_value = "HTTPS_PATH/job_tracking/12345" + mocker.patch(MODULE_PATH + "remove_key", return_value={"Message": "None"}) + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", return_value=[MANAGER_URI_ONE]) + obj_under_test = self.module.ExportDiagnostics(idrac_diagnostics_mock, obj) + mocker.patch(MODULE_PATH + "idrac_redfish_job_tracking", return_value=(True, "None", {"Message": "None"}, 0)) + exit_json_mock = mocker.patch.object(obj, "exit_json") + result = obj_under_test.get_job_status(diagnostics_job_response_mock) + exit_json_mock.assert_called_once_with(msg="None", failed=True, job_details={"Message": "None"}) + assert result == {"Message": "None"} + + def test_perform_check_mode(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + # Scenario 1: With status code 200 + obj.status_code = 200 + idrac_default_args.update({'ShareType': 'Local'}) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + export_diagnostics_obj.perform_check_mode() + assert exc.value.args[0] == CHANGES_FOUND_MSG + + # Scenario 2: With status code 400 + obj.status_code = 400 + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + val = export_diagnostics_obj.perform_check_mode() + assert val is None + + # Scenario 3: HTTP Error with message id SYS099 + json_str = to_text(json.dumps({"error": {MESSAGE_EXTENDED: [ + { + 'MessageId': "SYS099", + "Message": NO_FILE + } + ]}})) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + side_effect=HTTPError(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + with pytest.raises(Exception) as exc: + export_diagnostics_obj.perform_check_mode() + assert exc.value.args[0] == NO_FILE + + # Scenario 4: HTTP Error without message id + json_str = to_text(json.dumps({"error": {MESSAGE_EXTENDED: [ + { + 'MessageId': "123", + "Message": "error" + } + ]}})) + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, + side_effect=HTTPError(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + export_diagnostics_obj = self.module.ExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + val = export_diagnostics_obj.perform_check_mode() + assert val is None + + +class TestRunAndExportDiagnostics(FakeAnsibleModule): + module = idrac_diagnostics + + @pytest.fixture + def idrac_diagnostics_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_diagnostics_mock(self, mocker, idrac_diagnostics_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_diagnostics_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_diagnostics_mock + return idrac_conn_mock + + def test_execute(self, idrac_default_args, idrac_connection_diagnostics_mock, mocker): + obj = MagicMock() + obj.status_code = 200 + + def export_execute(): + msg = SUCCESS_EXPORT_MSG + job_status = "None" + file_path = SHARE_NAME + return msg, job_status, file_path + + # Scenario 1: When job wait is true + idrac_default_args.update({'job_wait': True}) + mocker.patch(MODULE_PATH + "RunDiagnostics", return_value=obj) + obj.execute = export_execute + mocker.patch(MODULE_PATH + "ExportDiagnostics", return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_and_export_obj = self.module.RunAndExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + msg, job_status, file_path = run_and_export_obj.execute() + assert msg == SUCCESS_RUN_AND_EXPORT_MSG + + # Scenario 2: When job wait is false + def run_execute(): + msg = RUNNING_RUN_MSG + job_status = "None" + file_path = "None" + return msg, job_status, file_path + + idrac_default_args.update({'job_wait': False}) + obj.execute = run_execute + mocker.patch(MODULE_PATH + "RunDiagnostics", return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + run_obj = self.module.RunAndExportDiagnostics(idrac_connection_diagnostics_mock, f_module) + msg, job_status, file_path = run_obj.execute() + assert msg == RUNNING_RUN_MSG + + +class TestDiagnosticsType(FakeAnsibleModule): + module = idrac_diagnostics + + @pytest.fixture + def idrac_diagnostics_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_diagnostics_mock(self, mocker, idrac_diagnostics_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_diagnostics_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_diagnostics_mock + return idrac_conn_mock + + def test_diagnostics_operation(self, idrac_default_args, idrac_connection_diagnostics_mock): + idrac_default_args.update({"run": True, "export": False}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + diags_class = self.module.DiagnosticsType.diagnostics_operation(idrac_connection_diagnostics_mock, f_module) + assert isinstance(diags_class, self.module.RunDiagnostics) + + idrac_default_args.update({"run": False, "export": True}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + diags_class = self.module.DiagnosticsType.diagnostics_operation(idrac_connection_diagnostics_mock, f_module) + assert isinstance(diags_class, self.module.ExportDiagnostics) + + idrac_default_args.update({"run": True, "export": True}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + diags_class = self.module.DiagnosticsType.diagnostics_operation(idrac_connection_diagnostics_mock, f_module) + assert isinstance(diags_class, self.module.RunAndExportDiagnostics) + + idrac_default_args.update({"run": False, "export": False}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + with pytest.raises(Exception) as exc: + self.module.DiagnosticsType.diagnostics_operation(idrac_connection_diagnostics_mock, f_module) + assert exc.value.args[0] == NO_OPERATION_SKIP_MSG + + @pytest.mark.parametrize("exc_type", + [URLError, HTTPError, SSLValidationError, ConnectionError, TypeError, ValueError]) + def test_idrac_diagnostics_main_exception_handling_case(self, exc_type, mocker, idrac_default_args): + idrac_default_args.update({"run": True}) + # Scenario 1: HTTPError with message id SYS099 + json_str = to_text(json.dumps({"error": {MESSAGE_EXTENDED: [ + { + 'MessageId': "SYS099", + "Message": "Error" + } + ]}})) + if exc_type in [HTTPError, SSLValidationError]: + mocker.patch(MODULE_PATH + RUN_EXEC_FUNC, + side_effect=exc_type(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + else: + mocker.patch(MODULE_PATH + RUN_EXEC_FUNC, + side_effect=exc_type('test')) + result = self._run_module(idrac_default_args) + if exc_type == URLError: + assert result['unreachable'] is True + assert 'msg' in result + + # Scenario 2: HTTPError with message id SYS098 + json_str = to_text(json.dumps({"error": {MESSAGE_EXTENDED: [ + { + 'MessageId': "SYS098", + "Message": "Error" + } + ]}})) + if exc_type in [HTTPError, SSLValidationError]: + mocker.patch(MODULE_PATH + RUN_EXEC_FUNC, + side_effect=exc_type(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + result = self._run_module(idrac_default_args) + assert 'msg' in result + + # Scenario 3: HTTPError with random message id + json_str = to_text(json.dumps({"error": {MESSAGE_EXTENDED: [ + { + 'MessageId': "123", + "Message": "Error" + } + ]}})) + if exc_type in [HTTPError, SSLValidationError]: + mocker.patch(MODULE_PATH + RUN_EXEC_FUNC, + side_effect=exc_type(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + result = self._run_module(idrac_default_args) + assert 'msg' in result + + def test_main(self, mocker): + module_mock = mocker.MagicMock() + idrac_mock = mocker.MagicMock() + diagnostics_mock = mocker.MagicMock() + diagnostics_mock.execute.return_value = (None, None, None) + + mocker.patch(MODULE_PATH + 'get_argument_spec', return_value={}) + mocker.patch(MODULE_PATH + 'idrac_auth_params', {}) + mocker.patch(MODULE_PATH + 'AnsibleModule', return_value=module_mock) + mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', return_value=idrac_mock) + mocker.patch(MODULE_PATH + 'DiagnosticsType.diagnostics_operation', return_value=diagnostics_mock) + main() + diagnostics_mock.execute.return_value = (None, None, SHARE_NAME) + mocker.patch(MODULE_PATH + 'DiagnosticsType.diagnostics_operation', return_value=diagnostics_mock) + main() diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_reset.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_reset.py index a6fbb1d04..d8c23160e 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_reset.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_reset.py @@ -2,94 +2,615 @@ # # Dell OpenManage Ansible Modules -# Version 7.0.0 -# Copyright (C) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. +# Version 9.2.0 +# Copyright (C) 2020-2024 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) # -from __future__ import (absolute_import, division, print_function) +from __future__ import absolute_import, division, print_function __metaclass__ = type -import pytest import json -from ansible_collections.dellemc.openmanage.plugins.modules import idrac_reset -from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule -from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError -from ansible.module_utils.urls import ConnectionError, SSLValidationError -from mock import MagicMock, Mock +import pytest from io import StringIO from ansible.module_utils._text import to_text +from urllib.error import HTTPError, URLError +from ansible.module_utils.urls import ConnectionError, SSLValidationError +from ansible_collections.dellemc.openmanage.plugins.modules import idrac_reset +from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule +from mock import MagicMock + +MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.idrac_reset.' +MODULE_UTILS_PATH = 'ansible_collections.dellemc.openmanage.plugins.module_utils.utils.' + +MANAGERS_URI = "/redfish/v1/Managers" +OEM = "Oem" +MANUFACTURER = "Dell" +ACTIONS = "Actions" +IDRAC_RESET_RETRIES = 50 +LC_STATUS_CHECK_SLEEP = 30 +IDRAC_URI = "/redfish/v1/Managers/iDRAC.Embedded.1" +IDRAC_JOB_URI = "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/{job_id}" +RESET_TO_DEFAULT_ERROR = "{reset_to_default} is not supported. The supported values are {supported_values}. Enter the valid values and retry the operation." +RESET_TO_DEFAULT_ERROR_MSG = "{reset_to_default} is not supported." +CUSTOM_ERROR = "{reset_to_default} is not supported on this firmware version of iDRAC. The supported values are {supported_values}. \ +Enter the valid values and retry the operation." +IDRAC_RESET_RESTART_SUCCESS_MSG = "iDRAC restart operation completed successfully." +IDRAC_RESET_SUCCESS_MSG = "Successfully performed iDRAC reset." +IDRAC_RESET_RESET_TRIGGER_MSG = "iDRAC reset operation triggered successfully." +IDRAC_RESET_RESTART_TRIGGER_MSG = "iDRAC restart operation triggered successfully." +INVALID_DIRECTORY_MSG = "Provided directory path '{path}' is invalid." +FAILED_RESET_MSG = "Failed to perform the reset operation." +RESET_UNTRACK = "iDRAC reset is in progress. Changes will apply once the iDRAC reset operation is successfully completed." +TIMEOUT_NEGATIVE_OR_ZERO_MSG = "The value of `job_wait_timeout` parameter cannot be negative or zero. Enter the valid value and retry the operation." +INVALID_FILE_MSG = "File extension is invalid. Supported extension for 'custom_default_file' is: .xml." +LC_STATUS_MSG = "Lifecycle controller status check is {lc_status} after {retries} number of retries, Exiting.." +INSUFFICIENT_DIRECTORY_PERMISSION_MSG = "Provided directory path '{path}' is not writable. Please check if the directory has appropriate permissions." +UNSUPPORTED_LC_STATUS_MSG = "Lifecycle controller status check is not supported." +CHANGES_NOT_FOUND = "No changes found to commit!" +CHANGES_FOUND = "Changes found to commit!" +MINIMUM_SUPPORTED_FIRMWARE_VERSION = "7.00.00" +SUCCESS_STATUS = "Success" +FAILED_STATUS = "Failed" +STATUS_SUCCESS = [200, 202, 204] +ERR_STATUS_CODE = [400, 404] +RESET_KEY = "Oem.#DellManager.ResetToDefaults" +RESTART_KEY = "#Manager.Reset" +GET_BASE_URI_KEY = "Validation.get_base_uri" +INVOKE_REQ_KEY = "iDRACRedfishAPI.invoke_request" +GET_CUSTOM_DEFAULT_KEY = "CustomDefaultsDownloadURI" +SET_CUSTOM_DEFAULT_KEY = "#DellManager.SetCustomDefaults" +CHECK_LC_STATUS = "FactoryReset.check_lcstatus" +RESET_ALLOWABLE_KEY = "ResetType@Redfish.AllowableValues" +VALIDATE_RESET_OPTION_KEY = "Validation.validate_reset_options" +FILE_PATH = "/root/custom_default_content.xml" +CHECK_IDRAC_VERSION = "FactoryReset.is_check_idrac_latest" +EXECUTE_KEY = "FactoryReset.execute" +HTTP_ERROR_MSG = "http error message" +RETURN_TYPE = "application/json" +FILE_PATH = "abc/test" + + +class TestValidation(FakeAnsibleModule): + module = idrac_reset + allowed_values = ["All", "Default", "CustomDefaults", "ResetAllWithRootDefaults"] + allowed_values_api = { + 'Actions': + { + "#Manager.Reset": { + "ResetType@Redfish.AllowableValues": [ + "Test" + ] + }, + "Oem": { + "#DellManager.ResetToDefaults": { + RESET_ALLOWABLE_KEY: [ + "All", + "Default", + "ResetAllWithRootDefaults" + ] + } + } + }, + "Oem": { + "Dell": { + "CustomDefaultsDownloadURI": "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/CustomDefaultsDownloadURI" + } + } + } + + @pytest.fixture + def idrac_reset_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_reset_mock(self, mocker, idrac_reset_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_reset_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_reset_mock + return idrac_conn_mock -from pytest import importorskip + def test_get_base_uri(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - when validate_and_get_first_resource_id_uri return proper uri + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(IDRAC_URI, '')) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + data = idr_obj.get_base_uri() + assert data == IDRAC_URI -importorskip("omsdk.sdkfile") -importorskip("omsdk.sdkcreds") + def test_validate_reset_options(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - when key 'OEM' doesn't exist in output from invoke_request + obj = MagicMock() + obj.json_data = {'Actions': {}} + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idrac_default_args.update({"reset_to_default": 'All'}) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + allowed_values, res = idr_obj.validate_reset_options(RESET_KEY) + assert res is False + + # Scenario - when reset_to_default is not in allowable values + obj.json_data = self.allowed_values_api + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idrac_default_args.update({"reset_to_default": 'CustomDefaults'}) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + allowed_values, res = idr_obj.validate_reset_options(RESET_KEY) + assert res is False + + def test_validate_graceful_restart_option(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - when key doesn't exist in output from invoke_request + obj = MagicMock() + obj.json_data = {'Actions': {}} + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + res = idr_obj.validate_graceful_restart_option(RESTART_KEY) + assert res is False -MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.' + # Scenario - when 'GracefulRestart is not in allowable values + obj.json_data = self.allowed_values_api + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + res = idr_obj.validate_graceful_restart_option(RESTART_KEY) + assert res is False + def test_validate_path(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - when custom default file path doesn't exist + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + 'os.path.exists', return_value=False) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_path(FILE_PATH) + assert exc.value.args[0] == INVALID_DIRECTORY_MSG.format(path=FILE_PATH) -@pytest.fixture -def idrac_reset_connection_mock(mocker, idrac_mock): - idrac_connection_class_mock = mocker.patch(MODULE_PATH + 'idrac_reset.iDRACConnection') - idrac_connection_class_mock.return_value.__enter__.return_value = idrac_mock - return idrac_mock + # Scenario - when custom default file path exist but not accessible + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + 'os.path.exists', return_value=True) + mocker.patch(MODULE_PATH + 'os.access', return_value=False) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_path(FILE_PATH) + assert exc.value.args[0] == INSUFFICIENT_DIRECTORY_PERMISSION_MSG.format(path=FILE_PATH) + def test_validate_file_format(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - when custom default file is not in XML format + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_file_format('abc/test.json') + assert exc.value.args[0] == INVALID_FILE_MSG -class TestReset(FakeAnsibleModule): + def test_validate_custom_option_exception_case(self, idrac_default_args, idrac_connection_reset_mock, mocker): + obj = MagicMock() + obj.json_data = self.allowed_values_api + json_str = to_text(json.dumps({"data": "out"})) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + 'get_dynamic_uri', return_value=obj) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, side_effect=HTTPError("https://test.com", 404, HTTP_ERROR_MSG, + {"accept-type": RETURN_TYPE}, + StringIO(json_str))) + idrac_default_args.update({"reset_to_default": 'CustomDefaults'}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation( + idrac_connection_reset_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_custom_option('CustomDefaults', self.allowed_values) + assert exc.value.args[0] == RESET_TO_DEFAULT_ERROR.format(reset_to_default='CustomDefaults', supported_values=self.allowed_values) + + def test_validate_job_wait_negative_values(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - when job_wait_timeout is negative + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, + return_value=IDRAC_URI) + idrac_default_args.update({"wait_for_idrac": True, "job_wait_timeout": -120}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation(idrac_connection_reset_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_job_timeout() + assert exc.value.args[0] == TIMEOUT_NEGATIVE_OR_ZERO_MSG + + # Scenario - when job_wait_timeout is positive + idrac_default_args.update({"job_wait": True, "job_wait_timeout": 120}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.Validation(idrac_connection_reset_mock, f_module) + idr_obj.validate_job_timeout() + + +class TestFactoryReset(FakeAnsibleModule): module = idrac_reset + lc_status_api_links = { + "Oem": { + "Dell": { + "DellLCService": { + "@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLCService" + } + } + } + } + + action_api_resp = { + "Actions": { + "#DellLCService.GetRemoteServicesAPIStatus": { + "target": "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLCService/Actions/DellLCService.GetRemoteServicesAPIStatus" + } + } + } + + action_api_resp_restart = { + RESTART_KEY: { + RESET_ALLOWABLE_KEY: [ + "GracefulRestart" + ], + "target": "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.Reset" + } + } + + lc_status_invoke = { + "LCStatus": "Ready" + } + lc_status_invoke_not_ready = { + "LCStatus": "Not Initialized" + } + + validate_allowed_values = { + "Actions": { + RESTART_KEY: { + RESET_ALLOWABLE_KEY: [ + "GracefulRestart" + ], + "target": "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.Reset" + }, + "#Manager.ResetToDefaults": { + RESET_ALLOWABLE_KEY: [ + "ResetAll", + "PreserveNetworkAndUsers" + ], + "target": "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.ResetToDefaults" + }, + "Oem": { + "#DellManager.ResetToDefaults": { + RESET_ALLOWABLE_KEY: [ + "All", + "CustomDefaults", + "Default", + "ResetAllWithRootDefaults" + ], + "target": "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/DellManager.ResetToDefaults" + }, + "#DellManager.SetCustomDefaults": { + "target": "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/DellManager.SetCustomDefaults" + }, + } + }, + "Oem": { + "Dell": { + "CustomDefaultsDownloadURI": "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/CustomDefaultsDownloadURI" + } + } + } + + custom_default_content = "\n\n \ + Disabled\n \n\n" @pytest.fixture - def idrac_mock(self, mocker): - omsdk_mock = MagicMock() + def idrac_reset_mock(self): idrac_obj = MagicMock() - omsdk_mock.config_mgr = idrac_obj - type(idrac_obj).reset_idrac = Mock(return_value="idracreset") return idrac_obj @pytest.fixture - def idrac_config_mngr_reset_mock(self, mocker): - try: - config_manager_obj = mocker.patch(MODULE_PATH + 'idrac_reset.config_mgr') - except AttributeError: - config_manager_obj = MagicMock() + def idrac_connection_reset_mock(self, mocker, idrac_reset_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_reset_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_reset_mock + return idrac_conn_mock + + def test_is_check_idrac_latest(self, idrac_default_args, idrac_connection_reset_mock, mocker): + allowed_values = ["All", "Default", "ResetAllWithRootDefaults"] + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + res = reset_obj.is_check_idrac_latest() + assert res is True + + def test_check_mode_output(self, idrac_default_args, idrac_connection_reset_mock, mocker): + # Scenario - When Reset to default is not passed and check mode is true + allowed_values = ["All", "Default", "ResetAllWithRootDefaults"] + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + "Validation.validate_graceful_restart_option", return_value=False) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.check_mode_output(True) + assert exc.value.args[0] == CHANGES_NOT_FOUND + + def test_execute(self, idrac_default_args, idrac_connection_reset_mock, mocker): + allowed_values = ["All", "Default", "ResetAllWithRootDefaults", "CustomDefaults"] + allowed_values_without_cd = ["All", "Default", "ResetAllWithRootDefaults"] + # Scenario - When 'GracefulRestart' is not supported and iDRAC8 and check_mode True + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="2.81.81") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + "Validation.validate_graceful_restart_option", return_value=False) + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=False) + idrac_default_args.update({}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.execute() + assert exc.value.args[0] == CHANGES_NOT_FOUND + + # Scenario: when success message is returned for graceful restart for IDRAC8 or IDRAC9 obj = MagicMock() - config_manager_obj.config_mgr.return_value = obj - config_manager_obj.config_mgr.reset_idrac().return_value = obj - return config_manager_obj - - def test_main_idrac_reset_success_case01(self, idrac_reset_connection_mock, idrac_default_args, mocker): - mocker.patch(MODULE_PATH + "idrac_reset.run_idrac_reset", - return_value=({"Status": "Success"}, False)) - idrac_reset_connection_mock.config_mgr.reset_idrac.return_value = {"Status": "Success"} - idrac_reset_connection_mock.config_mgr.reset_idrac.return_value = "Success" - result = self._run_module(idrac_default_args) - assert result == {'msg': 'Successfully performed iDRAC reset.', - 'reset_status': ({'Status': 'Success'}, False), 'changed': False} + obj.status_code = 204 + obj.json_data = self.lc_status_invoke + + def mock_get_dynamic_uri_request(*args, **kwargs): + if len(args) > 2 and args[2] == 'Links': + return self.lc_status_api_links + elif len(args) > 2 and args[2] == 'Actions': + return self.action_api_resp_restart + return self.action_api_resp + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=True) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, return_value=obj) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=mock_get_dynamic_uri_request) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + msg_resp, resp = reset_obj.execute() + assert msg_resp['msg'] == IDRAC_RESET_SUCCESS_MSG + + # Scenario: when success message reset_to_default is passed as 'Default' for idrac9 with job_wait set to True + obj.status_code = 200 + obj2 = MagicMock() + obj3 = MagicMock() + obj2.json_data = self.validate_allowed_values + obj3.json_data = self.lc_status_invoke_not_ready + + def mock_get_dynamic_uri_request(*args, **kwargs): + if len(args) > 2 and args[2] == 'Links': + return self.lc_status_api_links + elif len(args) > 2 and args[2] == 'Actions': + return self.validate_allowed_values + return self.action_api_resp + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=True) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, side_effect=[obj, obj2, obj, URLError('URL error occurred'), obj, URLError('URL error occurred'), obj3, obj]) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=mock_get_dynamic_uri_request) + idrac_default_args.update({"reset_to_default": "Default"}) + idrac_default_args.update({"wait_for_idrac": True}) + idrac_default_args.update({"job_wait_timeout": 300}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + msg_resp, resp = reset_obj.execute() + assert msg_resp['msg'] == IDRAC_RESET_SUCCESS_MSG + + # Scenario: when success message reset_to_default is passed as 'CustomDefaults' with custom_default_buffer + obj4 = MagicMock() + obj4.json_data = {'LCStatus': 'NOTINITIALIZED'} + obj2.headers = {'Location': "/joburl/JID12345"} + obj2.status_code = 200 + # allowed_values.append("CustomDefaults") + job_resp_completed = {'JobStatus': 'Completed'} + idrac_redfish_resp = (False, 'Job Success', job_resp_completed, 1200) + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=True) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, side_effect=[(allowed_values, True), (allowed_values, True)]) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, side_effect=[obj, obj2, obj, obj2]) + mocker.patch(MODULE_PATH + 'idrac_redfish_job_tracking', return_value=idrac_redfish_resp) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=[self.lc_status_api_links, self.action_api_resp_restart, + self.validate_allowed_values, self.validate_allowed_values, + self.validate_allowed_values, self.lc_status_api_links, self.action_api_resp_restart]) + idrac_default_args.update({"reset_to_default": "CustomDefaults", "custom_defaults_buffer": self.custom_default_content}) + idrac_default_args.update({"wait_for_idrac": False}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + msg_resp, resp = reset_obj.execute() + assert msg_resp['msg'] == IDRAC_RESET_RESET_TRIGGER_MSG - def test_run_idrac_reset_success_case01(self, idrac_reset_connection_mock, idrac_default_args): - f_module = self.get_module_mock(params=idrac_default_args) - result = self.module.run_idrac_reset(idrac_reset_connection_mock, f_module) - assert result == idrac_reset_connection_mock.config_mgr.reset_idrac() + # Scenario - When reset_to_default is passed and iDRAC8 and check_mode True + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="2.81.81") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=False) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, return_value=(None, False)) + idrac_default_args.update({"reset_to_default": "CustomDefaults"}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.execute() + assert exc.value.args[0] == CHANGES_NOT_FOUND + + # Scenario - When reset_to_default is passed and iDRAC8 and check_mode False + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="2.81.81") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, return_value=(None, False)) + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=False) + idrac_default_args.update({"reset_to_default": "CustomDefaults"}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.execute() + assert exc.value.args[0] == RESET_TO_DEFAULT_ERROR_MSG.format(reset_to_default='CustomDefaults') + + # Scenario - When reset_to_default is CustomDefaults and iDRAC9 firmware version not supported + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="6.99.99") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, return_value=(allowed_values_without_cd, True)) + mocker.patch(MODULE_PATH + CHECK_LC_STATUS, return_value=None) + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=False) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.execute() + assert exc.value.args[0] == CUSTOM_ERROR.format(reset_to_default="CustomDefaults", + supported_values=allowed_values_without_cd) + + # Scenario - When reset_to_default is passed and iDRAC9 and check_mode True + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.60") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, return_value=(allowed_values, False)) + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=True) + idrac_default_args.update({"reset_to_default": "CustomDefaults", "custom_defaults_buffer": self.custom_default_content}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.execute() + assert exc.value.args[0] == CHANGES_FOUND - def test_run_idrac_reset_status_success_case02(self, idrac_reset_connection_mock, idrac_default_args): + # Scenario - When reset_to_default is passed and iDRAC9 with firmware version not supported and check_mode True + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="6.81.81") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=True) + idrac_default_args.update({"reset_to_default": "CustomDefaults", "custom_defaults_buffer": self.custom_default_content}) f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) - result = self.module.run_idrac_reset(idrac_reset_connection_mock, f_module) - assert result == {'Message': 'Changes found to commit!', 'Status': 'Success', 'changes_applicable': True} + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + with pytest.raises(Exception) as exc: + reset_obj.execute() + assert exc.value.args[0] == CHANGES_NOT_FOUND - @pytest.mark.parametrize("exc_type", [SSLValidationError, URLError, ValueError, TypeError, - ConnectionError, HTTPError]) - def test_main_exception_handling_case(self, exc_type, mocker, idrac_reset_connection_mock, idrac_default_args): + # Scenario - When reset_to_default is 'CustomDefaults' and iDRAC9 and custom_defaults_file is passed json_str = to_text(json.dumps({"data": "out"})) - if exc_type not in [HTTPError, SSLValidationError]: - mocker.patch(MODULE_PATH + 'idrac_reset.run_idrac_reset', side_effect=exc_type('test')) + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + CHECK_IDRAC_VERSION, return_value=True) + mocker.patch(MODULE_PATH + CHECK_LC_STATUS, return_value=None) + mocker.patch(MODULE_PATH + "Validation.validate_path", return_value=None) + mocker.patch(MODULE_PATH + "Validation.validate_file_format", return_value=None) + mocker.patch(MODULE_PATH + "Validation.validate_custom_option", return_value=None) + mocker.patch(MODULE_PATH + 'open', mocker.mock_open(read_data=self.custom_default_content)) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, side_effect=[(allowed_values, True), (allowed_values, True)]) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, side_effect=[obj2, obj, HTTPError("https://test.com", + 401, HTTP_ERROR_MSG, {"accept-type": RETURN_TYPE}, + StringIO(json_str))]) + mocker.patch(MODULE_PATH + 'idrac_redfish_job_tracking', return_value=idrac_redfish_resp) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=[self.validate_allowed_values, self.validate_allowed_values, + self.validate_allowed_values, self.lc_status_api_links, self.action_api_resp_restart]) + idrac_default_args.update({"reset_to_default": "CustomDefaults", "custom_defaults_file": FILE_PATH}) + idrac_default_args.update({"wait_for_idrac": True}) + idrac_default_args.update({"job_wait_timeout": 300}) + idrac_default_args.update({"force_reset": True}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + msg_resp, resp = reset_obj.execute() + assert msg_resp['msg'] == IDRAC_RESET_SUCCESS_MSG + + # Scenario: Failure - when reset_to_default is passed as 'ResetAllWithRootDefaults' for idrac9 with job_wait set to True + def mock_get_dynamic_uri_request(*args, **kwargs): + if len(args) > 2 and args[2] == 'Links': + return self.lc_status_api_links + elif len(args) > 2 and args[2] == 'Actions': + return self.validate_allowed_values + return self.action_api_resp + obj.status_code = 400 + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + CHECK_LC_STATUS, return_value=None) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + VALIDATE_RESET_OPTION_KEY, return_value=(allowed_values, True)) + mocker.patch(MODULE_PATH + INVOKE_REQ_KEY, side_effect=[obj]) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=mock_get_dynamic_uri_request) + idrac_default_args.update({"reset_to_default": "ResetAllWithRootDefaults"}) + idrac_default_args.update({"wait_for_idrac": False}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + reset_obj = self.module.FactoryReset(idrac_connection_reset_mock, f_module, allowed_choices=allowed_values) + msg_resp, resp = reset_obj.execute() + assert msg_resp['msg'] == FAILED_RESET_MSG + + def test_idrac_reset_main_positive_case(self, idrac_default_args, + idrac_connection_reset_mock, mocker): + # Scenario - When reset_to_default is passed and successful + msg_resp = {'msg': "Success", 'changed': True} + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + EXECUTE_KEY, return_value=(msg_resp, {})) + data = self._run_module(idrac_default_args) + assert data['msg'] == "Success" + + # Scenario - When reset_to_default is passed and Failed + msg_resp = {'msg': "Failure", 'changed': False} + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + EXECUTE_KEY, return_value=(msg_resp, {})) + data = self._run_module(idrac_default_args) + assert data['msg'] == "Failure" and data['failed'] is True + + # Scenario - When reset_to_default is None and successful + msg_resp = {'msg': "Success", 'changed': True} + output = { + "reset_status": { + "idracreset": { + "Data": { + "StatusCode": 204 + }, + "Message": "Success", + "Status": "Success", + "StatusCode": 204, + "retval": True + } + } + } + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + EXECUTE_KEY, return_value=(msg_resp, output)) + data = self._run_module(idrac_default_args) + assert data['msg'] == "Success" and data['reset_status'] == output + + # Scenario - When reset_to_default is None and Failed + output['reset_status']['idracreset']['Message'] = "Failure" + output['reset_status']['idracreset']['Status'] = "Failure" + output['reset_status']['idracreset']['StatusCode'] = 404 + msg_resp = {'msg': "Failure", 'changed': False} + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="7.10.05") + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + mocker.patch(MODULE_PATH + EXECUTE_KEY, return_value=(msg_resp, output)) + data = self._run_module(idrac_default_args) + assert data['msg'] == "Failure" and data['reset_status'] == output + + @pytest.mark.parametrize("exc_type", + [URLError, HTTPError, SSLValidationError, ConnectionError, TypeError, ValueError]) + def test_idrac_reset_main_exception_handling_case(self, exc_type, idrac_default_args, + idrac_connection_reset_mock, mocker): + json_str = to_text(json.dumps({"data": "out"})) + mocker.patch(MODULE_PATH + GET_BASE_URI_KEY, return_value=IDRAC_URI) + if exc_type in [HTTPError, SSLValidationError]: + mocker.patch(MODULE_PATH + EXECUTE_KEY, + side_effect=exc_type('https://testhost.com', 400, + HTTP_ERROR_MSG, + {"accept-type": RETURN_TYPE}, + StringIO(json_str))) else: - mocker.patch(MODULE_PATH + 'idrac_reset.run_idrac_reset', - side_effect=exc_type('https://testhost.com', 400, 'http error message', - {"accept-type": "application/json"}, StringIO(json_str))) - if not exc_type == URLError: - result = self._run_module_with_fail_json(idrac_default_args) - assert result['failed'] is True + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", + side_effect=exc_type('test')) + result = self._run_module(idrac_default_args) + if exc_type == URLError: + assert result['unreachable'] is True else: - result = self._run_module(idrac_default_args) + assert result['failed'] is True assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_session.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_session.py new file mode 100644 index 000000000..a28aab255 --- /dev/null +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_session.py @@ -0,0 +1,590 @@ +# -*- coding: utf-8 -*- + +# +# Dell OpenManage Ansible Modules +# Version 9.2.0 +# Copyright (C) 2024 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) +# + +from __future__ import absolute_import, division, print_function + + +from io import StringIO +import json + + +from urllib.error import HTTPError, URLError +import pytest +from mock import MagicMock +from ansible_collections.dellemc.openmanage.plugins.modules import idrac_session +from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule +from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import AnsibleFailJSonException +from ansible.module_utils.urls import SSLValidationError +from ansible.module_utils._text import to_text + + +MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.idrac_session.' +MODULE_UTILS_PATH = 'ansible_collections.dellemc.openmanage.plugins.module_utils.utils.' + +REDFISH = "/redfish/v1" +SESSIONS = "Sessions" +ODATA = "@odata.id" +ODATA_REGEX = "(.*?)@odata" + +SESSION_URL = "/redfish/v1/SessionService/Sessions" +GET_SESSION_URL = "Session.get_session_url" + +CREATE_SUCCESS_MSG = "The session has been created successfully." +DELETE_SUCCESS_MSG = "The session has been deleted successfully." +FAILURE_MSG = "Unable to '{operation}' a session." +CHANGES_FOUND_MSG = "Changes found to be applied." +NO_CHANGES_FOUND_MSG = "No changes found to be applied." +HTTPS_PATH = "https://testhost.com" +HTTP_ERROR = "http error message" +APPLICATION_JSON = "application/json" + + +class TestSession(FakeAnsibleModule): + """ + Main class for testing the idrac_session module. + """ + module = idrac_session + + @pytest.fixture + def idrac_session_mock(self): + """ + Creates a mock object for the `idrac_session` fixture. + + This function uses the `MagicMock` class from the `unittest.mock` module to create a mock + object. The mock object is then returned by the function. + + Returns: + MagicMock: A mock object representing the `idrac_session`. + """ + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_session_mock(self, mocker, idrac_session_mock): + """ + Returns a mock object for the `SessionAPI` class from the `MODULE_PATH` module. + The mock object is initialized with the `idrac_session_mock` as the return value. + The `__enter__` method of the mock object is also mocked to return `idrac_session_mock`. + + :param mocker: The pytest fixture for mocking objects. + :type mocker: pytest_mock.plugin.MockerFixture + :param idrac_session_mock: The mock object for the `idrac_session_mock`. + :type idrac_session_mock: Any + :return: The mock object for the `SessionAPI` class. + :rtype: MagicMock + """ + idrac_conn_mock = mocker.patch(MODULE_PATH + 'SessionAPI', + return_value=idrac_session_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_session_mock + return idrac_conn_mock + + def test_get_session_url(self, idrac_default_args, idrac_connection_session_mock, mocker): + """ + Test the `get_session_url` method of the `Session` class. + + This test function mocks the `get_dynamic_uri` function to return a dictionary + containing the session URL. It then creates a `f_module` object with the + `idrac_default_args` and `check_mode` set to `False`. It initializes a + `session_obj` with the `idrac_connection_session_mock` and `f_module`. + Finally, it calls the `get_session_url` method on the `session_obj` and + asserts that the returned session URL is equal to the `SESSION_URL` constant. + + Args: + self (TestGetSessionUrl): The test case object. + idrac_default_args (dict): The default arguments for the IDRAC connection. + idrac_connection_session_mock (MagicMock): The mock object for the IDRAC + connection session. + mocker (MagicMock): The mocker object for mocking functions and modules. + + Returns: + None + """ + v1_resp = {'Links': {'Sessions': {'@odata.id': SESSION_URL}}} + mocker.patch(MODULE_PATH + "get_dynamic_uri", + return_value=v1_resp) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + session_obj = self.module.Session( + idrac_connection_session_mock, f_module) + sessions_url = session_obj.get_session_url() + assert sessions_url == SESSION_URL + + +class TestCreateSession(FakeAnsibleModule): + """ + Main class for testing the create_session module. + """ + module = idrac_session + + @pytest.fixture + def create_session_mock(self): + """ + Creates a mock object for the `idrac_session` fixture. + + This function is a pytest fixture that creates a mock object of type `MagicMock` and + assigns it to the variable `idrac_obj`. The `idrac_obj` mock object is then returned + by the fixture. + + Returns: + MagicMock: A mock object representing the `idrac_session` fixture. + """ + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_session_mock(self, mocker, create_session_mock): + """ + Creates a fixture for mocking the IDRAC connection session. + + This fixture uses the `mocker` fixture from the `pytest` library to patch the + `SessionAPI` class from the `MODULE_PATH` module. It returns a mock object of the + `SessionAPI` class with the `create_session_mock` object as the return value. + The `__enter__` method of the mock object is also patched to return the + `create_session_mock` object. + + Parameters: + - `self` (TestCase): The test case instance. + - `mocker` (MockerFixture): The `mocker` fixture from the `pytest` library. + - `create_session_mock` (Mock): The mock object representing the `create_session_mock`. + + Returns: + - `idrac_conn_mock` (MagicMock): The mock object of the `SessionAPI` class. + """ + idrac_conn_mock = mocker.patch(MODULE_PATH + 'SessionAPI', + return_value=create_session_mock) + idrac_conn_mock.return_value.__enter__.return_value = create_session_mock + return idrac_conn_mock + + def test_session_operation(self, idrac_default_args, idrac_connection_session_mock): + """ + Test the session operation of the module. + + Args: + idrac_default_args (dict): The default arguments for the IDRAC connection. + idrac_connection_session_mock (MagicMock): The mock object for the IDRAC + connection session. + + Returns: + None + + This function tests the session operation of the module by creating a session and deleting + a session. + It updates the `idrac_default_args` dictionary with the appropriate state parameter and + creates a `f_module` object with the updated arguments. It then creates a + `session_operation_obj` object using the `CreateSession` class of the module and asserts + that it is an instance of `CreateSession`. + It repeats the same process for deleting a session by updating the `idrac_default_args` + dictionary with the state parameter set to "absent" and creating a `session_operation_obj` + object using the + `DeleteSession` class of the module. It asserts that it is an instance of `DeleteSession`. + """ + idrac_default_args.update({"state": "present"}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + session_operation_obj = self.module.CreateSession(idrac_connection_session_mock, f_module) + assert isinstance(session_operation_obj, self.module.CreateSession) + + idrac_default_args.update({"state": "absent"}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + session_operation_obj = self.module.DeleteSession(idrac_connection_session_mock, f_module) + assert isinstance(session_operation_obj, self.module.DeleteSession) + + def test_create_session_failure(self, idrac_connection_session_mock, mocker): + """ + Test the failure scenario of creating a session. + + Args: + idrac_connection_session_mock (MagicMock): A mock object for the + idrac_connection_session. + mocker (MockerFixture): A fixture for mocking objects. + + Returns: + None + + This test function creates a session object using the `idrac_connection_session_mock` and + `f_module` objects. + It sets the `session_obj.get_session_url` to return a session URL. + It sets the `f_module.check_mode` to False and `f_module.params` to a dictionary containing + the username and password. + It mocks the `idrac_connection_session_mock.invoke_request` method to return a response + with a status code of 201. + It calls the `session_obj.execute()` method to create the session. + It asserts that the `f_module.exit_json` method is called once with the message "Unable to + 'create' a session." and `failed` set to True. + """ + f_module = MagicMock() + session_obj = idrac_session.CreateSession( + idrac_connection_session_mock, f_module) + session_obj.get_session_url = MagicMock(return_value=SESSION_URL) + f_module.check_mode = False + f_module.params = { + "username": "admin", + "password": "password" + } + response_mock = MagicMock() + response_mock.status_code = 201 + mocker.patch.object(idrac_connection_session_mock.return_value, 'invoke_request', + return_value=response_mock) + + session_obj.execute() + f_module.exit_json.assert_called_once_with( + msg="Unable to 'create' a session.", + failed=True + ) + + def test_create_session_check_mode(self, idrac_connection_session_mock): + """ + Test the create session functionality in check mode. + + Args: + idrac_connection_session_mock (MagicMock): A mock object for the IDRAC connection + session. + + Returns: + None + + This function tests the create session functionality in check mode. It creates an instance + of the `CreateSession` class with the provided `idrac_connection_session_mock` and a mock + `f_module` object. + It sets the required parameters for the `f_module` object and mocks the `get_session_url` + method of the `session_obj` to return the session URL. It also mocks the `exit_json` method + of the `f_module` object. + + Finally, it calls the `execute` method of the `session_obj` to execute the create session + functionality in check mode. + + Note: + This function assumes that the necessary imports and setup for the test are already + done. + """ + f_module = MagicMock() + session_obj = idrac_session.CreateSession( + idrac_connection_session_mock, f_module) + f_module = self.get_module_mock( + params={"session_id": "1234", "hostname": "X.X.X.X"}, check_mode=True) + session_obj.get_session_url = MagicMock(return_value=SESSION_URL) + f_module.exit_json = MagicMock() + + session_obj.execute() + + def test_create_session_success(self, idrac_connection_session_mock): + """ + Test the successful creation of a session. + + Args: + idrac_connection_session_mock (MagicMock): A mock object representing the IDRAC + connection session. + + This test case verifies the successful creation of a session by mocking the necessary + objects and invoking the `execute()` method of the `CreateSession` class. It sets the + parameters for the `f_module` object, initializes the `session_obj` with the mocked + `idrac_connection_session_mock` and `f_module`, and mocks the necessary methods and + attributes of the `idrac` object. It then asserts that the `exit_json` method of the + `f_module` object is called with the expected arguments. + + Returns: + None + """ + f_module = self.get_module_mock( + params={"username": "admin", "password": "password"}, check_mode=False) + session_obj = idrac_session.CreateSession(idrac_connection_session_mock, f_module) + session_obj.get_session_url = MagicMock(return_value=SESSION_URL) + session_obj.idrac.invoke_request.return_value.status_code = 201 + session_obj.idrac.invoke_request.return_value.json_data = {"SessionID": "123456"} + session_obj.idrac.invoke_request.return_value.headers.get.return_value = "token123" + f_module.exit_json = MagicMock() + + session_obj.execute() + f_module.exit_json.assert_called_once_with( + msg=CREATE_SUCCESS_MSG, + changed=True, + session_data={"SessionID": "123456"}, + x_auth_token="token123" + ) + + +class TestDeleteSession(FakeAnsibleModule): + """ + Main class for testing the delete session module. + """ + module = idrac_session + + @pytest.fixture + def idrac_session_mock(self): + """ + Creates a mock object for the `idrac_session` fixture. + + This function uses the `MagicMock` class from the `unittest.mock` module to create a mock + object. + The mock object is then returned by the function. + + Returns: + MagicMock: A mock object representing the `idrac_session`. + """ + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_session_mock(self, mocker, idrac_session_mock): + """ + Returns a mocked instance of the SessionAPI class from the specified module path. + The mocked instance is created using the `mocker.patch` function. The `idrac_session_mock` + parameter is passed as the return value of the mocked instance. The `__enter__` method + of the mocked instance is also mocked to return the `idrac_session_mock`. + :param mocker: The mocker fixture provided by the pytest framework. + :type mocker: _pytest.monkeypatch.MonkeyPatch + :param idrac_session_mock: The mocked instance of the idrac session. + :type idrac_session_mock: Any + :return: The mocked instance of the SessionAPI class. + :rtype: MagicMock + """ + idrac_conn_mock = mocker.patch(MODULE_PATH + 'SessionAPI', + return_value=idrac_session_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_session_mock + return idrac_conn_mock + + def test_delete_session_success_check_mode_changes(self, idrac_connection_session_mock): + """ + Test the `delete_session_success_check_mode_changes` method of the `DeleteSession` class. + + This method is responsible for testing the success case when the `delete_session` method + is called in check mode. + It verifies that the `exit_json` method of the `f_module` object is called with the + appropriate arguments when the session is successfully deleted. + + Parameters: + - idrac_connection_session_mock (MagicMock): A mock object representing the + `idrac_connection_session` object. + + Returns: + None + """ + f_module = MagicMock() + delete_session_obj = idrac_session.DeleteSession(idrac_connection_session_mock, f_module) + delete_session_obj.idrac.invoke_request.return_value.status_code = 200 + delete_session_obj.execute() + f_module.exit_json.assert_called_once_with(msg=CHANGES_FOUND_MSG, changed=True) + + def test_delete_session_success_check_mode_no_changes(self, idrac_connection_session_mock): + """ + Test the success case of deleting a session in check mode when no changes are expected. + + Args: + idrac_connection_session_mock (MagicMock): A mock object representing the IDRAC + connection session. + + This function tests the scenario where the deletion of a session is successful in check + mode and no changes are expected. It sets up the necessary mock objects and asserts that + the `exit_json` method of the `f_module` object is called once with the `msg` parameter + set to `NO_CHANGES_FOUND_MSG`. + + Returns: + None + """ + f_module = MagicMock() + delete_session_obj = idrac_session.DeleteSession(idrac_connection_session_mock, f_module) + delete_session_obj.idrac.invoke_request.return_value.status_code = 201 + delete_session_obj.execute() + f_module.exit_json.assert_called_once_with(msg=NO_CHANGES_FOUND_MSG) + + def test_delete_session_success(self, idrac_connection_session_mock): + """ + Test the successful deletion of a session. + + This test function verifies the behavior of the `DeleteSession` class when a session is + successfully deleted. It mocks the `idrac_connection_session_mock` object and sets up the + necessary parameters for the `f_module` object. It then creates an instance of the + `DeleteSession` class with the mocked `idrac_connection_session_mock` and the + `f_module` object. + + The `get_session_url` method of the `session_obj` is mocked to return a specific session + URL. The `invoke_request` method of the `idrac` object of the `session_obj` is also mocked + to return a response with a status code of 200. The `exit_json` method of the `f_module` + object is mocked as well. + + The `execute` method of the `session_obj` is called to execute the deletion of the session. + Finally, the `exit_json` method of the `f_module` object is asserted to have been called + with the expected arguments, including the success message and the changed flag set to + `True`. + + Parameters: + - idrac_connection_session_mock (MagicMock): A mocked object representing the + `idrac_connection_session_mock` object. + + Returns: + None + """ + f_module = self.get_module_mock( + params={"session_id": "1234", "hostname": "X.X.X.X"}, check_mode=False) + session_obj = idrac_session.DeleteSession(idrac_connection_session_mock, f_module) + session_obj.get_session_url = MagicMock(return_value=SESSION_URL) + session_obj.idrac.invoke_request.return_value.status_code = 200 + f_module.exit_json = MagicMock() + session_obj.execute() + f_module.exit_json.assert_called_once_with(msg=DELETE_SUCCESS_MSG, changed=True) + + def test_delete_session_check_mode_false_no_changes(self, idrac_connection_session_mock): + """ + Test the scenario where the delete session is executed in check mode with `check_mode` set + to False and no changes are expected. + + Args: + idrac_connection_session_mock (MagicMock): A mock object representing the IDRAC + connection session. + + Returns: + None + + This function creates a mock module object with the specified parameters and + initializes the `DeleteSession` object with the mock IDRAC connection and module. It sets + the `get_session_url` method of the session object to return a specific session URL. It + sets the status code of the invoke request to 201. It then asserts that the `exit_json` + method of the module object is called once with the `msg` parameter set to the + `NO_CHANGES_FOUND_MSG` constant. + """ + f_module = self.get_module_mock( + params={"session_id": "1234", "hostname": "X.X.X.X"}, check_mode=False) + session_obj = idrac_session.DeleteSession(idrac_connection_session_mock, f_module) + session_obj.get_session_url = MagicMock(return_value=SESSION_URL) + session_obj.idrac.invoke_request.return_value.status_code = 201 + f_module.exit_json = MagicMock() + session_obj.execute() + f_module.exit_json.assert_called_once_with(msg=NO_CHANGES_FOUND_MSG) + + def test_delete_session_http_error(self, idrac_connection_session_mock): + """ + Test the behavior of the `DeleteSession` class when an HTTP error occurs during the + deletion of a session. + + This test case creates a mock `f_module` object with the necessary parameters and + initializes a `DeleteSession` object with the mock `idrac_connection_session_mock` and the + `f_module` object. It then sets up the necessary mock functions and side effects to + simulate an HTTP error during the deletion of a session. Finally, it executes the + `execute()` method of the `DeleteSession` object and asserts that an + `AnsibleFailJSonException` is raised with the expected failure message and error + information. + + Parameters: + - idrac_connection_session_mock (MagicMock): A mock object representing the + `idrac_connection_session_mock` parameter. + + Raises: + - AssertionError: If the expected failure message or error information is not present + in the raised exception. + + Returns: + None + """ + f_module = self.get_module_mock( + params={"session_id": "1234", "hostname": "X.X.X.X"}, check_mode=False) + session_obj = idrac_session.DeleteSession(idrac_connection_session_mock, f_module) + session_obj.get_session_url = MagicMock(return_value=SESSION_URL) + session_obj.get_session_status = MagicMock(return_value=200) + json_str = to_text(json.dumps({"data": "out"})) + session_obj.idrac.invoke_request.side_effect = HTTPError(HTTPS_PATH, 200, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str)) + try: + session_obj.execute() + except AnsibleFailJSonException as ex: + assert ex.fail_msg == "Unable to 'delete' a session." + assert ex.fail_kwargs == {'error_info': {'data': 'out'}, 'failed': True} + + +class TestMain(FakeAnsibleModule): + """ + Class for testing the main. + """ + module = idrac_session + + @pytest.fixture + def idrac_session_mock(self): + """ + Creates a mock object for the `idrac_session` fixture. + + This function uses the `MagicMock` class from the `unittest.mock` module to create a mock + object. + The mock object is then returned by the function. + + Returns: + MagicMock: A mock object representing the `idrac_session`. + """ + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_session_mock(self, mocker, idrac_session_mock): + """ + Returns a mock object for the `SessionAPI` class from the `MODULE_PATH` module. + The mock object is initialized with the `idrac_session_mock` as the return value. + The `__enter__` method of the mock object is also mocked to return `idrac_session_mock`. + + :param mocker: The pytest fixture for mocking objects. + :type mocker: pytest_mock.plugin.MockerFixture + :param idrac_session_mock: The mock object for the `idrac_session_mock`. + :type idrac_session_mock: Any + :return: The mock object for the `SessionAPI` class. + :rtype: MagicMock + """ + idrac_conn_mock = mocker.patch(MODULE_PATH + 'SessionAPI', + return_value=idrac_session_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_session_mock + return idrac_conn_mock + + @pytest.mark.parametrize("exc_type", + [URLError, HTTPError, SSLValidationError, ConnectionError, + TypeError, ValueError]) + def test_idrac_session_main_exception_handling_case(self, exc_type, ome_default_args, mocker): + """ + Test the exception handling of the `idrac_session_main` module. + + This function tests the exception handling of the `idrac_session_main` module by mocking + different exceptions and verifying the expected behavior. + + Parameters: + - exc_type (Exception): The type of exception to be raised. + - ome_default_args (dict): The default arguments for the module. + - mocker (MockerFixture): The mocker fixture for mocking functions. + + Returns: + None + + Raises: + AssertionError: If the expected result does not match the actual result. + + Notes: + - The function uses the `pytest.mark.parametrize` decorator to parameterize the test + cases. + - The `exc_type` parameter represents the type of exception to be raised. + - The `ome_default_args` parameter contains the default arguments for the module. + - The `mocker` parameter is used to mock functions and simulate different exceptions. + - The function calls the `_run_module` method with the `ome_default_args` to execute + the module. + - The function verifies the expected result based on the raised exception type. + + """ + json_str = to_text(json.dumps({"data": "out"})) + if exc_type in [HTTPError, SSLValidationError]: + mocker.patch(MODULE_PATH + "CreateSession.get_session_url", + side_effect=exc_type(HTTPS_PATH, 400, + HTTP_ERROR, + {"accept-type": APPLICATION_JSON}, + StringIO(json_str))) + else: + ome_default_args.update({"state": "absent", "session_id": "1234", + "auth_token": "token123"}) + mocker.patch(MODULE_PATH + "DeleteSession.get_session_url", + side_effect=exc_type('test')) + result = self._run_module(ome_default_args) + if exc_type == URLError: + assert result['unreachable'] is True + else: + assert result['failed'] is True + assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_storage_volume.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_storage_volume.py new file mode 100644 index 000000000..3cdf742d2 --- /dev/null +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_storage_volume.py @@ -0,0 +1,1178 @@ +# -*- coding: utf-8 -*- + +# +# Dell OpenManage Ansible Modules +# Version 9.0.0 +# Copyright (C) 2024 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) +# + +from __future__ import absolute_import, division, print_function + +import json +import pytest +from io import StringIO +from ansible.module_utils._text import to_text +from urllib.error import HTTPError, URLError +from ansible.module_utils.urls import ConnectionError, SSLValidationError +from ansible_collections.dellemc.openmanage.plugins.modules import idrac_storage_volume +from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule +from mock import MagicMock +from copy import deepcopy + +MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.idrac_storage_volume.' +MODULE_UTILS_PATH = 'ansible_collections.dellemc.openmanage.plugins.module_utils.utils.' + +SYSTEMS_URI = "/redfish/v1/Systems" +iDRAC_JOB_URI = "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/{job_id}" +CONTROLLER_NOT_EXIST_ERROR = "Specified Controller {controller_id} does not exist in the System." +CONTROLLER_NOT_DEFINED = "Controller ID is required." +SUCCESSFUL_OPERATION_MSG = "Successfully completed the {operation} storage volume operation." +DRIVES_NOT_EXIST_ERROR = "No Drive(s) are attached to the specified Controller Id: {controller_id}." +DRIVES_NOT_MATCHED = "Following Drive(s) {specified_drives} are not attached to the specified Controller Id: {controller_id}." +NEGATIVE_OR_ZERO_MSG = "The value for the `{parameter}` parameter cannot be negative or zero." +NEGATIVE_MSG = "The value for the `{parameter}` parameter cannot be negative." +INVALID_VALUE_MSG = "The value for the `{parameter}` parameter is invalid." +ID_AND_LOCATION_BOTH_DEFINED = "Either id or location is allowed." +ID_AND_LOCATION_BOTH_NOT_DEFINED = "Either id or location should be specified." +DRIVES_NOT_DEFINED = "Drives must be defined for volume creation." +NOT_ENOUGH_DRIVES = "Number of sufficient disks not found in Controller '{controller_id}'!" +WAIT_TIMEOUT_MSG = "The job is not complete after {0} seconds." +JOB_TRIGERRED = "Successfully triggered the {0} storage volume operation." +VOLUME_NAME_REQUIRED_FOR_DELETE = "Virtual disk name is a required parameter for remove virtual disk operations." +VOLUME_NOT_FOUND = "Unable to find the virtual disk." +CHANGES_NOT_FOUND = "No changes found to commit!" +CHANGES_FOUND = "Changes found to commit!" +ODATA_ID = "@odata.id" +ODATA_REGEX = "(.*?)@odata" +ATTRIBUTE = "" +VIEW_OPERATION_FAILED = "Failed to fetch storage details." +VIEW_CONTROLLER_DETAILS_NOT_FOUND = "Failed to find the controller {controller_id}." +VIEW_OPERATION_CONTROLLER_NOT_SPECIFIED = "Controller identifier parameter is missing." +VIEW_VIRTUAL_DISK_DETAILS_NOT_FOUND = "Failed to find the volume : {volume_id} in controller : {controller_id}." +SUCCESS_STATUS = "Success" +FAILED_STATUS = "Failed" +CONTROLLER_BATTERY = "Battery.Integrated.1:RAID.SL.5-1" +CONTROLLER_ID_FIRST = "AHCI.Embedded.1-1" +CONTROLLER_ID_SECOND = "AHCI.Embedded.1-2" +CONTROLLER_ID_THIRD = "AHCI.Embedded.1-3" +CONTROLLER_ID_FOURTH = "RAID.SL.5-1" +CONTROLLER_ID_FIFTH = "RAID.SL.5-3" +SYSTEM = 'System.Embedded.1' +ENCLOSURE_ID = 'Enclosure.Internal.0-1:RAID.SL.5-1' +PHYSICAL_DISK_FIRST = 'Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.5-1' +PHYSICAL_DISK_SECOND = 'Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.5-3' +VIRTUAL_DISK_FIRST = 'Disk.Virtual.0:RAID.SL.5-1' +VIRTUAL_DISK_SECOND = 'Disk.Virtual.1:RAID.SL.5-1' +ALL_STORAGE_DATA_METHOD = "StorageData.all_storage_data" +FETCH_STORAGE_DATA_METHOD = "StorageData.fetch_storage_data" +FILTER_DISK = 'StorageCreate.filter_disk' +VIEW_EXECUTE = 'StorageView.execute' +DATA_XML = '' +REDFISH = "/redfish/v1" +API_INVOKE_MOCKER = "iDRACRedfishAPI.invoke_request" +BASE_IDRAC_API = "/redfish/v1/Chassis/System.Embedded.1" + + +class TestStorageData(FakeAnsibleModule): + module = idrac_storage_volume + storage_controllers = { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage" + } + volumes_list = [ + { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-1/Volumes/Disk.Virtual.0:RAID.SL.5-1" + }, + { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-1/Volumes/Disk.Virtual.1:RAID.SL.5-1" + }] + controllers_list = { + "Members": [ + { + "Controllers": { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-1/Controllers" + }, + "Drives": [ + { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-1/Drives/Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.5-1" + } + ], + "Id": CONTROLLER_ID_FOURTH, + "Links": { + "Enclosures": [ + { + ODATA_ID: "/redfish/v1/Chassis/Enclosure.Internal.0-1:RAID.SL.5-1" + } + ] + }, + "Volumes": { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-1/Volumes" + }, + "Oem": { + "Dell": { + "DellControllerBattery": { + "Id": CONTROLLER_BATTERY + }} + } + }, + { + "Drives": [ + { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/CPU.1/Drives/Disk.Bay.23:Enclosure.Internal.0-3" + } + ], + "Drives@odata.count": 1, + "Id": "CPU.1", + "Links": { + "Enclosures": [ + { + ODATA_ID: "/redfish/v1/Chassis/Enclosure.Internal.0-3" + } + ], + }, + "Volumes": { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/CPU.1/Volumes" + } + }, + { + "Controllers": { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/AHCI.Embedded.1-1/Controllers" + }, + "Drives": [], + "Id": CONTROLLER_ID_FIRST, + "Links": { + "Enclosures": [ + { + ODATA_ID: BASE_IDRAC_API + } + ] + }, + "Volumes": { + ODATA_ID: "/redfish/v1/Systems/System.Embedded.1/Storage/AHCI.Embedded.1-1/Volumes" + } + } + ] + } + + storage_data = { + 'Controllers': { + CONTROLLER_ID_FIRST: { + 'Controllers': { + ODATA_ID: '/redfish/v1/Systems/System.Embedded.1/Storage/AHCI.Embedded.1-1/Controllers', + }, + 'Drives': {}, + 'Id': CONTROLLER_ID_FIRST, + 'Links': { + 'Enclosures': { + SYSTEM: BASE_IDRAC_API, + }, + }, + 'Volumes': {}, + "Oem": { + "Dell": { + "CPUAffinity": [] + } + } + }, + CONTROLLER_ID_SECOND: { + 'Controllers': { + ODATA_ID: '/redfish/v1/Systems/System.Embedded.1/Storage/AHCI.Embedded.1-2/Controllers', + }, + 'Drives': { + 'Disk.Bay.0:Enclosure.Internal.0-1:AHCI.Embedded.1-2': '/redfish/v1/\ + Systems/System.Embedded.1/Storage/RAID.SL.5-1/Drives/Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.5-1', + }, + 'Id': CONTROLLER_ID_SECOND, + 'Links': { + 'Enclosures': { + SYSTEM: BASE_IDRAC_API, + }, + }, + 'Volumes': {}, + "Oem": { + "Dell": { + "CPUAffinity": [] + } + } + }, + CONTROLLER_ID_THIRD: { + 'Controllers': { + ODATA_ID: '/redfish/v1/Systems/System.Embedded.1/Storage/AHCI.Embedded.1-2/Controllers', + }, + 'Drives': { + 'Disk.Bay.0:Enclosure.Internal.0-1:AHCI.Embedded.1-3': '/redfish/v1/\ + Systems/System.Embedded.1/Storage/AHCI.Embedded.1-3/Drives/Disk.Bay.0:Enclosure.Internal.0-1:AHCI.Embedded.1-3', + }, + 'Id': CONTROLLER_ID_THIRD, + 'Links': { + 'Enclosures': { + ENCLOSURE_ID: { + "Links": { + "Drives": [] + } + }, + }, + }, + 'Volumes': {}, + "Oem": { + "Dell": { + "CPUAffinity": [] + } + } + }, + CONTROLLER_ID_FOURTH: { + 'Controllers': { + ODATA_ID: '/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-1/Controllers', + }, + 'Drives': { + PHYSICAL_DISK_FIRST: '/redfish/v1/Systems\ + /System.Embedded.1/Storage/RAID.SL.5-1/Drives/Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.5-1', + }, + 'Id': CONTROLLER_ID_FOURTH, + 'Links': { + 'Enclosures': { + ENCLOSURE_ID: {"Links": { + "Drives": [ + { + ODATA_ID: PHYSICAL_DISK_FIRST + } + ]}} + }, + }, + 'Volumes': { + VIRTUAL_DISK_FIRST: { + "Links": { + "Drives": [ + { + ODATA_ID: PHYSICAL_DISK_FIRST + } + ] + }, + }, + VIRTUAL_DISK_SECOND: { + "Links": { + "Drives": [ + { + ODATA_ID: PHYSICAL_DISK_FIRST + } + ] + }, + }, + }, + "Oem": { + "Dell": { + "DellControllerBattery": { + "Id": CONTROLLER_BATTERY + }} + } + } + } + } + + storage_data_expected = { + 'Controller': { + CONTROLLER_ID_FIRST: { + 'ControllerSensor': { + CONTROLLER_ID_FIRST: {}, + }, + }, + CONTROLLER_ID_SECOND: { + 'ControllerSensor': { + CONTROLLER_ID_SECOND: {}, + }, + 'PhysicalDisk': [ + 'Disk.Bay.0:Enclosure.Internal.0-1:AHCI.Embedded.1-2', + ], + }, + CONTROLLER_ID_THIRD: { + 'ControllerSensor': { + CONTROLLER_ID_THIRD: {} + }, + 'Enclosure': { + ENCLOSURE_ID: { + 'EnclosureSensor': { + ENCLOSURE_ID: {}, + }, + }, + }, + }, + CONTROLLER_ID_FOURTH: { + 'ControllerSensor': { + CONTROLLER_ID_FOURTH: { + 'ControllerBattery': [ + 'Battery.Integrated.1:RAID.SL.5-1', + ], + }, + }, + 'Enclosure': { + ENCLOSURE_ID: { + 'EnclosureSensor': { + ENCLOSURE_ID: {}, + }, + 'PhysicalDisk': [ + PHYSICAL_DISK_FIRST, + ], + }, + }, + 'VirtualDisk': { + VIRTUAL_DISK_FIRST: { + 'PhysicalDisk': [ + PHYSICAL_DISK_FIRST, + ], + }, + VIRTUAL_DISK_SECOND: { + 'PhysicalDisk': [ + PHYSICAL_DISK_FIRST, + ], + }, + }, + }, + } + } + + storage_data_idrac8 = { + 'Controllers': { + CONTROLLER_ID_FIFTH: { + 'Controllers': { + ODATA_ID: '/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.5-3/Controllers', + }, + 'Drives': { + PHYSICAL_DISK_SECOND: '/redfish/v1/Systems\ + /System.Embedded.1/Storage/RAID.SL.5-3/Drives/Disk.Bay.0:Enclosure.Internal.0-1:RAID.SL.5-3', + }, + 'Id': CONTROLLER_ID_FIFTH, + 'Links': { + 'Enclosures': { + ENCLOSURE_ID: {"Links": { + "Drives": [ + { + ODATA_ID: PHYSICAL_DISK_SECOND + } + ]}} + }, + }, + 'Volumes': { + 'Disk.Virtual.0:RAID.SL.5-3': { + "Links": { + "Drives": [ + { + ODATA_ID: PHYSICAL_DISK_SECOND + } + ] + }, + }, + 'Disk.Virtual.1:RAID.SL.5-3': { + "Links": { + "Drives": [ + { + ODATA_ID: PHYSICAL_DISK_SECOND + } + ] + }, + }, + }, + "Oem": { + "Dell": { + "DellControllerBattery": { + "Id": "Battery.Integrated.1:RAID.SL.5-3" + }} + } + } + } + } + + storage_data_expected_idrac8 = { + 'Controller': { + CONTROLLER_ID_FIFTH: { + 'ControllerSensor': { + CONTROLLER_ID_FIFTH: {}, + }, + 'Enclosure': { + ENCLOSURE_ID: { + 'EnclosureSensor': { + ENCLOSURE_ID: {}, + }, + 'PhysicalDisk': [ + PHYSICAL_DISK_SECOND, + ], + }, + }, + 'VirtualDisk': { + 'Disk.Virtual.0:RAID.SL.5-3': { + 'PhysicalDisk': [ + PHYSICAL_DISK_SECOND, + ], + }, + 'Disk.Virtual.1:RAID.SL.5-3': { + 'PhysicalDisk': [ + PHYSICAL_DISK_SECOND, + ], + }, + }, + }, + } + } + + @pytest.fixture + def idrac_storage_volume_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_storage_volume_mock(self, mocker, idrac_storage_volume_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_storage_volume_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_storage_volume_mock + return idrac_conn_mock + + def test_fetch_controllers_uri(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + def mock_get_dynamic_uri_request(*args, **kwargs): + return self.storage_controllers + + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(SYSTEM, '')) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=mock_get_dynamic_uri_request) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageData( + idrac_connection_storage_volume_mock, f_module) + data = idr_obj.fetch_controllers_uri() + assert data == self.storage_controllers + + # Scenario 2: for error message + mocker.patch(MODULE_PATH + "validate_and_get_first_resource_id_uri", + return_value=(REDFISH, "Error")) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageData( + idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.fetch_controllers_uri() + assert exc.value.args[0] == "Error" + + def test_fetch_api_data(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + key = "Storage" + obj = MagicMock() + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageData(idrac_connection_storage_volume_mock, f_module) + key_out, uri_data_out = idr_obj.fetch_api_data(self.storage_controllers[ODATA_ID], -1) + assert key == key_out + assert obj == uri_data_out + + def test_all_storage_data(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + def mock_get_dynamic_uri_request(*args, **kwargs): + if len(args) == 3 and args[2] == "Members": + return self.volumes_list + else: + return self.controllers_list + mocker.patch(MODULE_PATH + "StorageData.fetch_controllers_uri", + return_value=self.storage_controllers) + mocker.patch(MODULE_PATH + "get_dynamic_uri", + side_effect=mock_get_dynamic_uri_request) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageData(idrac_connection_storage_volume_mock, f_module) + storage_info = idr_obj.all_storage_data() + assert set(storage_info.keys()) == {'Controllers'} + assert set(storage_info["Controllers"].keys()) == {CONTROLLER_ID_FIRST, CONTROLLER_ID_FOURTH} + + def test_fetch_storage_data(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=self.storage_data) + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", + return_value="3.20.00") + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageData(idrac_connection_storage_volume_mock, f_module) + storage_info = idr_obj.fetch_storage_data() + assert storage_info == self.storage_data_expected + + # Scenario - for idrac 8 + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=self.storage_data_idrac8) + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", + return_value="2.00") + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageData(idrac_connection_storage_volume_mock, f_module) + storage_info = idr_obj.fetch_storage_data() + assert storage_info == self.storage_data_expected_idrac8 + + +class TestStorageView(TestStorageData): + module = idrac_storage_volume + + @pytest.fixture + def idrac_storage_volume_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_storage_volume_mock(self, mocker, idrac_storage_volume_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_storage_volume_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_storage_volume_mock + return idrac_conn_mock + + def test_execute(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data_expected) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageView(idrac_connection_storage_volume_mock, f_module) + out = idr_obj.execute() + assert out == {"Message": TestStorageData.storage_data_expected, "Status": SUCCESS_STATUS} + + # Scenario - When controller_id is passed + data_when_controller_id_passed = deepcopy(TestStorageData.storage_data_expected) + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=data_when_controller_id_passed) + idrac_default_args.update({"controller_id": CONTROLLER_ID_FIRST}) + out = idr_obj.execute() + assert out == {"Message": data_when_controller_id_passed, "Status": SUCCESS_STATUS} + + # Scenario - When invalid controller_id is passed + data_when_invlid_controller_id_passed = deepcopy(TestStorageData.storage_data_expected) + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=data_when_invlid_controller_id_passed) + controller_id = "AHCI.Embedded.1-invalid" + idrac_default_args.update({"controller_id": controller_id}) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == VIEW_OPERATION_FAILED + + # Scenario - When volume_id and invalid controller_id is passed + data_when_invlid_volume_id_passed = deepcopy(TestStorageData.storage_data_expected) + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=data_when_invlid_volume_id_passed) + idrac_default_args.update({"volume_id": VIRTUAL_DISK_FIRST}) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == VIEW_OPERATION_FAILED + # VIEW_CONTROLLER_DETAILS_NOT_FOUND.format(controller_id=controller_id) + + # Scenario - When volume_id and valid controller_id is passed + data_when_controller_id_and_volume_id_passed = deepcopy(TestStorageData.storage_data_expected) + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=data_when_controller_id_and_volume_id_passed) + idrac_default_args.update({"controller_id": CONTROLLER_ID_FOURTH, "volume_id": VIRTUAL_DISK_FIRST}) + out = idr_obj.execute() + assert out == {"Message": data_when_controller_id_and_volume_id_passed, "Status": SUCCESS_STATUS} + + # Scenario - When invalid volume_id and valid controller_id is passed + data_when_controller_id_and_volume_id_passed = deepcopy(TestStorageData.storage_data_expected) + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=data_when_controller_id_and_volume_id_passed) + idrac_default_args.update({"controller_id": CONTROLLER_ID_FIRST, "volume_id": VIRTUAL_DISK_FIRST}) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == VIEW_OPERATION_FAILED + + # Scenario - When volume_id is passed + data_when_volume_id_passed = deepcopy(TestStorageData.storage_data_expected) + mocker.patch(MODULE_PATH + FETCH_STORAGE_DATA_METHOD, + return_value=data_when_volume_id_passed) + del idrac_default_args["controller_id"] + idrac_default_args.update({"volume_id": VIRTUAL_DISK_FIRST}) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == VIEW_OPERATION_FAILED + + +class TestStorageBase(FakeAnsibleModule): + module = idrac_storage_volume + + @pytest.fixture + def idrac_storage_volume_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_storage_volume_mock(self, mocker, idrac_storage_volume_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_storage_volume_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_storage_volume_mock + return idrac_conn_mock + + def test_module_extend_input(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + 'StorageBase.data_conversion', return_value={}) + idrac_default_args.update({'span_length': 1, 'span_depth': 1}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.module_extend_input(f_module) + # Scenario 1: when volumes is None + assert data['volumes'] == [{'drives': {'id': [-1]}}] + + # Scenario 2: when volumes is given + idrac_default_args.update({'volumes': [{"drives": {'location': [3]}, 'span_length': '1'}]}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + mocker.patch(MODULE_PATH + 'StorageBase.data_conversion', return_value={"drives": {'location': [3]}, 'span_length': '1'}) + + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.module_extend_input(f_module) + assert data['volumes'] == [{"drives": {'location': [3]}, 'span_length': 1}] + + def test_payload_for_disk(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: When drives is given + data = idr_obj.payload_for_disk({'drives': {'id': [1, 2]}}) + assert data == '12' + + # Scenario 2: When dedicate_hot_spare is in each_volume + data = idr_obj.payload_for_disk({'dedicated_hot_spare': [3, 5]}) + assert data == '35' + + def test_construct_volume_payloadk(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + 'xml_data_conversion', return_value=DATA_XML) + mocker.patch(MODULE_PATH + 'StorageBase.payload_for_disk', return_value='payload_detail_in_xml') + # Scenario 1: When state is create + idrac_default_args.update({'state': 'create'}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.construct_volume_payload(1, {}) + assert data == DATA_XML + + def test_constuct_payload(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + 'xml_data_conversion', return_value=DATA_XML) + mocker.patch(MODULE_PATH + 'StorageBase.construct_volume_payload', return_value='') + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: Default + data = idr_obj.constuct_payload({}) + assert data == DATA_XML + + # Scenario 2: When raid_reset_config is 'true' + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + idr_obj.module_ext_params.update({'raid_reset_config': 'true'}) + data = idr_obj.constuct_payload({}) + assert data == DATA_XML + + def test_wait_for_job_completion(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + obj = MagicMock() + obj.headers = {'Location': "/joburl/JID12345"} + job = {"job_wait": True, "job_wait_timeout": 1200} + idrac_default_args.update(job) + job_resp_completed = {'JobStatus': 'Completed'} + idrac_redfish_resp = (False, 'Job Success', job_resp_completed, 1200) + mocker.patch(MODULE_PATH + 'idrac_redfish_job_tracking', return_value=idrac_redfish_resp) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: Job_wait is True, job_wait_timeout match with default + with pytest.raises(Exception) as exc: + idr_obj.wait_for_job_completion(obj) + assert exc.value.args[0] == WAIT_TIMEOUT_MSG.format(1200) + + # Scenario 2: Job_wait is True, job_wait_timeout less than default + idrac_redfish_resp = (False, 'Job Success', job_resp_completed, 1000) + mocker.patch(MODULE_PATH + 'idrac_redfish_job_tracking', return_value=idrac_redfish_resp) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.wait_for_job_completion(obj) + assert data == job_resp_completed + + # Scenario 3: Job failed in resp + job_resp_failed = {'JobStatus': 'Failed', 'Message': 'Job failed.'} + idrac_redfish_resp = (True, 'Job Failed', job_resp_failed, 1000) + mocker.patch(MODULE_PATH + 'idrac_redfish_job_tracking', return_value=idrac_redfish_resp) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.wait_for_job_completion(obj) + assert exc.value.args[0] == 'Job failed.' + + # Scenario 4: Job wait is false + obj.json_data = {'JobStatus': 'Running'} + mocker.patch(MODULE_PATH + API_INVOKE_MOCKER, return_value=obj) + job = {"job_wait": False, "job_wait_timeout": 1200} + idrac_default_args.update(job) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + f_module.params.update({'state': 'create'}) + idr_obj = self.module.StorageBase(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.wait_for_job_completion(obj) + assert exc.value.args[0] == JOB_TRIGERRED.format('create') + + +class TestStorageValidation(TestStorageBase): + module = idrac_storage_volume + + @pytest.fixture + def idrac_storage_volume_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_storage_volume_mock(self, mocker, idrac_storage_volume_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_storage_volume_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_storage_volume_mock + return idrac_conn_mock + + def test_validate_controller_exists(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + # Scenario - when controller_id is not passed + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_controller_exists() + assert exc.value.args[0] == CONTROLLER_NOT_DEFINED + + # Scenario - when invalid controller_id is passed + controller_id = "AHCI.Embedded.1-invalid" + idrac_default_args.update({"controller_id": controller_id}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_controller_exists() + assert exc.value.args[0] == CONTROLLER_NOT_EXIST_ERROR.format(controller_id=controller_id) + + # Scenario - when controller_id is passed + controller_id = CONTROLLER_ID_FIRST + idrac_default_args.update({"controller_id": controller_id}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + idr_obj.validate_controller_exists() + + def test_validate_job_wait_negative_values(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + # Scenario - when job_wait_timeout is negative + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data) + idrac_default_args.update({"job_wait": True, "job_wait_timeout": -120}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_job_wait_negative_values() + assert exc.value.args[0] == NEGATIVE_OR_ZERO_MSG.format(parameter="job_wait_timeout") + + # Scenario - when job_wait_timeout is positive + idrac_default_args.update({"job_wait": True, "job_wait_timeout": 120}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + idr_obj.validate_job_wait_negative_values() + + @pytest.mark.parametrize("params", [ + {"span_depth": -1, "span_length": 2, "capacity": 200, "strip_size": 131072}, + {"span_depth": 1, "span_length": -1, "capacity": 200, "strip_size": 131072}, + {"span_depth": 1, "span_length": 2, "capacity": -1, "strip_size": 131072}, + {"span_depth": 1, "span_length": 2, "capacity": 200, "strip_size": -131072}, + ]) + def test_validate_negative_values_for_volume_params(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker, params): + # Scenario - when job_wait_timeout is negative + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data) + # idrac_default_args.update(params) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_negative_values_for_volume_params(params) + # TO DO replace job_wait_timeout with key in params which has negative value + negative_key = next((k for k, v in params.items() if v < 0), None) + assert exc.value.args[0] == NEGATIVE_OR_ZERO_MSG.format(parameter=negative_key) + + def test_validate_negative_values_for_volume_params_with_different_parameter(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + # Scenario - passing different parameter + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + idr_obj.validate_negative_values_for_volume_params({"volume_type": "RAID 0", "number_dedicated_hot_spare": 0}) + + # Scenario - when number_dedicated_hot_spare is negative + with pytest.raises(Exception) as exc: + idr_obj.validate_negative_values_for_volume_params({"number_dedicated_hot_spare": -1}) + assert exc.value.args[0] == NEGATIVE_MSG.format(parameter="number_dedicated_hot_spare") + + # Scenario - when number_dedicated_hot_spare is not negative + idr_obj.validate_negative_values_for_volume_params({"number_dedicated_hot_spare": 0}) + + def test_validate_volume_drives(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + # Scenario - when volume drives are not defined + volumes = { + "name": "volume_1" + } + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + with pytest.raises(Exception) as exc: + idr_obj.validate_volume_drives(volumes) + assert exc.value.args[0] == DRIVES_NOT_DEFINED + + # Scenario - when in volume drives id and location both defined + volumes = { + "name": "volume_1", + "drives": { + "id": [ + PHYSICAL_DISK_FIRST, + PHYSICAL_DISK_SECOND + ], + "location": [7, 3] + } + } + with pytest.raises(Exception) as exc: + idr_obj.validate_volume_drives(volumes) + assert exc.value.args[0] == ID_AND_LOCATION_BOTH_DEFINED + + # Scenario - when in volume drives id and location both not defined + volumes = { + "name": "volume_1", + "drives": { + PHYSICAL_DISK_FIRST: {} + } + } + with pytest.raises(Exception) as exc: + idr_obj.validate_volume_drives(volumes) + assert exc.value.args[0] == ID_AND_LOCATION_BOTH_NOT_DEFINED + + # Scenario - when in volume drives id is defined + volumes = { + "name": "volume_1", + "drives": { + "id": [ + PHYSICAL_DISK_FIRST, + PHYSICAL_DISK_SECOND + ] + } + } + mocker.patch(MODULE_PATH + "StorageValidation.raid_std_validation", + return_value=True) + out = idr_obj.validate_volume_drives(volumes) + assert out is True + + def test_raid_std_validation(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, + return_value=TestStorageData.storage_data) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageValidation(idrac_connection_storage_volume_mock, f_module) + # Scenario - Invalid span_length + params = {"span_depth": 1, "span_length": 4, "pd_count": 2, "volume_type": "RAID 1"} + with pytest.raises(Exception) as exc: + idr_obj.raid_std_validation(params["span_length"], + params["span_depth"], + params["volume_type"], + params["pd_count"]) + assert exc.value.args[0] == INVALID_VALUE_MSG.format(parameter="span_length") + + # Scenario - Invalid span_depth for RAID 1 + params = {"span_depth": 4, "span_length": 2, "pd_count": 3, "volume_type": "RAID 1"} + with pytest.raises(Exception) as exc: + idr_obj.raid_std_validation(params["span_length"], + params["span_depth"], + params["volume_type"], + params["pd_count"]) + assert exc.value.args[0] == INVALID_VALUE_MSG.format(parameter="span_depth") + + # Scenario - Invalid span_depth for RAID 10 + params = {"span_depth": 1, "span_length": 2, "pd_count": 9, "volume_type": "RAID 10"} + with pytest.raises(Exception) as exc: + idr_obj.raid_std_validation(params["span_length"], + params["span_depth"], + params["volume_type"], + params["pd_count"]) + assert exc.value.args[0] == INVALID_VALUE_MSG.format(parameter="span_depth") + + # Scenario - Invalid drive count + params = {"span_depth": 3, "span_length": 2, "pd_count": 1, "volume_type": "RAID 10"} + with pytest.raises(Exception) as exc: + idr_obj.raid_std_validation(params["span_length"], + params["span_depth"], + params["volume_type"], + params["pd_count"]) + assert exc.value.args[0] == INVALID_VALUE_MSG.format(parameter="drives") + + # Scenario - Valid + params = {"span_depth": 2, "span_length": 2, "pd_count": 4, "volume_type": "RAID 10"} + out = idr_obj.raid_std_validation(params["span_length"], + params["span_depth"], + params["volume_type"], + params["pd_count"]) + assert out is True + + +class TestStorageCreate(TestStorageBase): + module = idrac_storage_volume + + @pytest.fixture + def idrac_storage_volume_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_storage_volume_mock(self, mocker, idrac_storage_volume_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_storage_volume_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_storage_volume_mock + return idrac_conn_mock + + def test_disk_slot_id_conversion(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + # Scenario 1: location is given in drives + volume = {'drives': {'location': [0, 1]}} + idrac_default_args.update({"controller_id": CONTROLLER_ID_SECOND}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.disk_slot_location_to_id_conversion(volume) + assert data['id'] == TestStorageData.storage_data_expected['Controller'][CONTROLLER_ID_SECOND]['PhysicalDisk'] + + # Scenario 2: id is given in drives + id_list = ['Disk.Bay.3:Enclosure.Internal.0-1:AHCI.Embedded.1-2', + 'Disk.Bay.2:Enclosure.Internal.0-1:AHCI.Embedded.1-2'] + volume = {'drives': {'id': id_list}} + idrac_default_args.update({"controller_id": CONTROLLER_ID_SECOND}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.disk_slot_location_to_id_conversion(volume) + assert data['id'] == id_list + + # Scenario 3: When id and location is not given in drives + volume = {'drives': {}} + data = idr_obj.disk_slot_location_to_id_conversion(volume) + assert data == {} + + def test_perform_intersection_on_disk(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + # Scenario 1: When iDRAC has firmware version greater than 3.00.00.00 + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="3.10.00") + volume = {'media_type': 'HDD', 'protocol': 'SATA'} + healthy_disk, available_disk, media_disk, protocol_disk = {1, 2, 3, 4, 5}, {1, 2, 3, 5}, {2, 3, 4, 5}, {1, 5} + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.perform_intersection_on_disk(volume, healthy_disk, available_disk, media_disk, protocol_disk) + assert data == [5] + + # Scenario 1: When iDRAC has firmware version less than 3.00.00.00 + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="2.00.00") + volume = {'media_type': None, 'protocol': None} + data = idr_obj.perform_intersection_on_disk(volume, healthy_disk, available_disk, media_disk, protocol_disk) + assert data == [1, 2, 3, 4, 5] + + def test_filter_disk(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + drive_resp = {'DriveID1': {'MediaType': 'HDD', 'Protocol': 'SAS', 'Status': {'Health': 'OK'}, + 'Oem': {'Dell': {'DellPhysicalDisk': {'RaidStatus': 'Ready'}}}}, + 'DriveID2': {'MediaType': 'SSD', 'Protocol': 'SATA', 'Status': {'Health': 'Not OK'}}} + idrac_data = {'Controllers': {CONTROLLER_ID_FIRST: {'Drives': drive_resp}}} + # Scenario 1: When iDRAC has firmware version equal to 3.00.00.00 + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=idrac_data) + mocker.patch(MODULE_PATH + "get_idrac_firmware_version", return_value="3.05.00") + volume = {'media_type': 'HDD', 'protocol': 'SAS'} + idrac_default_args.update({"controller_id": CONTROLLER_ID_FIRST}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + data = idr_obj.filter_disk(volume) + assert data == ['DriveID1'] + + def test_updating_drives_module_input_when_given(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: When id is in drives + volume = {'drives': {'id': [2, 3, 4, 5]}} + filter_disk_output = [1, 3, 5] + data = idr_obj.updating_drives_module_input_when_given(volume, filter_disk_output) + assert data == [3, 5] + + # Scenario 2: When id is not in drives + volume = {'drives': {'location': [2, 3, 4, 5]}} + data = idr_obj.updating_drives_module_input_when_given(volume, filter_disk_output) + assert data == [] + + def test_updating_volume_module_input_for_hotspare(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: number_dedicated_hot_spare is in volume and greator than zero + volume = {'number_dedicated_hot_spare': 2} + filter_disk_output = [1, 3, 5, 4, 2] + reserved_pd = [1] + drive_exists_in_id = [3, 5] + data = idr_obj.updating_volume_module_input_for_hotspare(volume, filter_disk_output, reserved_pd, drive_exists_in_id) + assert data == [4, 2] + + # Scenario 2: number_dedicated_hot_spare is in volume and equal to zero + volume = {'number_dedicated_hot_spare': 0} + data = idr_obj.updating_volume_module_input_for_hotspare(volume, filter_disk_output, reserved_pd, drive_exists_in_id) + assert data == [] + + def test_updating_volume_module_input(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + mocker.patch(MODULE_PATH + FILTER_DISK, return_value=[1, 2, 3, 4, 5]) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: When required pd is less than available pd + volume = {'volumes': [{'span_depth': 1, 'span_length': 1, 'stripe_size': 65536, 'capacity': 50.45, + 'drives': {'id': [2, 3, 4]}, + 'number_dedicated_hot_spare': 1}]} + idr_obj.module_ext_params.update(volume) + drive_exists_in_id = [1, 2] + idr_obj.updating_volume_module_input(drive_exists_in_id) + assert idr_obj.module_ext_params['volumes'][0]['drives']['id'] == [1] + + # Scenario 2: When required pd is less than available pd with check_mode + volume = {'volumes': [{'span_depth': 1, 'span_length': 1, 'stripe_size': 65536, 'capacity': 50.45, + 'drives': {'id': [2, 3, 4]}, + 'number_dedicated_hot_spare': 1}]} + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + idr_obj.module_ext_params.update(volume) + drive_exists_in_id = [1, 2] + with pytest.raises(Exception) as exc: + idr_obj.updating_volume_module_input(drive_exists_in_id) + assert exc.value.args[0] == CHANGES_FOUND + + # Scenario 3: When required pd is greater than available pd + mocker.patch(MODULE_PATH + FILTER_DISK, return_value=[1]) + controller_id = 'Qwerty' + volume = {'volumes': [{'span_depth': 2, 'span_length': 1, + 'drives': {'id': [1]}, 'number_dedicated_hot_spare': 0}], + 'controller_id': controller_id} + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + idr_obj.module_ext_params.update(volume) + drive_exists_in_id = [1, 2] + with pytest.raises(Exception) as exc: + idr_obj.updating_volume_module_input(drive_exists_in_id) + assert exc.value.args[0] == NOT_ENOUGH_DRIVES.format(controller_id=controller_id) + + # Scenario 4: When required pd is greater than available pd with check_mode + mocker.patch(MODULE_PATH + FILTER_DISK, return_value=[1]) + controller_id = 'Qwerty' + volume = {'volumes': [{'span_depth': 2, 'span_length': 1, + 'drives': {'id': [1, 2]}, 'number_dedicated_hot_spare': 0}], + 'controller_id': controller_id} + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + idr_obj.module_ext_params.update(volume) + drive_exists_in_id = [1] + with pytest.raises(Exception) as exc: + idr_obj.updating_volume_module_input(drive_exists_in_id) + assert exc.value.args[0] == CHANGES_NOT_FOUND + + def test_validate_create(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + mocker.patch(MODULE_PATH + 'StorageValidation.validate_controller_exists', return_value=None) + mocker.patch(MODULE_PATH + 'StorageValidation.validate_job_wait_negative_values', return_value=None) + mocker.patch(MODULE_PATH + 'StorageValidation.validate_negative_values_for_volume_params', return_value=None) + mocker.patch(MODULE_PATH + 'StorageValidation.validate_volume_drives', return_value=None) + mocker.patch(MODULE_PATH + 'StorageCreate.updating_volume_module_input', return_value=None) + mocker.patch(MODULE_PATH + 'StorageCreate.disk_slot_location_to_id_conversion', return_value={'id': []}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + # Scenario 1: When required pd is less than available pd + volume = {'volumes': [{'drives': {'location': [2, 3, 4]}}, + {'drives': {'id': [1]}}]} + idr_obj.module_ext_params.update(volume) + idr_obj.validate() + assert idr_obj.module_ext_params['volumes'][0]['drives']['id'] == [] + + def test_execute_create(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=TestStorageData.storage_data) + mocker.patch(MODULE_PATH + 'StorageCreate.validate', return_value=None) + mocker.patch(MODULE_PATH + 'StorageBase.constuct_payload', return_value=None) + mocker.patch(MODULE_PATH + 'iDRACRedfishAPI.import_scp', return_value=None) + mocker.patch(MODULE_PATH + 'StorageBase.wait_for_job_completion', return_value={}) + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageCreate(idrac_connection_storage_volume_mock, f_module) + idr_obj.controller_id = CONTROLLER_ID_FOURTH + data = idr_obj.execute() + assert data == {} + + +class TestStorageDelete(TestStorageBase): + module = idrac_storage_volume + + @pytest.fixture + def idrac_storage_volume_mock(self): + idrac_obj = MagicMock() + return idrac_obj + + @pytest.fixture + def idrac_connection_storage_volume_mock(self, mocker, idrac_storage_volume_mock): + idrac_conn_mock = mocker.patch(MODULE_PATH + 'iDRACRedfishAPI', + return_value=idrac_storage_volume_mock) + idrac_conn_mock.return_value.__enter__.return_value = idrac_storage_volume_mock + return idrac_conn_mock + + def test_execute_delete(self, idrac_default_args, idrac_connection_storage_volume_mock, mocker): + idrac_resp = {'Controllers': {'Cntrl1': {'Volumes': {'Volume_ID1': {'Name': 'Volume Name 1'}}}}} + mocker.patch(MODULE_PATH + ALL_STORAGE_DATA_METHOD, return_value=idrac_resp) + mocker.patch(MODULE_PATH + 'StorageDelete.validate', return_value=None) + mocker.patch(MODULE_PATH + 'iDRACRedfishAPI.import_scp', return_value=None) + mocker.patch(MODULE_PATH + 'StorageBase.wait_for_job_completion', return_value={}) + mocker.patch(MODULE_PATH + 'StorageBase.module_extend_input', return_value={}) + + # Scenario 1: When Non existing volume is passed as input + volume = {'volumes': [{'name': 'volume-1', 'span_depth': 1, 'span_length': 1}, + {'name': 'volume-2', 'span_depth': 1, 'span_length': 1}]} + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageDelete(idrac_connection_storage_volume_mock, f_module) + idr_obj.module.params.update(volume) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == VOLUME_NOT_FOUND + + # Scenario 2: When Non existing volume is passed as input with check_mode + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageDelete(idrac_connection_storage_volume_mock, f_module) + idr_obj.module.params.update(volume) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == VOLUME_NOT_FOUND + + # Scenario 3: When Existing volume is passed as input + volume = {'volumes': [{'name': 'Volume Name 1', 'span_depth': 1, 'span_length': 1}]} + f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idr_obj = self.module.StorageDelete(idrac_connection_storage_volume_mock, f_module) + idr_obj.module.params.update(volume) + idr_obj.module_ext_params.update({'state': 'delete', 'volumes': volume}) + data = idr_obj.execute() + assert data == {} + + # Scenario 3: When Existing volume is passed as input with check_mode + f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + idr_obj = self.module.StorageDelete(idrac_connection_storage_volume_mock, f_module) + idr_obj.module.params.update(volume) + idr_obj.module_ext_params.update({'state': 'delete', 'volumes': volume}) + with pytest.raises(Exception) as exc: + idr_obj.execute() + assert exc.value.args[0] == CHANGES_FOUND + + def test_idrac_storage_volume_main_positive_case(self, idrac_default_args, + idrac_connection_storage_volume_mock, mocker): + def returning_none(): + return None + mocker.patch(MODULE_PATH + VIEW_EXECUTE, return_value=returning_none) + view = 'view' + idrac_default_args.update({'state': view}) + data = self._run_module(idrac_default_args) + assert data['msg'] == SUCCESSFUL_OPERATION_MSG.format(operation=view) + + @pytest.mark.parametrize("exc_type", + [URLError, HTTPError, SSLValidationError, ConnectionError, TypeError, ValueError]) + def test_idrac_storage_volume_main_exception_handling_case(self, exc_type, idrac_default_args, + idrac_connection_storage_volume_mock, mocker): + + json_str = to_text(json.dumps({"data": "out"})) + if exc_type in [HTTPError, SSLValidationError]: + mocker.patch(MODULE_PATH + VIEW_EXECUTE, + side_effect=exc_type('https://testhost.com', 400, + 'http error message', + {"accept-type": "application/json"}, + StringIO(json_str))) + else: + mocker.patch(MODULE_PATH + VIEW_EXECUTE, + side_effect=exc_type('test')) + result = self._run_module(idrac_default_args) + if exc_type == URLError: + assert result['unreachable'] is True + else: + assert result['failed'] is True + assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_user.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_user.py index 0ef6e6da3..6087f140f 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_user.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_idrac_user.py @@ -22,6 +22,24 @@ from ansible.module_utils._text import to_text from io import StringIO MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.' +VERSION = "3.60.60.60" +VERSION13G = "2.70.70.70" +SLOT_API = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/" +CHANGES_FOUND = "Changes found to commit!" +SLEEP_PATH = 'idrac_user.time.sleep' +USERNAME2 = "Users.2#UserName" +GET_PAYLOAD = "idrac_user.get_payload" +PAYLOAD_XML = "idrac_user.convert_payload_xml" +XML_DATA = "" +USERNAME1 = "Users.1#UserName" +IMPORT_SCP = "idrac_user.iDRACRedfishAPI.import_scp" +USER2 = "User.2#UserName" +SUCCESS_CREATED = "Successfully created a request." +SUCCESS_MSG = "Successfully created user account." +SUCCESS_UPDATED = "Successfully updated user account." +INVOKE_REQUEST = "idrac_user.iDRACRedfishAPI.invoke_request" +CM_ACCOUNT = "idrac_user.create_or_modify_account" +USER_PRIVILAGE = "Users.1#Privilege" class TestIDRACUser(FakeAnsibleModule): @@ -78,7 +96,7 @@ class TestIDRACUser(FakeAnsibleModule): "Users.1.ProtocolEnable": idrac_default_args["protocol_enable"], "Users.1.AuthenticationProtocol": idrac_default_args["authentication_protocol"], "Users.1.PrivacyProtocol": idrac_default_args["privacy_protocol"]} - xml_payload, json_payload = self.module.convert_payload_xml(payload) + _xml, json_payload = self.module.convert_payload_xml(payload) assert json_payload["Users.1#SolEnable"] is True def test_remove_user_account_check_mode_1(self, idrac_connection_user_mock, idrac_default_args, mocker): @@ -87,12 +105,14 @@ class TestIDRACUser(FakeAnsibleModule): "ipmi_serial_privilege": None, "enable": False, "sol_enable": False, "protocol_enable": False, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=True) slot_id = 1 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) + slot_uri = SLOT_API.format(slot_id) with pytest.raises(Exception) as exc: - self.module.remove_user_account(f_module, idrac_connection_user_mock, slot_uri, slot_id) - assert exc.value.args[0] == "Changes found to commit!" + self.module.remove_user_account( + f_module, idrac_connection_user_mock, slot_uri, slot_id) + assert exc.value.args[0] == CHANGES_FOUND def test_remove_user_account_check_mode_2(self, idrac_connection_user_mock, idrac_default_args, mocker): idrac_default_args.update({"state": "absent", "user_name": "user_name", "new_user_name": None, @@ -100,9 +120,11 @@ class TestIDRACUser(FakeAnsibleModule): "ipmi_serial_privilege": None, "enable": False, "sol_enable": False, "protocol_enable": False, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=True) with pytest.raises(Exception) as exc: - self.module.remove_user_account(f_module, idrac_connection_user_mock, None, None) + self.module.remove_user_account( + f_module, idrac_connection_user_mock, None, None) assert exc.value.args[0] == "No changes found to commit!" def test_remove_user_account_check_mode_3(self, idrac_connection_user_mock, idrac_default_args, mocker): @@ -111,12 +133,15 @@ class TestIDRACUser(FakeAnsibleModule): "ipmi_serial_privilege": None, "enable": False, "sol_enable": False, "protocol_enable": False, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - idrac_connection_user_mock.remove_user_account.return_value = {"success": True} - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idrac_connection_user_mock.remove_user_account.return_value = { + "success": True} + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) slot_id = 1 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - mocker.patch(MODULE_PATH + 'idrac_user.time.sleep', return_value=None) - self.module.remove_user_account(f_module, idrac_connection_user_mock, slot_uri, slot_id) + slot_uri = SLOT_API.format(slot_id) + mocker.patch(MODULE_PATH + SLEEP_PATH, return_value=None) + self.module.remove_user_account( + f_module, idrac_connection_user_mock, slot_uri, slot_id) def test_remove_user_account_check_mode_4(self, idrac_connection_user_mock, idrac_default_args, mocker): idrac_default_args.update({"state": "absent", "user_name": "user_name", "new_user_name": None, @@ -124,10 +149,13 @@ class TestIDRACUser(FakeAnsibleModule): "ipmi_serial_privilege": None, "enable": False, "sol_enable": False, "protocol_enable": False, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - idrac_connection_user_mock.remove_user_account.return_value = {"success": True} - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + idrac_connection_user_mock.remove_user_account.return_value = { + "success": True} + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) with pytest.raises(Exception) as exc: - self.module.remove_user_account(f_module, idrac_connection_user_mock, None, None) + self.module.remove_user_account( + f_module, idrac_connection_user_mock, None, None) assert exc.value.args[0] == 'The user account is absent.' def test_get_user_account_1(self, idrac_connection_user_mock, idrac_default_args, mocker): @@ -140,10 +168,12 @@ class TestIDRACUser(FakeAnsibleModule): mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.export_scp", return_value=MagicMock()) mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.get_idrac_local_account_attr", - return_value={"Users.2#UserName": "test_user", "Users.3#UserName": ""}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - response = self.module.get_user_account(f_module, idrac_connection_user_mock) - assert response[0]["Users.2#UserName"] == "test_user" + return_value={USERNAME2: "test_user", "Users.3#UserName": ""}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + response = self.module.get_user_account( + f_module, idrac_connection_user_mock) + assert response[0][USERNAME2] == "test_user" assert response[3] == 3 assert response[4] == "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/3" @@ -157,9 +187,11 @@ class TestIDRACUser(FakeAnsibleModule): mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.export_scp", return_value=MagicMock()) mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.get_idrac_local_account_attr", - return_value={"Users.2#UserName": "test_user", "Users.3#UserName": "test"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - response = self.module.get_user_account(f_module, idrac_connection_user_mock) + return_value={USERNAME2: "test_user", "Users.3#UserName": "test"}) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + response = self.module.get_user_account( + f_module, idrac_connection_user_mock) assert response[2] == 3 assert response[1] == "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/3" @@ -170,227 +202,93 @@ class TestIDRACUser(FakeAnsibleModule): "ipmi_serial_privilege": "Administrator", "enable": True, "sol_enable": True, "protocol_enable": True, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) with pytest.raises(Exception) as err: self.module.get_user_account(f_module, idrac_connection_user_mock) assert err.value.args[0] == "User name is not valid." - def test_create_or_modify_account_1(self, idrac_connection_user_mock, idrac_default_args, mocker): + @pytest.mark.parametrize("params", [ + {"ret_val": SUCCESS_MSG, "empty_slot_id": 2, + "empty_slot_uri": SLOT_API.format(2)}, + {"ret_val": SUCCESS_UPDATED, "slot_id": 2, + "slot_uri": SLOT_API.format(2)}, + {"firm_ver": (14, VERSION), "ret_val": SUCCESS_MSG, + "empty_slot_id": 2, "empty_slot_uri": SLOT_API.format(2)}, + {"firm_ver": (14, VERSION), "ret_val": SUCCESS_UPDATED, + "slot_id": 2, "slot_uri": SLOT_API.format(2)}, + {"firm_ver": (14, VERSION), "ret_val": SUCCESS_UPDATED, "slot_id": 2, "slot_uri": SLOT_API.format(2), + "empty_slot_id": 2, "empty_slot_uri": SLOT_API.format(2)}, + ]) + def test_create_or_modify_account(self, idrac_connection_user_mock, idrac_default_args, mocker, params): idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", "user_name": "test", "user_password": "password", "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", "ipmi_serial_privilege": "Administrator", "enable": True, "sol_enable": True, "protocol_enable": True, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (13, "2.70.70.70") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.import_scp", - return_value={"Message": "Successfully created a request."}) - empty_slot_id = 2 - empty_slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(empty_slot_id) - user_attr = {"User.2#UserName": "test_user"} - mocker.patch(MODULE_PATH + 'idrac_user.time.sleep', return_value=None) - response = self.module.create_or_modify_account(f_module, idrac_connection_user_mock, None, None, - empty_slot_id, empty_slot_uri, user_attr) - assert response[1] == "Successfully created user account." + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) + idrac_connection_user_mock.get_server_generation = params.get( + "firm_ver", (13, VERSION13G)) + mocker.patch(MODULE_PATH + GET_PAYLOAD, + return_value={USERNAME2: "test_user"}) + mocker.patch(MODULE_PATH + PAYLOAD_XML, + return_value=(XML_DATA, {USERNAME2: "test_user"})) + mocker.patch(MODULE_PATH + IMPORT_SCP, + return_value={"Message": SUCCESS_CREATED}) + mocker.patch(MODULE_PATH + SLEEP_PATH, return_value=None) + mocker.patch(MODULE_PATH + INVOKE_REQUEST, + return_value={"Message": SUCCESS_CREATED}) + + empty_slot_id = params.get("empty_slot_id", None) + empty_slot_uri = params.get("empty_slot_uri", None) + slot_id = params.get("slot_id", None) + slot_uri = params.get("slot_uri", None) + user_attr = {USER2: "test_user"} - def test_create_or_modify_account_2(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (13, "2.70.70.70") - mocker.patch(MODULE_PATH + 'idrac_user.time.sleep', return_value=None) - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.import_scp", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"User.2#UserName": "test_user"} response = self.module.create_or_modify_account(f_module, idrac_connection_user_mock, slot_uri, slot_id, - None, None, user_attr) - assert response[1] == "Successfully updated user account." - - def test_create_or_modify_account_3(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (13, "2.70.70.70") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.import_scp", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"Users.1#UserName": "test_user"} - with pytest.raises(Exception) as exc: - self.module.create_or_modify_account(f_module, idrac_connection_user_mock, slot_uri, slot_id, - None, None, user_attr) - assert exc.value.args[0] == "Requested changes are already present in the user slot." - - def test_create_or_modify_account_4(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) - idrac_connection_user_mock.get_server_generation = (13, "2.70.70.70") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.import_scp", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"Users.1#UserName": "test_user"} - with pytest.raises(Exception) as exc: - self.module.create_or_modify_account(f_module, idrac_connection_user_mock, slot_uri, slot_id, - None, None, user_attr) - assert exc.value.args[0] == "No changes found to commit!" - - def test_create_or_modify_account_5(self, idrac_connection_user_mock, idrac_default_args, mocker): + empty_slot_id, empty_slot_uri, user_attr) + assert response[1] == params.get("ret_val") + + @pytest.mark.parametrize("params", [ + {"ret_val": "Requested changes are already present in the user slot."}, + {"firm_ver": (14, VERSION), "slot_id": None, "slot_uri": None, + "ret_val": "Maximum number of users reached. Delete a user account and retry the operation."}, + {"check_mode": True, "ret_val": "No changes found to commit!"}, + {"check_mode": True, "user_attr": { + USERNAME1: "test_user"}, "ret_val": CHANGES_FOUND}, + {"check_mode": True, "user_attr": {USERNAME1: "test_user"}, "ret_val": + CHANGES_FOUND, "empty_slot_id": 2, "empty_slot_uri": SLOT_API.format(2)}, + ]) + def test_create_or_modify_account_exception(self, idrac_connection_user_mock, idrac_default_args, mocker, params): idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", "user_name": "test", "user_password": "password", "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", "ipmi_serial_privilege": "Administrator", "enable": True, "sol_enable": True, "protocol_enable": True, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) - idrac_connection_user_mock.get_server_generation = (13, "2.70.70.70") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.2#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.import_scp", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"Users.1#UserName": "test_user"} + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=params.get("check_mode", False)) + idrac_connection_user_mock.get_server_generation = params.get( + "firm_ver", (13, VERSION13G)) + mocker.patch(MODULE_PATH + GET_PAYLOAD, + return_value={USERNAME2: "test_user"}) + mocker.patch(MODULE_PATH + PAYLOAD_XML, + return_value=(XML_DATA, {USERNAME2: "test_user"})) + mocker.patch(MODULE_PATH + IMPORT_SCP, + return_value={"Message": SUCCESS_CREATED}) + mocker.patch(MODULE_PATH + INVOKE_REQUEST, + return_value={"Message": SUCCESS_CREATED}) + slot_id = params.get("slot_id", 2) + slot_uri = params.get("slot_uri", SLOT_API.format(2)) + empty_slot_id = params.get("empty_slot_id", None) + empty_slot_uri = params.get("empty_slot_uri", None) + user_attr = params.get("user_attr", {USERNAME2: "test_user"}) with pytest.raises(Exception) as exc: self.module.create_or_modify_account(f_module, idrac_connection_user_mock, slot_uri, slot_id, - None, None, user_attr) - assert exc.value.args[0] == "Changes found to commit!" - - def test_create_or_modify_account_6(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (14, "3.60.60.60") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.invoke_request", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"User.2#UserName": "test_user"} - response = self.module.create_or_modify_account(f_module, idrac_connection_user_mock, None, None, - slot_id, slot_uri, user_attr) - assert response[1] == "Successfully created user account." - - def test_create_or_modify_account_7(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=True) - idrac_connection_user_mock.get_server_generation = (14, "3.60.60.60") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.invoke_request", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"User.2#UserName": "test_user"} - with pytest.raises(Exception) as exc: - self.module.create_or_modify_account(f_module, idrac_connection_user_mock, None, None, - slot_id, slot_uri, user_attr) - assert exc.value.args[0] == "Changes found to commit!" - - def test_create_or_modify_account_8(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (14, "3.60.60.60") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.invoke_request", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"User.2#UserName": "test_user"} - response = self.module.create_or_modify_account(f_module, idrac_connection_user_mock, slot_uri, slot_id, - None, None, user_attr) - assert response[1] == "Successfully updated user account." - - def test_create_or_modify_account_both_slot_empty_input(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (14, "3.60.60.60") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.invoke_request", - return_value={"Message": "Successfully created a request."}) - slot_id = 2 - slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"User.2#UserName": "test_user"} - response = self.module.create_or_modify_account(f_module, idrac_connection_user_mock, slot_id, slot_uri, - slot_id, slot_uri, user_attr) - assert response[1] == "Successfully updated user account." - - def test_create_or_modify_account_both_slot_empty_none_input(self, idrac_connection_user_mock, idrac_default_args, mocker): - idrac_default_args.update({"state": "present", "new_user_name": "new_user_name", - "user_name": "test", "user_password": "password", - "privilege": "Administrator", "ipmi_lan_privilege": "Administrator", - "ipmi_serial_privilege": "Administrator", "enable": True, - "sol_enable": True, "protocol_enable": True, - "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) - idrac_connection_user_mock.get_server_generation = (14, "3.60.60.60") - mocker.patch(MODULE_PATH + "idrac_user.get_payload", return_value={"Users.2#UserName": "test_user"}) - mocker.patch(MODULE_PATH + "idrac_user.convert_payload_xml", - return_value=("", {"Users.1#UserName": "test_user"})) - mocker.patch(MODULE_PATH + "idrac_user.iDRACRedfishAPI.invoke_request", - return_value={"Message": "Successfully created a request."}) - # slot_id = 2 - # slot_uri = "/redfish/v1/Managers/iDRAC.Embedded.1/Accounts/{0}/".format(slot_id) - user_attr = {"User.2#UserName": "test_user"} - with pytest.raises(Exception) as exc: - self.module.create_or_modify_account(f_module, idrac_connection_user_mock, None, None, - None, None, user_attr) - assert exc.value.args[0] == "Maximum number of users reached. Delete a user account and retry the operation." + empty_slot_id, empty_slot_uri, user_attr) + assert exc.value.args[0] == params.get("ret_val") @pytest.mark.parametrize("exc_type", [SSLValidationError, URLError, ValueError, TypeError, ConnectionError, HTTPError, ImportError, RuntimeError]) @@ -403,10 +301,10 @@ class TestIDRACUser(FakeAnsibleModule): "authentication_protocol": "SHA", "privacy_protocol": "AES"}) json_str = to_text(json.dumps({"data": "out"})) if exc_type not in [HTTPError, SSLValidationError]: - mocker.patch(MODULE_PATH + "idrac_user.create_or_modify_account", + mocker.patch(MODULE_PATH + CM_ACCOUNT, side_effect=exc_type('test')) else: - mocker.patch(MODULE_PATH + "idrac_user.create_or_modify_account", + mocker.patch(MODULE_PATH + CM_ACCOUNT, side_effect=exc_type('https://testhost.com', 400, 'http error message', {"accept-type": "application/json"}, StringIO(json_str))) if exc_type != URLError: @@ -425,7 +323,8 @@ class TestIDRACUser(FakeAnsibleModule): "authentication_protocol": "SHA", "privacy_protocol": "AES"}) obj = MagicMock() obj.json_data = {"error": {"message": "Some Error Occured"}} - mocker.patch(MODULE_PATH + "idrac_user.remove_user_account", return_value=(obj, "error")) + mocker.patch(MODULE_PATH + "idrac_user.remove_user_account", + return_value=(obj, "error")) result = self._run_module_with_fail_json(idrac_default_args) assert result['failed'] is True assert result['msg'] == "Some Error Occured" @@ -438,8 +337,10 @@ class TestIDRACUser(FakeAnsibleModule): "sol_enable": True, "protocol_enable": True, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) obj = MagicMock() - obj.json_data = {"Oem": {"Dell": {"Message": "Unable to complete application of configuration profile values."}}} - mocker.patch(MODULE_PATH + "idrac_user.remove_user_account", return_value=(obj, "error")) + obj.json_data = {"Oem": {"Dell": { + "Message": "Unable to complete application of configuration profile values."}}} + mocker.patch(MODULE_PATH + "idrac_user.remove_user_account", + return_value=(obj, "error")) result = self._run_module_with_fail_json(idrac_default_args) assert result['failed'] is True assert result['msg'] == "Unable to complete application of configuration profile values." @@ -452,8 +353,9 @@ class TestIDRACUser(FakeAnsibleModule): "sol_enable": True, "protocol_enable": True, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) obj = MagicMock() - obj.json_data = {"Oem": {"Dell": {"Message": "This Message Does Not Exists"}}} - mocker.patch(MODULE_PATH + "idrac_user.create_or_modify_account", return_value=(obj, "created")) + obj.json_data = { + "Oem": {"Dell": {"Message": "This Message Does Not Exists"}}} + mocker.patch(MODULE_PATH + CM_ACCOUNT, return_value=(obj, "created")) # with pytest.raises(Exception) as exc: result = self._run_module(idrac_default_args) assert result['changed'] is True @@ -477,7 +379,8 @@ class TestIDRACUser(FakeAnsibleModule): "ipmi_serial_privilege": "Administrator", "enable": True, "sol_enable": True, "protocol_enable": True, "authentication_protocol": "SHA", "privacy_protocol": "AES"}) - f_module = self.get_module_mock(params=idrac_default_args, check_mode=False) + f_module = self.get_module_mock( + params=idrac_default_args, check_mode=False) with pytest.raises(Exception) as err: self.module.validate_input(f_module) assert err.value.args[0] == "custom_privilege value should be from 0 to 511." @@ -491,12 +394,14 @@ class TestIDRACUser(FakeAnsibleModule): is_change_required = self.module.compare_payload(json_payload, None) assert is_change_required is True - json_payload = {"Users.1#Privilege": "123"} - idrac_attr = {"Users.1#Privilege": "123"} - is_change_required = self.module.compare_payload(json_payload, idrac_attr) + json_payload = {USER_PRIVILAGE: "123"} + idrac_attr = {USER_PRIVILAGE: "123"} + is_change_required = self.module.compare_payload( + json_payload, idrac_attr) assert is_change_required is False - json_payload = {"Users.1#Privilege": "123"} - idrac_attr = {"Users.1#Privilege": "124"} - is_change_required = self.module.compare_payload(json_payload, idrac_attr) + json_payload = {USER_PRIVILAGE: "123"} + idrac_attr = {USER_PRIVILAGE: "124"} + is_change_required = self.module.compare_payload( + json_payload, idrac_attr) assert is_change_required is True diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_application_console_preferences.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_application_console_preferences.py index 627c5e71d..1f51f6cae 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_application_console_preferences.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_application_console_preferences.py @@ -2,8 +2,8 @@ # # Dell OpenManage Ansible Modules -# Version 7.0.0 -# Copyright (C) 2022 Dell Inc. or its subsidiaries. All Rights Reserved. +# Version 9.1.0 +# Copyright (C) 2022-2024 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) # @@ -2227,12 +2227,12 @@ class TestOmeAppConsolePreferences(FakeAnsibleModule): assert result["unreachable"] is True elif exc_type not in [HTTPError, SSLValidationError]: mocker.patch(MODULE_PATH + '_validate_params', side_effect=exc_type("exception message")) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True else: mocker.patch(MODULE_PATH + '_validate_params', side_effect=exc_type('https://testhost.com', 400, 'http error message', {"accept-type": "application/json"}, StringIO(json_str))) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_local_access_configuration.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_local_access_configuration.py index 9b92bb3c2..50f9e09e6 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_local_access_configuration.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_local_access_configuration.py @@ -2,8 +2,8 @@ # # Dell OpenManage Ansible Modules -# Version 8.1.0 -# Copyright (C) 2021-2023 Dell Inc. or its subsidiaries. All Rights Reserved. +# Version 9.1.0 +# Copyright (C) 2022-2024 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) # @@ -59,7 +59,7 @@ class TestOMEMDevicePower(FakeAnsibleModule): "SettingType": "LocalAccessConfiguration", "EnableChassisDirect": False, "EnableChassisPowerButton": False, "EnableKvmAccess": True, "EnableLcdOverridePin": False, "LcdAccess": "VIEW_ONLY", "LcdCustomString": "LCD Text", "LcdLanguage": "en", - "LcdPresence": "Present", "LcdOverridePin": "123456", + "LcdPresence": "Present", "LcdPinLength": 6, "LedPresence": "Absent", "LcdOverridePin": "123456", "QuickSync": {"QuickSyncAccess": True, "TimeoutLimit": 10, "EnableInactivityTimeout": True, "TimeoutLimitUnit": "MINUTES", "EnableReadAuthentication": True, "EnableQuickSyncWifi": True, "QuickSyncHardware": "Present"}}, @@ -86,7 +86,7 @@ class TestOMEMDevicePower(FakeAnsibleModule): "SettingType": "LocalAccessConfiguration", "EnableChassisDirect": False, "EnableChassisPowerButton": False, "EnableKvmAccess": True, "EnableLcdOverridePin": False, "LcdAccess": "VIEW_ONLY", "LcdCustomString": "LCD Text", "LcdLanguage": "en", - "LcdPresence": "Present", "LcdOverridePin": "123456", + "LcdPresence": "Present", "LcdPinLength": 6, "LedPresence": "Absent", "LcdOverridePin": "123456", "QuickSync": {"QuickSyncAccess": True, "TimeoutLimit": 10, "EnableInactivityTimeout": True, "TimeoutLimitUnit": "MINUTES", "EnableReadAuthentication": True, "EnableQuickSyncWifi": True, "QuickSyncHardware": "Present"}}, @@ -287,7 +287,7 @@ class TestOMEMDevicePower(FakeAnsibleModule): def test_check_mode_validation(self, ome_conn_mock_lac, ome_default_args, ome_response_mock, mocker): loc_data = {"EnableKvmAccess": True, "EnableChassisDirect": True, "EnableChassisPowerButton": True, "EnableLcdOverridePin": True, "LcdAccess": True, "LcdCustomString": "LCD Text", - "LcdLanguage": "en", "LcdOverridePin": "123456", "LcdPresence": "Present", + "LcdLanguage": "en", "LcdPresence": "Present", "LcdPinLength": 6, "LedPresence": "Absent", "LcdOverridePin": "123456", "QuickSync": {"QuickSyncAccess": True, "TimeoutLimit": 10, "EnableInactivityTimeout": True, "TimeoutLimitUnit": "MINUTES", "EnableReadAuthentication": True, "EnableQuickSyncWifi": True, "QuickSyncHardware": "Present"}, } @@ -329,18 +329,12 @@ class TestOMEMDevicePower(FakeAnsibleModule): elif exc_type not in [HTTPError, SSLValidationError]: mocker.patch(MODULE_PATH + 'check_domain_service', side_effect=exc_type("exception message")) - result = self._run_module_with_fail_json(ome_default_args) - assert result['failed'] is True - elif exc_type in [HTTPError]: - mocker.patch(MODULE_PATH + 'check_domain_service', - side_effect=exc_type(HTTPS_ADDRESS, 400, HTTP_ERROR_MSG, - {"accept-type": "application/json"}, StringIO(json_str))) result = self._run_module(ome_default_args) assert result['failed'] is True else: mocker.patch(MODULE_PATH + 'check_domain_service', side_effect=exc_type(HTTPS_ADDRESS, 400, HTTP_ERROR_MSG, {"accept-type": "application/json"}, StringIO(json_str))) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_location.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_location.py index 40fe1b1a2..fd76d6ac9 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_location.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_location.py @@ -112,36 +112,36 @@ class TestOMEMDeviceLocation(FakeAnsibleModule): @pytest.mark.parametrize("params", [ {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "Successfully updated the location settings.", - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "device_id": 1234, "data_center": "data center", "room": "room", "aisle": "aisle", "rack": "rack"} }, {"json_data": {"value": [ {'Id': 1234, 'DeviceServiceTag': 'ABCD123', - 'PublicAddress': "1.2.3.4", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "Successfully updated the location settings.", - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "device_service_tag": "ABCD123", "data_center": "data center", "room": "room", "aisle": "aisle", "rack": "rack"} }, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "Successfully updated the location settings.", - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "data_center": "data center", "room": "room", "aisle": "aisle", "rack": "rack"} }, {"json_data": {"value": [ {'Id': 1234, 'PublicAddress': "dummyhost_shouldnotexist", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "Successfully updated the location settings.", 'mparams': {"hostname": "dummyhost_shouldnotexist", "data_center": "data center", @@ -159,9 +159,9 @@ class TestOMEMDeviceLocation(FakeAnsibleModule): @pytest.mark.parametrize("params", [ {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "The device location settings operation is supported only on OpenManage Enterprise Modular systems.", 'http_error_json': { "error": { @@ -179,14 +179,14 @@ class TestOMEMDeviceLocation(FakeAnsibleModule): ] } }, - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "data_center": "data center", "room": "room", "aisle": "aisle", "rack": "rack"} }, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "Unable to complete the operation because the location settings are not supported on the specified device.", 'http_error_json': { "error": { @@ -206,16 +206,16 @@ class TestOMEMDeviceLocation(FakeAnsibleModule): }, 'check_domain_service': 'mocked_check_domain_service', 'standalone_chassis': ('Id', 1234), - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "data_center": "data center", "room": "room", "aisle": "aisle", "rack": "rack"} }, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': "Unable to complete the operation because the entered target device id '123' is invalid.", - 'mparams': {"hostname": "1.2.3.4", "device_id": 123, + 'mparams': {"hostname": "xxx.xxx.x.x.x.x.x.x", "device_id": 123, "data_center": "data center", "room": "room", "aisle": "aisle", "rack": "rack"} }, diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_mgmt_network.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_mgmt_network.py index 004586393..c0bf63e4a 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_mgmt_network.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_mgmt_network.py @@ -366,11 +366,11 @@ class TestOmeDeviceMgmtNetwork(FakeAnsibleModule): {"mparams": {"device_id": 123}, "success": True, "json_data": {"Type": 2000, "Id": 123, "Identifier": "ABCD123"}, "res": {"Type": 2000, "Id": 123, "Identifier": "ABCD123"}, - "diff": {"IPV4": "1.2.3.4"}}, + "diff": {"IPV4": "xxx.xxx.x.x"}}, {"mparams": {"device_id": 123}, "success": True, "json_data": {"Type": 4000, "Id": 123, "Identifier": "ABCD123"}, "res": {"Type": 4000, "Id": 123, "Identifier": "ABCD123"}, - "diff": {"IPV4": "1.2.3.4"}}, + "diff": {"IPV4": "xxx.xxx.x.x"}}, ]) def test_get_network_payload( self, params, ome_connection_mock_for_device_network, ome_response_mock, mocker): diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_power_settings.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_power_settings.py index 553a57369..9a2255c49 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_power_settings.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_power_settings.py @@ -35,6 +35,28 @@ POWER_FAIL_MSG = "Unable to complete the operation because the power settings " "are not supported on the specified device." DOMAIN_FAIL_MSG = "The device location settings operation is supported only on " \ "OpenManage Enterprise Modular." +ERROR_JSON = { + "error": { + "code": "Base.1.0.GeneralError", + "message": "A general error has occurred. See ExtendedInfo for more information.", + "@Message.ExtendedInfo": [ + { + "MessageId": "CGEN1004", + "RelatedProperties": [], + "Message": "Unable to process the request because an error occurred.", + "MessageArgs": [], + "Severity": "Critical", + "Resolution": "Retry the operation. If the issue persists, contact your system administrator." + } + ] + }} +MPARAMS = {"hostname": "xxx.xxx.x.x", + "power_configuration": {"enable_power_cap": True, "power_cap": 3424} + } +POWER_JSON_DATA = {"value": [ + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", + 'DeviceId': 1234, "Type": 1000}, + {'PublicAddress': "xxx.xxx.xx.x", 'DeviceId': 1235, "Type": 1000}]} @pytest.fixture @@ -49,11 +71,11 @@ class TestOMEMDevicePower(FakeAnsibleModule): module = ome_device_power_settings - @pytest.mark.parametrize("params", [ + @pytest.mark.parametrize("params_inp", [ {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceServiceTag': 'ABCD123', "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}], + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}], "EnableHotSpare": True, "EnablePowerCapSettings": True, "MaxPowerCap": "3424", @@ -63,15 +85,15 @@ class TestOMEMDevicePower(FakeAnsibleModule): "RedundancyPolicy": "NO_REDUNDANCY", "SettingType": "Power"}, 'message': SUCCESS_MSG, - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "power_configuration": {"enable_power_cap": True, "power_cap": 3424}, "hot_spare_configuration": {"enable_hot_spare": False, "primary_grid": "GRID_1"}, "device_id": 1234, }}, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceServiceTag': 'ABCD123', "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}], + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}], "EnableHotSpare": True, "EnablePowerCapSettings": True, "MaxPowerCap": "3424", @@ -81,15 +103,15 @@ class TestOMEMDevicePower(FakeAnsibleModule): "RedundancyPolicy": "NO_REDUNDANCY", "SettingType": "Power"}, 'message': SUCCESS_MSG, - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "power_configuration": {"enable_power_cap": False, "power_cap": 3424}, "hot_spare_configuration": {"enable_hot_spare": True, "primary_grid": "GRID_1"}, "device_service_tag": 'ABCD123', }}, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}], + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}], "EnableHotSpare": True, "EnablePowerCapSettings": True, "MaxPowerCap": "3424", @@ -99,14 +121,14 @@ class TestOMEMDevicePower(FakeAnsibleModule): "RedundancyPolicy": "NO_REDUNDANCY", "SettingType": "Power"}, 'message': SUCCESS_MSG, - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "power_configuration": {"enable_power_cap": False, "power_cap": 3424}, "hot_spare_configuration": {"enable_hot_spare": True, "primary_grid": "GRID_1"} }}, {"json_data": {"value": [ {'Id': 1234, 'PublicAddress': "dummyhostname_shouldnotexist", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}], + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}], "EnableHotSpare": True, "EnablePowerCapSettings": True, "MaxPowerCap": "3424", @@ -121,20 +143,17 @@ class TestOMEMDevicePower(FakeAnsibleModule): "hot_spare_configuration": {"enable_hot_spare": True, "primary_grid": "GRID_1"} }} ]) - def test_ome_devices_power_settings_success(self, params, ome_conn_mock_power, ome_response_mock, + def test_ome_devices_power_settings_success(self, params_inp, ome_conn_mock_power, ome_response_mock, ome_default_args, module_mock, mocker): - ome_response_mock.success = params.get("success", True) - ome_response_mock.json_data = params['json_data'] - ome_default_args.update(params['mparams']) + ome_response_mock.success = params_inp.get("success", True) + ome_response_mock.json_data = params_inp['json_data'] + ome_default_args.update(params_inp['mparams']) result = self._run_module( - ome_default_args, check_mode=params.get('check_mode', False)) - assert result['msg'] == params['message'] + ome_default_args, check_mode=params_inp.get('check_mode', False)) + assert result['msg'] == params_inp['message'] @pytest.mark.parametrize("params", [ - {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", - 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {"json_data": POWER_JSON_DATA, 'message': DOMAIN_FAIL_MSG, 'http_error_json': { "error": { @@ -151,77 +170,39 @@ class TestOMEMDevicePower(FakeAnsibleModule): } ] }}, - 'mparams': {"hostname": "1.2.3.4", + 'mparams': {"hostname": "xxx.xxx.x.x", "device_service_tag": 'ABCD123', "power_configuration": {"enable_power_cap": True, "power_cap": 3424} }}, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': POWER_FAIL_MSG, 'check_domain_service': 'mocked_check_domain_service', 'get_chassis_device': ('Id', 1234), - 'http_error_json': { - "error": { - "code": "Base.1.0.GeneralError", - "message": "A general error has occurred. See ExtendedInfo for more information.", - "@Message.ExtendedInfo": [ - { - "MessageId": "CGEN1004", - "RelatedProperties": [], - "Message": "Unable to process the request because an error occurred.", - "MessageArgs": [], - "Severity": "Critical", - "Resolution": "Retry the operation. If the issue persists, contact your system administrator." - } - ] - }}, - 'mparams': {"hostname": "1.2.3.4", - "power_configuration": {"enable_power_cap": True, "power_cap": 3424} - }}, - {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", - 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + 'http_error_json': ERROR_JSON, + 'mparams': MPARAMS}, + {"json_data": POWER_JSON_DATA, 'message': POWER_FAIL_MSG, 'check_domain_service': 'mocked_check_domain_service', 'get_chassis_device': ('Id', 1234), 'http_err_code': 404, - 'http_error_json': { - "error": { - "code": "Base.1.0.GeneralError", - "message": "A general error has occurred. See ExtendedInfo for more information.", - "@Message.ExtendedInfo": [ - { - "MessageId": "CGEN1004", - "RelatedProperties": [], - "Message": "Unable to process the request because an error occurred.", - "MessageArgs": [], - "Severity": "Critical", - "Resolution": "Retry the operation. If the issue persists, contact your system administrator." - } - ] - }}, - 'mparams': {"hostname": "1.2.3.4", - "power_configuration": {"enable_power_cap": True, "power_cap": 3424} - }}, - {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", - 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + 'http_error_json': ERROR_JSON, + 'mparams': MPARAMS}, + {"json_data": POWER_JSON_DATA, 'message': DEVICE_FAIL_MSG.format('id', 123), 'check_domain_service': 'mocked_check_domain_service', 'get_chassis_device': ('Id', 1234), - 'mparams': {"hostname": "1.2.3.4", 'device_id': 123, + 'mparams': {"hostname": "xxx.xxx.x.x", 'device_id': 123, "power_configuration": {"enable_power_cap": True, "power_cap": 3424} }}, {"json_data": {"value": [ - {'Id': 1234, 'PublicAddress': "1.2.3.4", + {'Id': 1234, 'PublicAddress': "xxx.xxx.x.x", 'DeviceId': 1234, "Type": 1000}, - {'PublicAddress': "1.2.3.5", 'DeviceId': 1235, "Type": 1000}]}, + {'PublicAddress': "X.X.X.X", 'DeviceId': 1235, "Type": 1000}]}, 'message': CONFIG_FAIL_MSG, - 'mparams': {"hostname": "1.2.3.4", "device_id": 123}} + 'mparams': {"hostname": "xxx.xxx.x.x", "device_id": 123}} ]) def test_ome_devices_power_settings_failure(self, params, ome_conn_mock_power, ome_response_mock, ome_default_args, module_mock, mocker): @@ -303,29 +284,29 @@ class TestOMEMDevicePower(FakeAnsibleModule): result = self.module.get_ip_from_host("ZZ.ZZ.ZZ.ZZ") assert result == "ZZ.ZZ.ZZ.ZZ" - @pytest.mark.parametrize("exc_type", + @pytest.mark.parametrize("exc_type_ps", [IOError, ValueError, SSLError, TypeError, ConnectionError, HTTPError, URLError]) - def test_ome_device_power_main_exception_case(self, exc_type, mocker, ome_default_args, + def test_ome_device_power_main_exception_case(self, exc_type_ps, mocker, ome_default_args, ome_conn_mock_power, ome_response_mock): ome_default_args.update({"device_id": 25011, "power_configuration": {"enable_power_cap": True, "power_cap": 3424}}) ome_response_mock.status_code = 400 ome_response_mock.success = False json_str = to_text(json.dumps({"info": "error_details"})) - if exc_type == URLError: + if exc_type_ps == URLError: mocker.patch(MODULE_PATH + 'check_domain_service', - side_effect=exc_type("url open error")) - result = self._run_module(ome_default_args) - assert result["unreachable"] is True - elif exc_type not in [HTTPError, SSLValidationError]: + side_effect=exc_type_ps("url open error")) + result_ps = self._run_module(ome_default_args) + assert result_ps["unreachable"] is True + elif exc_type_ps not in [HTTPError, SSLValidationError]: mocker.patch(MODULE_PATH + 'check_domain_service', - side_effect=exc_type("exception message")) - result = self._run_module_with_fail_json(ome_default_args) - assert result['failed'] is True + side_effect=exc_type_ps("exception message")) + result_ps = self._run_module_with_fail_json(ome_default_args) + assert result_ps['failed'] is True else: mocker.patch(MODULE_PATH + 'check_domain_service', - side_effect=exc_type('https://testhost.com', 400, 'http error message', - {"accept-type": "application/json"}, StringIO(json_str))) - result = self._run_module_with_fail_json(ome_default_args) - assert result['failed'] is True - assert 'msg' in result + side_effect=exc_type_ps('https://testhost.com', 400, 'http error message', + {"accept-type": "application/json"}, StringIO(json_str))) + result_ps = self._run_module_with_fail_json(ome_default_args) + assert result_ps['failed'] is True + assert 'msg' in result_ps diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_quick_deploy.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_quick_deploy.py index 60b8c17cc..78299581d 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_quick_deploy.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_device_quick_deploy.py @@ -267,13 +267,13 @@ class TestOMEMDevicePower(FakeAnsibleModule): assert result["unreachable"] is True elif exc_type not in [HTTPError, SSLValidationError]: mocker.patch(MODULE_PATH + 'check_domain_service', side_effect=exc_type("exception message")) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True else: mocker.patch(MODULE_PATH + 'check_domain_service', side_effect=exc_type(HTTP_ADDRESS, 400, HTTP_ERROR_MSG, {"accept-type": ACCESS_TYPE}, StringIO(json_str))) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_devices.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_devices.py index 23148d390..f341911cf 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_devices.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_ome_devices.py @@ -2,8 +2,8 @@ # # Dell OpenManage Ansible Modules -# Version 6.1.0 -# Copyright (C) 2021-2022 Dell Inc. or its subsidiaries. All Rights Reserved. +# Version 9.1.0 +# Copyright (C) 2022-2024 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) # @@ -456,12 +456,12 @@ class TestOmeDevices(FakeAnsibleModule): assert result["unreachable"] is True elif exc_type not in [HTTPError, SSLValidationError]: mocker.patch(MODULE_PATH + 'get_dev_ids', side_effect=exc_type("exception message")) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True else: mocker.patch(MODULE_PATH + 'get_dev_ids', side_effect=exc_type('https://testhost.com', 400, 'http error message', {"accept-type": "application/json"}, StringIO(json_str))) - result = self._run_module_with_fail_json(ome_default_args) + result = self._run_module(ome_default_args) assert result['failed'] is True assert 'msg' in result diff --git a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_redfish_storage_volume.py b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_redfish_storage_volume.py index 40160edf5..b1413d4bd 100644 --- a/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_redfish_storage_volume.py +++ b/ansible_collections/dellemc/openmanage/tests/unit/plugins/modules/test_redfish_storage_volume.py @@ -2,8 +2,8 @@ # # Dell OpenManage Ansible Modules -# Version 7.0.0 -# Copyright (C) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. +# Version 9.1.0 +# Copyright (C) 2020-2024 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) # @@ -23,6 +23,8 @@ from ansible.module_utils._text import to_text MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.' HTTPS_ADDRESS = 'https://testhost.com' +REDFISH = "/redfish/v1/" +VOLUME_URI = "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.Integrated.1-1/Volumes/" @pytest.fixture @@ -40,6 +42,10 @@ class TestStorageVolume(FakeAnsibleModule): def storage_volume_base_uri(self): self.module.storage_collection_map.update({"storage_base_uri": "/redfish/v1/Systems/System.Embedded.1/Storage"}) + @pytest.fixture + def greater_version(self): + return True + arg_list1 = [{"state": "present"}, {"state": "present", "volume_id": "volume_id"}, {"state": "absent", "volume_id": "volume_id"}, {"command": "initialize", "volume_id": "volume_id"}, @@ -61,6 +67,7 @@ class TestStorageVolume(FakeAnsibleModule): redfish_connection_mock_for_storage_volume, param, storage_volume_base_uri): mocker.patch(MODULE_PATH + 'redfish_storage_volume.validate_inputs') + mocker.patch(MODULE_PATH + 'redfish_storage_volume.is_fw_ver_greater', return_value=True) mocker.patch(MODULE_PATH + 'redfish_storage_volume.fetch_storage_resource') mocker.patch(MODULE_PATH + 'redfish_storage_volume.configure_raid_operation', return_value={"msg": "Successfully submitted volume task.", @@ -95,8 +102,8 @@ class TestStorageVolume(FakeAnsibleModule): def test_redfish_storage_volume_main_exception_handling_case(self, exc_type, mocker, redfish_default_args, redfish_connection_mock_for_storage_volume, redfish_response_mock): - redfish_default_args.update({"state": "present"}) - mocker.patch(MODULE_PATH + 'redfish_storage_volume.validate_inputs') + redfish_default_args.update({"state": "present", "controller_id": "controller_id"}) + mocker.patch(MODULE_PATH + 'redfish_storage_volume.is_fw_ver_greater') redfish_response_mock.status_code = 400 redfish_response_mock.success = False json_str = to_text(json.dumps({"data": "out"})) @@ -150,7 +157,7 @@ class TestStorageVolume(FakeAnsibleModule): assert message["msg"] == "Successfully submitted {0} volume task.".format(action) @pytest.mark.parametrize("input", [{"state": "present"}, {"state": "absent"}, {"command": "initialize"}, {"command": None}]) - def test_configure_raid_operation(self, input, redfish_connection_mock_for_storage_volume, mocker): + def test_configure_raid_operation(self, input, redfish_connection_mock_for_storage_volume, mocker, greater_version): f_module = self.get_module_mock(params=input) mocker.patch(MODULE_PATH + 'redfish_storage_volume.perform_volume_create_modify', return_value={"msg": "Successfully submitted create volume task.", @@ -164,7 +171,7 @@ class TestStorageVolume(FakeAnsibleModule): return_value={"msg": "Successfully submitted initialize volume task.", "task_uri": "JobService/Jobs", "task_id": "JID_789"}) - message = self.module.configure_raid_operation(f_module, redfish_connection_mock_for_storage_volume) + message = self.module.configure_raid_operation(f_module, redfish_connection_mock_for_storage_volume, greater_version) val = list(input.values()) if val[0] == "present": assert message["msg"] == "Successfully submitted create volume task." @@ -257,7 +264,7 @@ class TestStorageVolume(FakeAnsibleModule): assert exc.value.args[0] == "No changes found to be applied." def test_perform_volume_create_modify_success_case_01(self, mocker, storage_volume_base_uri, - redfish_connection_mock_for_storage_volume): + redfish_connection_mock_for_storage_volume, greater_version): f_module = self.get_module_mock(params={"volume_id": "volume_id", "controller_id": "controller_id"}) message = {"msg": "Successfully submitted create volume task.", "task_uri": "JobService/Jobs", "task_id": "JID_123"} @@ -265,13 +272,13 @@ class TestStorageVolume(FakeAnsibleModule): mocker.patch(MODULE_PATH + 'redfish_storage_volume.volume_payload', return_value={"payload": "value"}) mocker.patch(MODULE_PATH + 'redfish_storage_volume.perform_storage_volume_action', return_value=message) mocker.patch(MODULE_PATH + 'redfish_storage_volume.check_mode_validation', return_value=None) - message = self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume) + message = self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume, greater_version) assert message["msg"] == "Successfully submitted create volume task." assert message["task_id"] == "JID_123" def test_perform_volume_create_modify_success_case_02(self, mocker, storage_volume_base_uri, redfish_connection_mock_for_storage_volume, - redfish_response_mock): + redfish_response_mock, greater_version): f_module = self.get_module_mock(params={"volume_id": "volume_id"}) message = {"msg": "Successfully submitted modify volume task.", "task_uri": "JobService/Jobs", "task_id": "JID_123"} @@ -280,13 +287,13 @@ class TestStorageVolume(FakeAnsibleModule): mocker.patch(MODULE_PATH + 'redfish_storage_volume.volume_payload', return_value={"payload": "value"}) mocker.patch(MODULE_PATH + 'redfish_storage_volume.perform_storage_volume_action', return_value=message) mocker.patch(MODULE_PATH + 'redfish_storage_volume.check_mode_validation', return_value=None) - message = self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume) + message = self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume, greater_version) assert message["msg"] == "Successfully submitted modify volume task." assert message["task_id"] == "JID_123" def test_perform_volume_create_modify_success_case_03(self, mocker, storage_volume_base_uri, redfish_connection_mock_for_storage_volume, - redfish_response_mock): + redfish_response_mock, greater_version): f_module = self.get_module_mock(params={"volume_id": "volume_id"}) message = {"msg": "Successfully submitted modify volume task.", "task_uri": "JobService/Jobs", "task_id": "JID_123"} @@ -295,13 +302,13 @@ class TestStorageVolume(FakeAnsibleModule): mocker.patch(MODULE_PATH + 'redfish_storage_volume.volume_payload', return_value={"payload": "value"}) mocker.patch(MODULE_PATH + 'redfish_storage_volume.perform_storage_volume_action', return_value=message) mocker.patch(MODULE_PATH + 'redfish_storage_volume.check_mode_validation', return_value=None) - message = self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume) + message = self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume, greater_version) assert message["msg"] == "Successfully submitted modify volume task." assert message["task_id"] == "JID_123" def test_perform_volume_create_modify_failure_case_01(self, mocker, storage_volume_base_uri, redfish_connection_mock_for_storage_volume, - redfish_response_mock): + redfish_response_mock, greater_version): f_module = self.get_module_mock(params={"volume_id": "volume_id"}) message = {"msg": "Successfully submitted modify volume task.", "task_uri": "JobService/Jobs", "task_id": "JID_123"} @@ -311,7 +318,7 @@ class TestStorageVolume(FakeAnsibleModule): mocker.patch(MODULE_PATH + 'redfish_storage_volume.perform_storage_volume_action', return_value=message) mocker.patch(MODULE_PATH + 'redfish_storage_volume.check_mode_validation', return_value=None) with pytest.raises(Exception) as exc: - self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume) + self.module.perform_volume_create_modify(f_module, redfish_connection_mock_for_storage_volume, greater_version) assert exc.value.args[0] == "Input options are not provided for the modify volume task." def test_perform_storage_volume_action_success_case(self, mocker, redfish_response_mock, @@ -485,7 +492,7 @@ class TestStorageVolume(FakeAnsibleModule): self.module.check_physical_disk_exists(f_module, drive) assert exc.value.args[0] == "No Drive(s) are attached to the specified Controller Id: RAID.Mezzanine.1C-1." - def test_volume_payload_case_01(self, storage_volume_base_uri): + def test_volume_payload_case_01(self, storage_volume_base_uri, greater_version): param = { "drives": ["Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1"], "capacity_bytes": 299439751168, @@ -505,7 +512,7 @@ class TestStorageVolume(FakeAnsibleModule): "SpanLength": 2, "WriteCachePolicy": "WriteThrough"}}}} f_module = self.get_module_mock(params=param) - payload = self.module.volume_payload(f_module) + payload = self.module.volume_payload(f_module, greater_version) assert payload["Drives"][0]["@odata.id"] == "/redfish/v1/Systems/System.Embedded.1/Storage/" \ "Drives/Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1" assert payload["RAIDType"] == "RAID0" @@ -518,19 +525,19 @@ class TestStorageVolume(FakeAnsibleModule): assert payload["Dell"]["DellVirtualDisk"]["ReadCachePolicy"] == "NoReadAhead" assert payload["@Redfish.OperationApplyTime"] == "Immediate" - def test_volume_payload_case_02(self): + def test_volume_payload_case_02(self, greater_version): param = {"block_size_bytes": 512, "raid_type": "RAID0", "name": "VD1", "optimum_io_size_bytes": 65536} f_module = self.get_module_mock(params=param) - payload = self.module.volume_payload(f_module) + payload = self.module.volume_payload(f_module, greater_version) assert payload["RAIDType"] == "RAID0" assert payload["Name"] == "VD1" assert payload["BlockSizeBytes"] == 512 assert payload["OptimumIOSizeBytes"] == 65536 - def test_volume_payload_case_03(self, storage_volume_base_uri): + def test_volume_payload_case_03(self, storage_volume_base_uri, greater_version): """Testing encrypted value in case value is passed false""" param = { "drives": ["Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1"], @@ -550,7 +557,7 @@ class TestStorageVolume(FakeAnsibleModule): "SpanLength": 2, "WriteCachePolicy": "WriteThrough"}}}} f_module = self.get_module_mock(params=param) - payload = self.module.volume_payload(f_module) + payload = self.module.volume_payload(f_module, greater_version) assert payload["Drives"][0]["@odata.id"] == "/redfish/v1/Systems/System.Embedded.1/" \ "Storage/Drives/Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1" assert payload["RAIDType"] == "RAID0" @@ -562,7 +569,7 @@ class TestStorageVolume(FakeAnsibleModule): assert payload["EncryptionTypes"] == ["NativeDriveEncryption"] assert payload["Dell"]["DellVirtualDisk"]["ReadCachePolicy"] == "NoReadAhead" - def test_volume_payload_case_04(self, storage_volume_base_uri): + def test_volume_payload_case_04(self, storage_volume_base_uri, greater_version): param = { "drives": ["Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1"], "capacity_bytes": 299439751168, @@ -581,7 +588,7 @@ class TestStorageVolume(FakeAnsibleModule): "SpanLength": 2, "WriteCachePolicy": "WriteThrough"}}}} f_module = self.get_module_mock(params=param) - payload = self.module.volume_payload(f_module) + payload = self.module.volume_payload(f_module, greater_version) assert payload["Drives"][0]["@odata.id"] == "/redfish/v1/Systems/System.Embedded.1/Storage/" \ "Drives/Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1" assert payload["RAIDType"] == "RAID0" @@ -593,7 +600,7 @@ class TestStorageVolume(FakeAnsibleModule): assert payload["EncryptionTypes"] == ["NativeDriveEncryption"] assert payload["Dell"]["DellVirtualDisk"]["ReadCachePolicy"] == "NoReadAhead" - def test_volume_payload_case_05(self, storage_volume_base_uri): + def test_volume_payload_case_05(self, storage_volume_base_uri, greater_version): param = { "drives": ["Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1", "Disk.Bay.0:Enclosure.Internal.0-1:RAID.Mezzanine.1C-1", @@ -615,7 +622,7 @@ class TestStorageVolume(FakeAnsibleModule): "SpanLength": 2, "WriteCachePolicy": "WriteThrough"}}}} f_module = self.get_module_mock(params=param) - payload = self.module.volume_payload(f_module) + payload = self.module.volume_payload(f_module, greater_version) assert payload["Drives"][0]["@odata.id"] == "/redfish/v1/Systems/System.Embedded.1/Storage/" \ "Drives/Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1" assert payload["RAIDType"] == "RAID6" @@ -627,7 +634,7 @@ class TestStorageVolume(FakeAnsibleModule): assert payload["EncryptionTypes"] == ["NativeDriveEncryption"] assert payload["Dell"]["DellVirtualDisk"]["ReadCachePolicy"] == "NoReadAhead" - def test_volume_payload_case_06(self, storage_volume_base_uri): + def test_volume_payload_case_06(self, storage_volume_base_uri, greater_version): param = { "drives": ["Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1", "Disk.Bay.0:Enclosure.Internal.0-1:RAID.Mezzanine.1C-1", @@ -653,7 +660,7 @@ class TestStorageVolume(FakeAnsibleModule): "SpanLength": 2, "WriteCachePolicy": "WriteThrough"}}}} f_module = self.get_module_mock(params=param) - payload = self.module.volume_payload(f_module) + payload = self.module.volume_payload(f_module, greater_version) assert payload["Drives"][0]["@odata.id"] == "/redfish/v1/Systems/System.Embedded.1/Storage/" \ "Drives/Disk.Bay.0:Enclosure.Internal.0-0:RAID.Mezzanine.1C-1" assert payload["RAIDType"] == "RAID60" @@ -679,7 +686,7 @@ class TestStorageVolume(FakeAnsibleModule): "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage" }, } - redfish_connection_mock_for_storage_volume.root_uri = "/redfish/v1/" + redfish_connection_mock_for_storage_volume.root_uri = REDFISH self.module.fetch_storage_resource(f_module, redfish_connection_mock_for_storage_volume) assert self.module.storage_collection_map["storage_base_uri"] == "/redfish/v1/Systems/System.Embedded.1/Storage" @@ -694,7 +701,7 @@ class TestStorageVolume(FakeAnsibleModule): } ], } - redfish_connection_mock_for_storage_volume.root_uri = "/redfish/v1/" + redfish_connection_mock_for_storage_volume.root_uri = REDFISH with pytest.raises(Exception) as exc: self.module.fetch_storage_resource(f_module, redfish_connection_mock_for_storage_volume) assert exc.value.args[0] == "Target out-of-band controller does not support storage feature using Redfish API." @@ -707,7 +714,7 @@ class TestStorageVolume(FakeAnsibleModule): "Members": [ ], } - redfish_connection_mock_for_storage_volume.root_uri = "/redfish/v1/" + redfish_connection_mock_for_storage_volume.root_uri = REDFISH with pytest.raises(Exception) as exc: self.module.fetch_storage_resource(f_module, redfish_connection_mock_for_storage_volume) assert exc.value.args[0] == "Target out-of-band controller does not support storage feature using Redfish API." @@ -716,7 +723,7 @@ class TestStorageVolume(FakeAnsibleModule): redfish_response_mock): f_module = self.get_module_mock() msg = "Target out-of-band controller does not support storage feature using Redfish API." - redfish_connection_mock_for_storage_volume.root_uri = "/redfish/v1/" + redfish_connection_mock_for_storage_volume.root_uri = REDFISH redfish_connection_mock_for_storage_volume.invoke_request.side_effect = HTTPError(HTTPS_ADDRESS, 404, json.dumps(msg), {}, None) with pytest.raises(Exception) as exc: @@ -726,7 +733,7 @@ class TestStorageVolume(FakeAnsibleModule): redfish_response_mock): f_module = self.get_module_mock() msg = "http error" - redfish_connection_mock_for_storage_volume.root_uri = "/redfish/v1/" + redfish_connection_mock_for_storage_volume.root_uri = REDFISH redfish_connection_mock_for_storage_volume.invoke_request.side_effect = HTTPError(HTTPS_ADDRESS, 400, msg, {}, None) with pytest.raises(Exception, match=msg) as exc: @@ -736,13 +743,13 @@ class TestStorageVolume(FakeAnsibleModule): redfish_response_mock): f_module = self.get_module_mock() msg = "connection error" - redfish_connection_mock_for_storage_volume.root_uri = "/redfish/v1/" + redfish_connection_mock_for_storage_volume.root_uri = REDFISH redfish_connection_mock_for_storage_volume.invoke_request.side_effect = URLError(msg) with pytest.raises(Exception, match=msg) as exc: self.module.fetch_storage_resource(f_module, redfish_connection_mock_for_storage_volume) def test_check_mode_validation(self, redfish_connection_mock_for_storage_volume, - redfish_response_mock, storage_volume_base_uri): + redfish_response_mock, storage_volume_base_uri, greater_version): param = {"drives": ["Disk.Bay.0:Enclosure.Internal.0-0:RAID.Integrated.1-1"], "capacity_bytes": 214748364800, "block_size_bytes": 512, "encryption_types": "NativeDriveEncryption", "encrypted": False, "raid_type": "RAID0", "optimum_io_size_bytes": 65536} @@ -751,13 +758,15 @@ class TestStorageVolume(FakeAnsibleModule): with pytest.raises(Exception) as exc: self.module.check_mode_validation( f_module, redfish_connection_mock_for_storage_volume, "create", - "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.Integrated.1-1/Volumes/") + VOLUME_URI, + greater_version=True) assert exc.value.args[0] == "Changes found to be applied." redfish_response_mock.json_data = {"Members@odata.count": 0} with pytest.raises(Exception) as exc: self.module.check_mode_validation( f_module, redfish_connection_mock_for_storage_volume, "create", - "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.Integrated.1-1/Volumes/") + VOLUME_URI, + greater_version=True) assert exc.value.args[0] == "Changes found to be applied." redfish_response_mock.json_data = { "Members@odata.count": 1, "Id": "Disk.Virtual.0:RAID.Integrated.1-1", @@ -772,18 +781,20 @@ class TestStorageVolume(FakeAnsibleModule): with pytest.raises(Exception) as exc: self.module.check_mode_validation( f_module, redfish_connection_mock_for_storage_volume, "create", - "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.Integrated.1-1/Volumes/") + VOLUME_URI, + greater_version=True) assert exc.value.args[0] == "No changes found to be applied." def test_check_mode_validation_01(self, redfish_connection_mock_for_storage_volume, - redfish_response_mock, storage_volume_base_uri): + redfish_response_mock, storage_volume_base_uri, greater_version): param1 = {"volume_id": None, 'name': None} f_module = self.get_module_mock(params=param1) f_module.check_mode = False result = self.module.check_mode_validation(f_module, redfish_connection_mock_for_storage_volume, "", - "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.Integrated.1-1/Volumes/") + VOLUME_URI, + greater_version=True) assert not result def test_check_raid_type_supported_success_case01(self, mocker, redfish_response_mock, storage_volume_base_uri, @@ -845,29 +856,31 @@ class TestStorageVolume(FakeAnsibleModule): def test_get_apply_time_success_case_01(self, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"controller_id": "controller_id", "apply_time": "Immediate"} f_module = self.get_module_mock(params=param) redfish_response_mock.success = True redfish_response_mock.json_data = {"@Redfish.OperationApplyTimeSupport": {"SupportedValues": ["Immediate"]}} self.module.get_apply_time(f_module, redfish_connection_mock_for_storage_volume, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) def test_get_apply_time_success_case_02(self, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"controller_id": "controller_id"} f_module = self.get_module_mock(params=param) redfish_response_mock.success = True redfish_response_mock.json_data = {"@Redfish.OperationApplyTimeSupport": {"SupportedValues": ["Immediate"]}} self.module.get_apply_time(f_module, redfish_connection_mock_for_storage_volume, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) def test_get_apply_time_supported_failure_case(self, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"controller_id": "controller_id", "apply_time": "Immediate"} f_module = self.get_module_mock(params=param) redfish_response_mock.success = True @@ -875,25 +888,27 @@ class TestStorageVolume(FakeAnsibleModule): with pytest.raises(Exception) as exc: self.module.get_apply_time(f_module, redfish_connection_mock_for_storage_volume, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) assert exc.value.args[0] == "Apply time Immediate \ is not supported. The supported values are ['OnReset']. Enter the valid values and retry the operation." def test_get_apply_time_supported_exception_case(self, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"controller_id": "controller_id", "apply_time": "Immediate"} f_module = self.get_module_mock(params=param) redfish_connection_mock_for_storage_volume.invoke_request.side_effect = HTTPError(HTTPS_ADDRESS, 400, '', {}, None) with pytest.raises(HTTPError) as ex: self.module.get_apply_time(f_module, redfish_connection_mock_for_storage_volume, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) def test_check_apply_time_supported_and_reboot_required_success_case01(self, mocker, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"reboot_server": True} f_module = self.get_module_mock(params=param) mocker.patch(MODULE_PATH + 'redfish_storage_volume.get_apply_time', @@ -901,13 +916,14 @@ is not supported. The supported values are ['OnReset']. Enter the valid values a apply_time = self.module.get_apply_time(f_module, redfish_connection_mock_for_storage_volume) val = self.module.check_apply_time_supported_and_reboot_required(f_module, redfish_connection_mock_for_storage_volume, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) assert val def test_check_apply_time_supported_and_reboot_required_success_case02(self, mocker, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"reboot_server": False} f_module = self.get_module_mock(params=param) mocker.patch(MODULE_PATH + 'redfish_storage_volume.get_apply_time', @@ -915,12 +931,13 @@ is not supported. The supported values are ['OnReset']. Enter the valid values a apply_time = self.module.get_apply_time(f_module, redfish_connection_mock_for_storage_volume) val = self.module.check_apply_time_supported_and_reboot_required(f_module, redfish_connection_mock_for_storage_volume, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) assert not val def test_check_job_tracking_required_success_case01(self, mocker, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"job_wait": True} mocker.patch(MODULE_PATH + 'redfish_storage_volume.get_apply_time', return_value="OnReset") @@ -929,12 +946,13 @@ is not supported. The supported values are ['OnReset']. Enter the valid values a val = self.module.check_job_tracking_required(f_module, redfish_connection_mock_for_storage_volume, reboot_required=False, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) assert not val def test_check_job_tracking_required_success_case02(self, mocker, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"job_wait": True} mocker.patch(MODULE_PATH + 'redfish_storage_volume.get_apply_time', return_value="Immediate") @@ -942,12 +960,13 @@ is not supported. The supported values are ['OnReset']. Enter the valid values a val = self.module.check_job_tracking_required(f_module, redfish_connection_mock_for_storage_volume, reboot_required=True, - controller_id="controller_id") + controller_id="controller_id", + greater_version=True) assert val def test_check_job_tracking_required_success_case03(self, mocker, redfish_response_mock, redfish_connection_mock_for_storage_volume, - storage_volume_base_uri): + storage_volume_base_uri, greater_version): param = {"job_wait": False} mocker.patch(MODULE_PATH + 'redfish_storage_volume.get_apply_time', return_value="Immediate") @@ -955,7 +974,8 @@ is not supported. The supported values are ['OnReset']. Enter the valid values a val = self.module.check_job_tracking_required(f_module, redfish_connection_mock_for_storage_volume, reboot_required=True, - controller_id=None) + controller_id=None, + greater_version=True) assert not val def test_perform_reboot_timeout_case(self, mocker, redfish_response_mock, @@ -1129,3 +1149,32 @@ is not supported. The supported values are ['OnReset']. Enter the valid values a with pytest.raises(Exception) as ex: self.module.validate_negative_job_time_out(f_module) assert ex.value.args[0] == "The parameter job_wait_timeout value cannot be negative or zero." + + def test_is_fw_ver_greater(self, redfish_connection_mock_for_storage_volume, redfish_response_mock): + # Scenario 1: FW version is not greater + redfish_response_mock.json_data = { + '@odata.context': '/redfish/v1/$metadata#Manager.Manager', + '@odata.id': '/redfish/v1/Managers/iDRAC.Embedded.1', + '@odata.type': '#Manager.v1_3_3.Manager', + 'FirmwareVersion': '2.81' + } + redfish_connection_mock_for_storage_volume.root_uri = REDFISH + ver = self.module.is_fw_ver_greater(redfish_connection_mock_for_storage_volume) + if ver is True: + assert ver is True + else: + assert ver is False + + # Scenario 1: FW version is not greater + redfish_response_mock.json_data = { + '@odata.context': '/redfish/v1/$metadata#Manager.Manager', + '@odata.id': '/redfish/v1/Managers/iDRAC.Embedded.1', + '@odata.type': '#Manager.v1_18_0.Manager', + 'FirmwareVersion': '7.10' + } + redfish_connection_mock_for_storage_volume.root_uri = REDFISH + ver = self.module.is_fw_ver_greater(redfish_connection_mock_for_storage_volume) + if ver is True: + assert ver is True + else: + assert ver is False -- cgit v1.2.3