1# -*- coding: utf-8 -*- 2 3# 4# Dell EMC OpenManage Ansible Modules 5# Version 3.5.0 6# Copyright (C) 2021 Dell Inc. or its subsidiaries. All Rights Reserved. 7 8# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 9# 10 11from __future__ import (absolute_import, division, print_function) 12 13__metaclass__ = type 14 15import json 16import pytest 17from ssl import SSLError 18from io import StringIO 19from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError 20from ansible.module_utils.urls import ConnectionError, SSLValidationError 21from ansible.module_utils._text import to_text 22from ansible_collections.dellemc.openmanage.plugins.modules import ome_groups 23from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule, Constants 24 25MULTIPLE_GROUPS_MSG = "Provide only one unique device group when state is present." 26NONEXIST_GROUP_ID = "A device group with the provided ID does not exist." 27NONEXIST_PARENT_ID = "A parent device group with the provided ID does not exist." 28INVALID_PARENT = "The provided parent device group is not a valid user-defined static device group." 29INVALID_GROUPS_DELETE = "Provide valid static device group(s) for deletion." 30INVALID_GROUPS_MODIFY = "Provide valid static device group for modification." 31PARENT_CREATION_FAILED = "Unable to create a parent device group with the name {pname}." 32PARENT_IN_SUBTREE = "The parent group is already under the provided group." 33CREATE_SUCCESS = "Successfully {op}d the device group." 34GROUP_PARENT_SAME = "Provided parent and the device group cannot be the same." 35DELETE_SUCCESS = "Successfully deleted the device group(s)." 36NO_CHANGES_MSG = "No changes found to be applied." 37CHANGES_FOUND = "Changes found to be applied." 38STATIC_ROOT = 'Static Groups' 39SETTLING_TIME = 2 40 41MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.ome_groups.' 42 43 44@pytest.fixture 45def ome_connection_mock_for_groups(mocker, ome_response_mock): 46 connection_class_mock = mocker.patch('ansible_collections.dellemc.openmanage.plugins.modules.ome_groups.RestOME') 47 ome_connection_mock_obj = connection_class_mock.return_value.__enter__.return_value 48 ome_connection_mock_obj.invoke_request.return_value = ome_response_mock 49 return ome_connection_mock_obj 50 51 52class TestOmeGroups(FakeAnsibleModule): 53 module = ome_groups 54 55 @pytest.mark.parametrize("params", [ 56 {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]}, 57 'message': DELETE_SUCCESS, "success": True, 'mparams': {'name': 'g1', 'state': 'absent'}}, 58 {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]}, 59 'message': DELETE_SUCCESS, "success": True, 'mparams': {'name': 'g1', 'state': 'absent'}}, 60 {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]}, 61 'message': CHANGES_FOUND, "success": True, 'mparams': {'group_id': 24, 'state': 'absent'}, 'check_mode': True}, 62 {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]}, 63 'message': NO_CHANGES_MSG, "success": True, 'mparams': {'name': 'g2', 'state': 'absent'}}, 64 {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]}, 65 'message': NO_CHANGES_MSG, "success": True, 'mparams': {'name': 'g2', 'state': 'absent'}, 'check_mode': True}]) 66 def test_ome_groups_delete(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args, 67 module_mock): 68 ome_response_mock.success = params.get("success", True) 69 ome_response_mock.json_data = params['json_data'] 70 ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data'] 71 ome_default_args.update(params['mparams']) 72 result = self._run_module(ome_default_args, check_mode=params.get('check_mode', False)) 73 assert result['msg'] == params['message'] 74 75 @pytest.mark.parametrize("params", [{"json_data": { 76 "value": [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 77 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 78 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 79 'message': CREATE_SUCCESS, "success": True, 80 'mparams': {'name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'}, 81 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, 82 {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 83 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 84 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 85 'message': CREATE_SUCCESS, "success": True, 86 'mparams': {'name': 'g1', 'parent_group_name': 'gp21', 'description': 'My group described'}, 'return_data': 22, 87 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, 88 {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 89 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 90 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 91 'message': CREATE_SUCCESS, "success": True, 92 'mparams': {'name': 'g1', 'parent_group_id': 25, 'description': 'My group described'}, 'return_data': 22, 93 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, 94 {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 95 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 96 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 97 'message': CREATE_SUCCESS, "success": True, 98 'mparams': {'name': 'g1', 'parent_group_name': 'Static Groups', 'description': 'My group described'}, 99 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 1, 'MembershipTypeId': 12}}, 100 {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 101 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 102 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 103 'message': CREATE_SUCCESS, "success": True, 104 'mparams': {'name': 'g1', 'parent_group_id': 1, 'description': 'My group described'}, 'return_data': 22, 105 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 1, 'MembershipTypeId': 12}}, 106 {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 107 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 108 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 109 'message': CHANGES_FOUND, "success": True, 110 'mparams': {'name': 'g1', 'parent_group_name': 'gp21', 'description': 'My group described'}, 'return_data': 22, 111 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}, 'check_mode': True}]) 112 def test_ome_groups_create(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args, 113 module_mock, mocker): 114 ome_response_mock.success = params.get("success", True) 115 ome_response_mock.json_data = params['return_data'] 116 ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data'] 117 ome_connection_mock_for_groups.strip_substr_dict.return_value = params.get('created_group', {}) 118 mocker.patch(MODULE_PATH + 'get_ome_group_by_id', return_value=params.get('created_group', {})) 119 mocker.patch(MODULE_PATH + 'create_parent', return_value=params['created_group'].get('ParentId')) 120 ome_default_args.update(params['mparams']) 121 result = self._run_module(ome_default_args, check_mode=params.get('check_mode', False)) 122 assert result['msg'] == (params['message']).format(op='create') 123 124 @pytest.mark.parametrize("params", [{"json_data": { 125 'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12, 'description': 'My group described'}, 126 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 127 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 128 'message': CREATE_SUCCESS, "success": True, 129 'mparams': {'name': 'g1', 'new_name': 'j1', 'parent_group_name': 'gp1', 'description': 'description modified'}, 130 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 131 "json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12, 132 'description': 'My group described'}, 133 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 134 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 135 'message': CHANGES_FOUND, "success": True, 136 'mparams': {'name': 'g1', 'parent_group_name': 'gp1', 'description': 'description modified'}, 'return_data': 22, 137 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}, 'check_mode': True}, { 138 "json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12, 139 'Description': 'My group described'}, 140 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 141 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 142 'message': NO_CHANGES_MSG, "success": True, 143 'mparams': {'name': 'g1', 'new_name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'}, 144 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12}, 145 'check_mode': True}, ]) 146 def test_ome_groups_modify(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args, 147 module_mock, mocker): 148 ome_response_mock.success = params.get("success", True) 149 ome_response_mock.json_data = params['return_data'] 150 ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data'] 151 ome_connection_mock_for_groups.strip_substr_dict.return_value = params.get('created_group', {}) 152 mocker.patch(MODULE_PATH + 'get_ome_group_by_id', return_value=params.get('created_group', {})) 153 mocker.patch(MODULE_PATH + 'create_parent', return_value=params['created_group'].get('ParentId')) 154 # mocker.patch(MODULE_PATH + 'is_parent_in_subtree', return_value=False) 155 ome_default_args.update(params['mparams']) 156 result = self._run_module(ome_default_args, check_mode=params.get('check_mode', False)) 157 assert result['msg'] == (params['message']).format(op='update') 158 159 @pytest.mark.parametrize("params", [{"json_data": { 160 'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 161 {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24}, 162 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 163 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 164 'message': MULTIPLE_GROUPS_MSG, "success": True, 'mparams': {'name': ['g1', 'g3'], 'parent_group_name': 'gp1', 165 'description': 'State present and multiple groups'}, 166 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 167 "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 168 {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24}, 169 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 170 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 171 'message': NONEXIST_GROUP_ID, "success": True, 172 'mparams': {'group_id': 13, 'parent_group_name': 'gp1', 'description': 'State present and no group_id'}, 173 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 174 "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 175 {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24}, 176 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 177 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 178 'message': INVALID_PARENT, "success": True, 179 'mparams': {'name': 'g1', 'parent_group_name': 'g3', 'description': 'State present and invalid parent'}, 180 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 181 "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 182 {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24}, 183 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 184 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 185 'message': INVALID_GROUPS_DELETE, "success": True, 186 'mparams': {'name': ['g1', 'g3'], 'state': 'absent', 'description': 'State absent and invalid group'}, 187 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 188 "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 189 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 190 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 191 'message': NONEXIST_PARENT_ID, "success": True, 192 'mparams': {'name': 'g1', 'parent_group_id': 26, 'description': 'create with non exist parent id'}, 193 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 194 "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 2000, 'MembershipTypeId': 24}, 195 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 196 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 197 'message': INVALID_PARENT, "success": True, 198 'mparams': {'name': 'g1', 'parent_group_id': 24, 'description': 'create with non exist parent id'}, 199 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, { 200 "json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 2000, 'ParentId': 25, 'MembershipTypeId': 24, 201 'Description': 'My group described'}, 202 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12}, 203 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 204 'message': INVALID_GROUPS_MODIFY, "success": True, 205 'mparams': {'name': 'g1', 'new_name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'}, 206 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12}, 207 'check_mode': True}, 208 {"json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12, 209 'Description': 'My group described'}, {'Name': 'gp1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 210 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 211 'message': GROUP_PARENT_SAME, "success": True, 212 'mparams': {'name': 'g1', 'new_name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'}, 213 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12}, 214 'check_mode': True}, 215 {"json_data": {'value': [{'Name': 'x1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12, 216 'Description': 'My group described'}, 217 {'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}, 218 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]}, 219 'message': GROUP_PARENT_SAME, "success": True, 220 'mparams': {'name': 'g1', 'parent_group_name': 'g1', 'description': 'My group described'}, 221 'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12}, 222 'check_mode': True}]) 223 def test_ome_groups_fail_jsons(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args, 224 module_mock, mocker): 225 ome_response_mock.success = params.get("success", True) 226 ome_response_mock.json_data = params['return_data'] 227 ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data'] 228 ome_connection_mock_for_groups.strip_substr_dict.return_value = params.get('created_group', {}) 229 mocker.patch(MODULE_PATH + 'get_ome_group_by_id', return_value=params.get('created_group', {})) 230 mocker.patch(MODULE_PATH + 'create_parent', return_value=params['created_group'].get('ParentId')) 231 ome_default_args.update(params['mparams']) 232 result = self._run_module_with_fail_json(ome_default_args) 233 assert result['msg'] == params['message'] 234 235 @pytest.mark.parametrize("params", [{"json_data": 12, "mparams": {'name': 'g1', 'parent_group_name': 'gp21'}}]) 236 def test_create_parent(self, params, ome_connection_mock_for_groups, ome_response_mock): 237 ome_response_mock.success = params.get("success", True) 238 ome_response_mock.json_data = params["json_data"] 239 f_module = self.get_module_mock(params=params['mparams']) 240 static_root = {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12} 241 group_id = self.module.create_parent(ome_connection_mock_for_groups, f_module, static_root) 242 assert group_id == params['json_data'] 243 244 @pytest.mark.parametrize("params", 245 [{"json_data": {'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}}]) 246 def test_get_ome_group_by_id(self, params, ome_connection_mock_for_groups, ome_response_mock): 247 ome_response_mock.success = params.get("success", True) 248 ome_response_mock.json_data = params["json_data"] 249 group = self.module.get_ome_group_by_id(ome_connection_mock_for_groups, 24) 250 assert group == params['json_data'] 251 252 @pytest.mark.parametrize("exc_type", 253 [IOError, ValueError, SSLError, TypeError, ConnectionError, HTTPError, URLError]) 254 def test_ome_groups_main_exception_failure_case(self, exc_type, mocker, ome_default_args, 255 ome_connection_mock_for_groups, ome_response_mock): 256 ome_default_args.update({"state": "absent", "name": "t1"}) 257 ome_response_mock.status_code = 400 258 ome_response_mock.success = False 259 json_str = to_text(json.dumps({"info": "error_details"})) 260 if exc_type == URLError: 261 mocker.patch(MODULE_PATH + 'get_valid_groups', side_effect=exc_type("url open error")) 262 result = self._run_module(ome_default_args) 263 assert result["unreachable"] is True 264 elif exc_type not in [HTTPError, SSLValidationError]: 265 mocker.patch(MODULE_PATH + 'get_valid_groups', side_effect=exc_type("exception message")) 266 result = self._run_module_with_fail_json(ome_default_args) 267 assert result['failed'] is True 268 else: 269 mocker.patch(MODULE_PATH + 'get_valid_groups', 270 side_effect=exc_type('http://testhost.com', 400, 'http error message', 271 {"accept-type": "application/json"}, StringIO(json_str))) 272 result = self._run_module_with_fail_json(ome_default_args) 273 assert result['failed'] is True 274 assert 'msg' in result 275