diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/dellemc/powerflex/tests | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/dellemc/powerflex/tests')
23 files changed, 3672 insertions, 0 deletions
diff --git a/ansible_collections/dellemc/powerflex/tests/requirements.txt b/ansible_collections/dellemc/powerflex/tests/requirements.txt new file mode 100644 index 000000000..3541acd15 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/requirements.txt @@ -0,0 +1,7 @@ +pytest +pytest-xdist +pytest-mock +pytest-cov +pytest-forked +coverage==4.5.4 +mock diff --git a/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.12.txt b/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..c78903cdf --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.12.txt @@ -0,0 +1,11 @@ +plugins/modules/device.py validate-modules:missing-gplv3-license +plugins/modules/sdc.py validate-modules:missing-gplv3-license +plugins/modules/sds.py validate-modules:missing-gplv3-license +plugins/modules/snapshot.py validate-modules:missing-gplv3-license +plugins/modules/storagepool.py validate-modules:missing-gplv3-license +plugins/modules/volume.py validate-modules:missing-gplv3-license +plugins/modules/info.py validate-modules:missing-gplv3-license +plugins/modules/protection_domain.py validate-modules:missing-gplv3-license +plugins/modules/mdm_cluster.py validate-modules:missing-gplv3-license +plugins/modules/replication_consistency_group.py validate-modules:missing-gplv3-license +plugins/modules/replication_pair.py validate-modules:missing-gplv3-license diff --git a/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.13.txt b/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..c78903cdf --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.13.txt @@ -0,0 +1,11 @@ +plugins/modules/device.py validate-modules:missing-gplv3-license +plugins/modules/sdc.py validate-modules:missing-gplv3-license +plugins/modules/sds.py validate-modules:missing-gplv3-license +plugins/modules/snapshot.py validate-modules:missing-gplv3-license +plugins/modules/storagepool.py validate-modules:missing-gplv3-license +plugins/modules/volume.py validate-modules:missing-gplv3-license +plugins/modules/info.py validate-modules:missing-gplv3-license +plugins/modules/protection_domain.py validate-modules:missing-gplv3-license +plugins/modules/mdm_cluster.py validate-modules:missing-gplv3-license +plugins/modules/replication_consistency_group.py validate-modules:missing-gplv3-license +plugins/modules/replication_pair.py validate-modules:missing-gplv3-license diff --git a/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.14.txt b/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..c78903cdf --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/sanity/ignore-2.14.txt @@ -0,0 +1,11 @@ +plugins/modules/device.py validate-modules:missing-gplv3-license +plugins/modules/sdc.py validate-modules:missing-gplv3-license +plugins/modules/sds.py validate-modules:missing-gplv3-license +plugins/modules/snapshot.py validate-modules:missing-gplv3-license +plugins/modules/storagepool.py validate-modules:missing-gplv3-license +plugins/modules/volume.py validate-modules:missing-gplv3-license +plugins/modules/info.py validate-modules:missing-gplv3-license +plugins/modules/protection_domain.py validate-modules:missing-gplv3-license +plugins/modules/mdm_cluster.py validate-modules:missing-gplv3-license +plugins/modules/replication_consistency_group.py validate-modules:missing-gplv3-license +plugins/modules/replication_pair.py validate-modules:missing-gplv3-license diff --git a/ansible_collections/dellemc/powerflex/tests/unit/__init__.py b/ansible_collections/dellemc/powerflex/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/__init__.py diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/__init__.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/__init__.py diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_api_exception.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_api_exception.py new file mode 100644 index 000000000..5128e54b3 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_api_exception.py @@ -0,0 +1,14 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Mock ApiException for Dell Technologies (Dell) PowerFlex Test modules""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockApiException(Exception): + body = "PyPowerFlex Error message" + status = "500" diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_info_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_info_api.py new file mode 100644 index 000000000..e2ef01fe7 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_info_api.py @@ -0,0 +1,240 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of info module on Dell Technologies (Dell) PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_storagepool_api import MockStoragePoolApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_volume_api import MockVolumeApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_replication_consistency_group_api \ + import MockReplicationConsistencyGroupApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_replication_pair_api \ + import MockReplicationPairApi + + +__metaclass__ = type + + +class MockInfoApi: + INFO_COMMON_ARGS = { + "hostname": "**.***.**.***", + "gather_subset": [], + "filters": None + } + + DUMMY_IP = 'xx.xx.xx.xx' + INFO_ARRAY_DETAILS = [ + { + 'systemVersionName': 'DellEMC PowerFlex Version', + 'perfProfile': 'Compact', + 'authenticationMethod': 'Native', + 'capacityAlertHighThresholdPercent': 80, + 'capacityAlertCriticalThresholdPercent': 90, + 'upgradeState': 'NoUpgrade', + 'remoteReadOnlyLimitState': False, + 'mdmManagementPort': 6611, + 'mdmExternalPort': 7611, + 'sdcMdmNetworkDisconnectionsCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'sdcSdsNetworkDisconnectionsCounterParameters': { + 'shortWindow': { + 'threshold': 800, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 4000, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 20000, + 'windowSizeInSec': 86400 + } + }, + 'sdcMemoryAllocationFailuresCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'sdcSocketAllocationFailuresCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'sdcLongOperationsCounterParameters': { + 'shortWindow': { + 'threshold': 10000, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 100000, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 1000000, + 'windowSizeInSec': 86400 + } + }, + 'cliPasswordAllowed': True, + 'managementClientSecureCommunicationEnabled': True, + 'tlsVersion': 'TLSv1.2', + 'showGuid': True, + 'defragmentationEnabled': True, + 'mdmSecurityPolicy': 'None', + 'mdmCluster': { + 'clusterState': 'ClusteredNormal', + 'clusterMode': 'ThreeNodes', + 'slaves': [ + { + 'managementIPs': [ + DUMMY_IP + ], + 'ips': [ + DUMMY_IP + ], + 'versionInfo': '', + 'virtualInterfaces': [ + '' + ], + 'opensslVersion': 'OpenSSL 26 Jan 2017', + 'role': 'Manager', + 'status': 'Normal', + 'name': 'test_node1_MDM', + 'id': 'test_id_1', + 'port': 0000 + } + ], + 'goodNodesNum': 3, + 'master': { + 'managementIPs': [ + DUMMY_IP + ], + 'ips': [ + DUMMY_IP + ], + 'versionInfo': 'R3_6.0.0', + 'virtualInterfaces': [ + 'ens192' + ], + 'opensslVersion': 'OpenSSL26 Jan 2017', + 'role': 'Manager', + 'status': 'Normal', + 'name': 'test_node_0', + 'id': 'test_id_2', + 'port': 0000 + }, + 'tieBreakers': [ + { + 'managementIPs': [ + DUMMY_IP + ], + 'ips': [ + DUMMY_IP + ], + 'versionInfo': '', + 'opensslVersion': 'N/A', + 'role': 'TieBreaker', + 'status': 'Normal', + 'id': 'test_id_3', + 'port': 0000 + } + ], + 'goodReplicasNum': 2, + 'id': '' + }, + 'sdcSdsConnectivityInfo': { + 'clientServerConnectivityStatus': 'AllConnected', + 'disconnectedClientId': None, + 'disconnectedClientName': None, + 'disconnectedServerId': None, + 'disconnectedServerName': None, + 'disconnectedServerIp': None + }, + 'addressSpaceUsage': 'Normal', + 'lastUpgradeTime': 0, + 'sdcSdrConnectivityInfo': { + 'clientServerConnectivityStatus': 'AllConnected', + 'disconnectedClientId': None, + 'disconnectedClientName': None, + 'disconnectedServerId': None, + 'disconnectedServerName': None, + 'disconnectedServerIp': None + }, + 'sdrSdsConnectivityInfo': { + 'clientServerConnectivityStatus': 'AllConnected', + 'disconnectedClientId': None, + 'disconnectedClientName': None, + 'disconnectedServerId': None, + 'disconnectedServerName': None, + 'disconnectedServerIp': None + }, + 'isInitialLicense': False, + 'capacityTimeLeftInDays': '253', + 'swid': 'abcdXXX', + 'installId': 'id_111', + 'restrictedSdcModeEnabled': False, + 'restrictedSdcMode': 'None', + 'enterpriseFeaturesEnabled': True, + 'daysInstalled': 112, + 'maxCapacityInGb': '5120', + 'id': 'id_222' + } + ] + + INFO_VOLUME_GET_LIST = MockVolumeApi.VOLUME_GET_LIST + + INFO_VOLUME_STATISTICS = { + 'test_vol_id_1': MockVolumeApi.VOLUME_STATISTICS + } + + INFO_STORAGE_POOL_GET_LIST = MockStoragePoolApi.STORAGE_POOL_GET_LIST + + INFO_STORAGE_POOL_STATISTICS = { + 'test_pool_id_1': MockStoragePoolApi.STORAGE_POOL_STATISTICS + } + + RCG_LIST = MockReplicationConsistencyGroupApi.get_rcg_details() + PAIR_LIST = MockReplicationPairApi.get_pair_details() + + @staticmethod + def get_exception_response(response_type): + if response_type == 'volume_get_details': + return "Get volumes list from powerflex array failed with error " + elif response_type == 'sp_get_details': + return "Get storage pool list from powerflex array failed with error " + elif response_type == 'rcg_get_details': + return "Get replication consistency group list from powerflex array failed with error " + elif response_type == 'replication_pair_get_details': + return "Get replication pair list from powerflex array failed with error " diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_mdm_cluster_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_mdm_cluster_api.py new file mode 100644 index 000000000..e2966fad8 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_mdm_cluster_api.py @@ -0,0 +1,403 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of MDM cluster module on PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockMdmClusterApi: + MODULE_PATH = 'ansible_collections.dellemc.powerflex.plugins.modules.mdm_cluster.PowerFlexMdmCluster' + MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell.utils' + + MDM_CLUSTER_COMMON_ARGS = { + "hostname": "**.***.**.***", + "mdm_id": None, + "mdm_name": None, + "mdm_new_name": None, + "performance_profile": None, + "standby_mdm": None, + "is_primary": None, + "cluster_mode": None, + "mdm": None, + "mdm_state": None, + "virtual_ip_interfaces": None, + "clear_interfaces": None, + 'state': None + } + + MDM_NAME = "mdm_node1" + MDM_NAME_STB_MGR = "mdm_node_mgr" + MDM_ID = "5908d328581d1401" + STB_TB_MDM_ID = "5908d328581d1403" + STB_MGR_MDM_ID = "36279b98215e5a04" + IP_1 = "10.x.y.z" + IP_2 = "10.x.x.z" + IP_3 = "10.x.z.z" + IP_4 = "10.x.y.y" + SSL_VERSION = "OpenSSL 1.0.2k-fips 26 Jan 2017" + SYS_VERSION = "DellEMC PowerFlex Version: R3_6.0.354" + + THREE_MDM_CLUSTER_DETAILS = { + "clusterState": "ClusteredNormal", + "clusterMode": "ThreeNodes", + "goodNodesNum": 3, + "master": { + "virtualInterfaces": [ + "ens1" + ], + "managementIPs": [ + IP_1 + ], + "ips": [ + IP_1 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": SSL_VERSION, + "role": "Manager", + "status": "Normal", + "name": "sample_mdm", + "id": "5908d328581d1400", + "port": 9011 + }, + "perfProfile": "HighPerformance", + "slaves": [ + { + "virtualInterfaces": [ + "ens1" + ], + "managementIPs": [ + IP_2 + ], + "ips": [ + IP_2 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": SSL_VERSION, + "role": "Manager", + "status": "Normal", + "name": "sample_mdm1", + "id": MDM_ID, + "port": 9011 + } + ], + "tieBreakers": [ + { + "managementIPs": [], + "ips": [ + IP_4 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "TieBreaker", + "status": "Normal", + "id": "5908d328581d1402", + "port": 9011 + } + ], + "standbyMDMs": [ + { + "managementIPs": [ + IP_3 + ], + "ips": [ + IP_3 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "TieBreaker", + "status": "Normal", + "name": MDM_NAME, + "id": STB_TB_MDM_ID, + "port": 9011 + }, + { + "virtualInterfaces": [ + "ens12" + ], + "managementIPs": [ + IP_3 + ], + "ips": [ + IP_3 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "Manager", + "status": "Normal", + "name": MDM_NAME_STB_MGR, + "id": STB_MGR_MDM_ID, + "port": 9011 + } + ], + "goodReplicasNum": 2, + "id": "cdd883cf00000002" + } + + THREE_MDM_CLUSTER_DETAILS_2 = { + "clusterState": "ClusteredNormal", + "clusterMode": "ThreeNodes", + "goodNodesNum": 3, + "master": { + "virtualInterfaces": [ + "ens1" + ], + "managementIPs": [ + IP_1 + ], + "ips": [ + IP_1 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": SSL_VERSION, + "role": "Manager", + "status": "Normal", + "name": "sample_mdm", + "id": "5908d328581d1400", + "port": 9011 + }, + "perfProfile": "HighPerformance", + "slaves": [ + { + "virtualInterfaces": [ + "ens1" + ], + "managementIPs": [ + IP_2 + ], + "ips": [ + IP_2 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": SSL_VERSION, + "role": "Manager", + "status": "Normal", + "name": "sample_mdm1", + "id": MDM_ID, + "port": 9011 + } + ], + "tieBreakers": [ + { + "managementIPs": [], + "ips": [ + IP_4 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "TieBreaker", + "status": "Normal", + "id": "5908d328581d1402", + "port": 9011 + } + ], + "goodReplicasNum": 2, + "id": "cdd883cf00000002" + } + + FIVE_MDM_CLUSTER_DETAILS = { + "clusterState": "ClusteredNormal", + "clusterMode": "FiveNodes", + "goodNodesNum": 5, + "master": { + "virtualInterfaces": [ + "ens1" + ], + "managementIPs": [ + IP_1 + ], + "ips": [ + IP_1 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": SSL_VERSION, + "role": "Manager", + "status": "Normal", + "name": "sample_mdm", + "id": "5908d328581d1400", + "port": 9011 + }, + "perfProfile": "HighPerformance", + "slaves": [ + { + "virtualInterfaces": [], + "managementIPs": [ + IP_2 + ], + "ips": [ + IP_2 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": SSL_VERSION, + "role": "Manager", + "status": "Normal", + "name": "sample_mdm11", + "id": MDM_ID, + "port": 9011 + }, + { + "virtualInterfaces": [ + "ens12" + ], + "managementIPs": [ + IP_3 + ], + "ips": [ + IP_3 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "Manager", + "status": "Normal", + "name": MDM_NAME_STB_MGR, + "id": STB_MGR_MDM_ID, + "port": 9011 + } + ], + "tieBreakers": [ + { + "managementIPs": [ + IP_3 + ], + "ips": [ + IP_3 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "TieBreaker", + "status": "Normal", + "name": MDM_NAME, + "id": STB_TB_MDM_ID, + "port": 9011 + }, + { + "managementIPs": [], + "ips": [ + IP_4 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "TieBreaker", + "status": "Normal", + "id": "5908d328581d1402", + "port": 9011 + } + ], + "standbyMDMs": [ + { + "virtualInterfaces": [ + "ens13" + ], + "managementIPs": [ + IP_1 + ], + "ips": [ + IP_1 + ], + "versionInfo": "R3_6.0.0", + "opensslVersion": "N/A", + "role": "Manager", + "status": "Normal", + "name": "mgr_node_2", + "id": "5120af354fb17305", + "port": 9011 + } + ], + "goodReplicasNum": 2, + "id": "cdd883cf00000002" + } + PARTIAL_SYSTEM_DETAILS = [ + { + "systemVersionName": SYS_VERSION, + "perfProfile": "Compact", + "name": "System:3c567fd2298f020f", + "id": "3c567fd2298f020f" + }, + { + "systemVersionName": SYS_VERSION, + "perfProfile": "Compact", + "name": "System:3c567fd2298f0201", + "id": "3c567fd2298f0201" + } + ] + PARTIAL_SYSTEM_DETAILS_1 = [ + { + "systemVersionName": SYS_VERSION, + "perfProfile": "Compact", + "name": "System:3c567fd2298f020f", + "id": "3c567fd2298f020f" + } + ] + + @staticmethod + def get_failed_response(): + return "Failed to get the MDM cluster with error" + + @staticmethod + def rename_failed_response(): + return "Failed to rename the MDM mdm_node1 with error" + + @staticmethod + def perf_profile_failed_response(): + return "Failed to update performance profile to Compact with error" + + @staticmethod + def virtual_ip_interface_failed_response(): + return "Failed to modify the virtual IP interfaces of MDM 5908d328581d1401 with error" + + @staticmethod + def remove_mdm_failed_response(): + return "Failed to remove the standby MDM 5908d328581d1403 from the MDM cluster with error" + + @staticmethod + def add_mdm_failed_response(): + return "Failed to Add a standby MDM with error" + + @staticmethod + def owner_failed_response(): + return "Failed to update the Owner of MDM cluster to MDM sample_mdm1 with error" + + @staticmethod + def switch_mode_failed_response(): + return "Failed to change the MDM cluster mode with error" + + @staticmethod + def system_failed_response(): + return "Failed to get system id with error" + + @staticmethod + def multiple_system_failed_response(): + return "Multiple systems exist on the given host." + + @staticmethod + def remove_mdm_no_id_name_failed_response(): + return "Either mdm_name or mdm_id is required while removing the standby MDM." + + @staticmethod + def without_standby_failed_response(): + return "No Standby MDMs found. To expand cluster size, first add standby MDMs." + + @staticmethod + def no_cluster_failed_response(): + return "MDM cluster not found" + + @staticmethod + def id_none_interface_failed_response(): + return "Please provide mdm_name/mdm_id to modify virtual IP interfaces the MDM" + + @staticmethod + def id_none_rename_failed_response(): + return "Please provide mdm_name/mdm_id to rename the MDM" + + @staticmethod + def id_none_change_owner_failed_response(): + return "Either mdm_name or mdm_id is required while changing ownership of MDM cluster" + + @staticmethod + def new_name_add_mdm_failed_response(): + return "Parameters mdm_id/mdm_new_name are not allowed while adding a standby MDM" diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_protection_domain_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_protection_domain_api.py new file mode 100644 index 000000000..60452ecda --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_protection_domain_api.py @@ -0,0 +1,68 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of protection domain module on Dell Technologies (Dell) PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockProtectionDomainApi: + MODULE_PATH = 'ansible_collections.dellemc.powerflex.plugins.modules.protection_domain.PowerFlexProtectionDomain' + MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell.utils' + + PROTECTION_DOMAIN = { + "protectiondomain": [ + { + "id": "7bd6457000000000", + "name": "test_domain", + "protectionDomainState": "Active", + "overallIoNetworkThrottlingInKbps": 20480, + "rebalanceNetworkThrottlingInKbps": 10240, + "rebuildNetworkThrottlingInKbps": 10240, + "vtreeMigrationNetworkThrottlingInKbps": 10240, + "rfcacheEnabled": "false", + "rfcacheMaxIoSizeKb": 128, + "rfcacheOpertionalMode": "None", + "rfcachePageSizeKb": 64, + "storagePools": [ + { + "id": "8d1cba1700000000", + "name": "pool1" + } + ] + } + ] + } + STORAGE_POOL = { + "storagepool": [ + { + "protectionDomainId": "7bd6457000000000", + "rebuildEnabled": True, + "mediaType": "HDD", + "name": "pool1", + "id": "8d1cba1700000000" + } + ] + } + + @staticmethod + def modify_pd_with_failed_msg(protection_domain_name): + return "Failed to update the rf cache limits of protection domain " + protection_domain_name + " with error " + + @staticmethod + def delete_pd_failed_msg(protection_domain_id): + return "Delete protection domain '" + protection_domain_id + "' operation failed with error ''" + + @staticmethod + def rename_pd_failed_msg(protection_domain_name): + return "Failed to update the protection domain " + protection_domain_name + " with error " + + @staticmethod + def version_pd_failed_msg(): + return "Getting PyPowerFlex SDK version, failed with Error The 'PyPowerFlex' distribution was " \ + "not found and is required by the application" diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_replication_consistency_group_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_replication_consistency_group_api.py new file mode 100644 index 000000000..6671fd875 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_replication_consistency_group_api.py @@ -0,0 +1,70 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of volume module on Dell Technologies (Dell) PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockReplicationConsistencyGroupApi: + RCG_COMMON_ARGS = { + "hostname": "**.***.**.***", + "rcg_name": None, + "rcg_id": None, + "create_snapshot": None, "new_rcg_name": None, + "rpo": None, "protection_domain_name": None, "protection_domain_id": None, + "activity_mode": None, "pause": None, "pause_mode": None, "freeze": None, + "remote_peer": {"hostname": None, "username": None, "password": None, + "verifycert": None, "port": None, "protection_domain_name": None, + "protection_domain_id": None}, + "target_volume_access_mode": None, "is_consistent": None, + "state": None + } + RCG_ID = "aadc17d500000000" + FAIL_MSG = " failed with error" + + @staticmethod + def get_rcg_details(pause_mode="None", freeze_state="Unfrozen", activity_mode="Active", consistency="Consistent"): + return [{"protectionDomainId": "b969400500000000", + "peerMdmId": "6c3d94f600000000", + "remoteId": "2130961a00000000", + "remoteMdmId": "0e7a082862fedf0f", + "currConsistMode": consistency, + "freezeState": freeze_state, + "lifetimeState": "Normal", + "pauseMode": pause_mode, + "snapCreationInProgress": False, + "lastSnapGroupId": "e58280b300000001", + "lastSnapCreationRc": "SUCCESS", + "targetVolumeAccessMode": "NoAccess", + "remoteProtectionDomainId": "4eeb304600000000", + "remoteProtectionDomainName": "domain1", + "failoverType": "None", + "failoverState": "None", + "activeLocal": True, + "activeRemote": True, + "abstractState": "Ok", + "localActivityState": activity_mode, + "remoteActivityState": "Active", + "inactiveReason": 11, + "rpoInSeconds": 30, + "replicationDirection": "LocalToRemote", + "disasterRecoveryState": "None", + "remoteDisasterRecoveryState": "None", + "error": 65, + "name": "test_rcg", + "type": "User", + "id": "aadc17d500000000"}] + + @staticmethod + def get_exception_response(response_type): + return "Failed to get the replication consistency group " + + @staticmethod + def create_snapshot_exception_response(response_type, rcg_id): + return "Create RCG snapshot for RCG with id " + rcg_id + " operation failed" diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_replication_pair_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_replication_pair_api.py new file mode 100644 index 000000000..f621db47e --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_replication_pair_api.py @@ -0,0 +1,50 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of replication pair module on Dell Technologies (Dell) PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockReplicationPairApi: + REPLICATION_PAIR_COMMON_ARGS = { + "hostname": "**.***.**.***", + "rcg_name": None, "rcg_id": None, + "pair_id": None, "pair_name": None, + "pairs": [{"source_volume_name": None, "source_volume_id": None, "target_volume_name": None, + "target_volume_id": None}], "pause": None, + "remote_peer": {"hostname": None, "username": None, "password": None, + "verifycert": None, "port": None}, "state": None + } + PAIR_ID = "23aa0bc900000001" + FAIL_MSG = " failed with error" + + @staticmethod + def get_pair_details(copy_state="Done"): + return [{"copyType": "OnlineCopy", + "id": "23aa0bc900000001", + "initialCopyPriority": -1, + "initialCopyState": copy_state, + "lifetimeState": "Normal", + "localActivityState": "RplEnabled", + "localVolumeId": "e2bc1fab00000008", + "name": None, + "peerSystemName": None, + "remoteActivityState": "RplEnabled", + "remoteCapacityInMB": 8192, + "remoteId": "a058446700000001", + "remoteVolumeId": "1cda7af20000000d", + "remoteVolumeName": "vol", + "replicationConsistencyGroupId": "e2ce036b00000002", + "userRequestedPauseTransmitInitCopy": False, + "links": []}] + + @staticmethod + def get_volume_details(): + return [{"id": "0001", + "name": "volume1"}] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_sdk_response.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_sdk_response.py new file mode 100644 index 000000000..9e47f4ba5 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_sdk_response.py @@ -0,0 +1,15 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Mock SDKResponse for Unit tests for Dell Technologies (Dell) PowerFlex modules""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockSDKResponse: + def __init__(self, data=None, status_code=200): + self.data = data + self.status_code = status_code diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_storagepool_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_storagepool_api.py new file mode 100644 index 000000000..0246b9dd4 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_storagepool_api.py @@ -0,0 +1,467 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of storage pool module on Dell Technologies (Dell) PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockStoragePoolApi: + STORAGE_POOL_COMMON_ARGS = { + "hostname": "**.***.**.***", + "storage_pool_name": None, + "storage_pool_id": None, + "storage_pool_new_name": None, + "protection_domain_name": None, + "protection_domain_id": None, + "use_rmcache": None, + "use_rfcache": None, + "media_type": None, + 'state': None + } + + STORAGE_POOL_GET_LIST = [ + { + 'protectionDomainId': '4eeb304600000000', + 'rebuildEnabled': True, + 'dataLayout': 'MediumGranularity', + 'persistentChecksumState': 'Protected', + 'addressSpaceUsage': 'Normal', + 'externalAccelerationType': 'None', + 'rebalanceEnabled': True, + 'sparePercentage': 10, + 'rmcacheWriteHandlingMode': 'Cached', + 'checksumEnabled': False, + 'useRfcache': False, + 'compressionMethod': 'Invalid', + 'fragmentationEnabled': True, + 'numOfParallelRebuildRebalanceJobsPerDevice': 2, + 'capacityAlertHighThreshold': 80, + 'capacityAlertCriticalThreshold': 90, + 'capacityUsageState': 'Normal', + 'capacityUsageType': 'NetCapacity', + 'addressSpaceUsageType': 'DeviceCapacityLimit', + 'bgScannerCompareErrorAction': 'ReportAndFix', + 'bgScannerReadErrorAction': 'ReportAndFix', + 'fglExtraCapacity': None, + 'fglOverProvisioningFactor': None, + 'fglWriteAtomicitySize': None, + 'fglMaxCompressionRatio': None, + 'fglPerfProfile': None, + 'replicationCapacityMaxRatio': 0, + 'persistentChecksumEnabled': True, + 'persistentChecksumBuilderLimitKb': 3072, + 'persistentChecksumValidateOnRead': False, + 'useRmcache': False, + 'fglAccpId': None, + 'rebuildIoPriorityPolicy': 'limitNumOfConcurrentIos', + 'rebalanceIoPriorityPolicy': 'favorAppIos', + 'vtreeMigrationIoPriorityPolicy': 'favorAppIos', + 'protectedMaintenanceModeIoPriorityPolicy': 'limitNumOfConcurrentIos', + 'rebuildIoPriorityNumOfConcurrentIosPerDevice': 1, + 'rebalanceIoPriorityNumOfConcurrentIosPerDevice': 1, + 'vtreeMigrationIoPriorityNumOfConcurrentIosPerDevice': 1, + 'protectedMaintenanceModeIoPriorityNumOfConcurrentIosPerDevice': 1, + 'rebuildIoPriorityBwLimitPerDeviceInKbps': 10240, + 'rebalanceIoPriorityBwLimitPerDeviceInKbps': 10240, + 'vtreeMigrationIoPriorityBwLimitPerDeviceInKbps': 10240, + 'protectedMaintenanceModeIoPriorityBwLimitPerDeviceInKbps': 10240, + 'rebuildIoPriorityAppIopsPerDeviceThreshold': None, + 'rebalanceIoPriorityAppIopsPerDeviceThreshold': None, + 'vtreeMigrationIoPriorityAppIopsPerDeviceThreshold': None, + 'protectedMaintenanceModeIoPriorityAppIopsPerDeviceThreshold': None, + 'rebuildIoPriorityAppBwPerDeviceThresholdInKbps': None, + 'rebalanceIoPriorityAppBwPerDeviceThresholdInKbps': None, + 'vtreeMigrationIoPriorityAppBwPerDeviceThresholdInKbps': None, + 'protectedMaintenanceModeIoPriorityAppBwPerDeviceThresholdInKbps': None, + 'rebuildIoPriorityQuietPeriodInMsec': None, + 'rebalanceIoPriorityQuietPeriodInMsec': None, + 'vtreeMigrationIoPriorityQuietPeriodInMsec': None, + 'protectedMaintenanceModeIoPriorityQuietPeriodInMsec': None, + 'zeroPaddingEnabled': True, + 'backgroundScannerMode': 'DataComparison', + 'backgroundScannerBWLimitKBps': 3072, + 'fglMetadataSizeXx100': None, + 'fglNvdimmWriteCacheSizeInMb': None, + 'fglNvdimmMetadataAmortizationX100': None, + 'mediaType': 'HDD', + 'name': 'test_pool', + 'id': 'test_pool_id_1' + } + ] + + STORAGE_POOL_STATISTICS = { + 'backgroundScanFixedReadErrorCount': 0, + 'pendingMovingOutBckRebuildJobs': 0, + 'degradedHealthyCapacityInKb': 0, + 'activeMovingOutFwdRebuildJobs': 0, + 'bckRebuildWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netFglUncompressedDataSizeInKb': 0, + 'primaryReadFromDevBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'BackgroundScannedInMB': 3209584, + 'volumeIds': [ + 'test_vol_id_1' + ], + 'maxUserDataCapacityInKb': 761204736, + 'persistentChecksumBuilderProgress': 100.0, + 'rfcacheReadsSkippedAlignedSizeTooLarge': 0, + 'pendingMovingInRebalanceJobs': 0, + 'rfcacheWritesSkippedHeavyLoad': 0, + 'unusedCapacityInKb': 761204736, + 'userDataSdcReadLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'totalReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'numOfDeviceAtFaultRebuilds': 0, + 'totalWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'persistentChecksumCapacityInKb': 414720, + 'rmPendingAllocatedInKb': 0, + 'numOfVolumes': 1, + 'rfcacheIosOutstanding': 0, + 'capacityAvailableForVolumeAllocationInKb': 377487360, + 'numOfMappedToAllVolumes': 0, + 'netThinUserDataCapacityInKb': 0, + 'backgroundScanFixedCompareErrorCount': 0, + 'volMigrationWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'thinAndSnapshotRatio': 'Infinity', + 'fglUserDataCapacityInKb': 0, + 'pendingMovingInEnterProtectedMaintenanceModeJobs': 0, + 'activeMovingInNormRebuildJobs': 0, + 'aggregateCompressionLevel': 'Uncompressed', + 'targetOtherLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netUserDataCapacityInKb': 0, + 'pendingMovingOutExitProtectedMaintenanceModeJobs': 0, + 'overallUsageRatio': 'Infinity', + 'volMigrationReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netCapacityInUseNoOverheadInKb': 0, + 'pendingMovingInBckRebuildJobs': 0, + 'rfcacheReadsSkippedInternalError': 0, + 'activeBckRebuildCapacityInKb': 0, + 'rebalanceCapacityInKb': 0, + 'pendingMovingInExitProtectedMaintenanceModeJobs': 0, + 'rfcacheReadsSkippedLowResources': 0, + 'rplJournalCapAllowed': 0, + 'thinCapacityInUseInKb': 0, + 'userDataSdcTrimLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'activeMovingInEnterProtectedMaintenanceModeJobs': 0, + 'rfcacheWritesSkippedInternalError': 0, + 'netUserDataCapacityNoTrimInKb': 0, + 'rfcacheWritesSkippedCacheMiss': 0, + 'degradedFailedCapacityInKb': 0, + 'activeNormRebuildCapacityInKb': 0, + 'fglSparesInKb': 0, + 'snapCapacityInUseInKb': 0, + 'numOfMigratingVolumes': 0, + 'compressionRatio': 0.0, + 'rfcacheWriteMiss': 0, + 'primaryReadFromRmcacheBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'migratingVtreeIds': [ + ], + 'numOfVtrees': 1, + 'userDataCapacityNoTrimInKb': 0, + 'rfacheReadHit': 0, + 'compressedDataCompressionRatio': 0.0, + 'rplUsedJournalCap': 0, + 'pendingMovingCapacityInKb': 0, + 'numOfSnapshots': 0, + 'pendingFwdRebuildCapacityInKb': 0, + 'tempCapacityInKb': 0, + 'totalFglMigrationSizeInKb': 0, + 'normRebuildCapacityInKb': 0, + 'logWrittenBlocksInKb': 0, + 'primaryWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'numOfThickBaseVolumes': 0, + 'enterProtectedMaintenanceModeReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'activeRebalanceCapacityInKb': 0, + 'numOfReplicationJournalVolumes': 0, + 'rfcacheReadsSkippedLockIos': 0, + 'unreachableUnusedCapacityInKb': 0, + 'netProvisionedAddressesInKb': 0, + 'trimmedUserDataCapacityInKb': 0, + 'provisionedAddressesInKb': 0, + 'numOfVolumesInDeletion': 0, + 'pendingMovingOutFwdRebuildJobs': 0, + 'maxCapacityInKb': 845783040, + 'rmPendingThickInKb': 0, + 'protectedCapacityInKb': 0, + 'secondaryWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'normRebuildReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'thinCapacityAllocatedInKb': 16777216, + 'netFglUserDataCapacityInKb': 0, + 'metadataOverheadInKb': 0, + 'thinCapacityAllocatedInKm': 16777216, + 'rebalanceWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'primaryVacInKb': 8388608, + 'deviceIds': [ + 'dv_id_1', + 'dv_id_2', + 'dv_id_3' + ], + 'netSnapshotCapacityInKb': 0, + 'secondaryVacInKb': 8388608, + 'numOfDevices': 3, + 'rplTotalJournalCap': 0, + 'failedCapacityInKb': 0, + 'netMetadataOverheadInKb': 0, + 'activeMovingOutBckRebuildJobs': 0, + 'rfcacheReadsFromCache': 0, + 'activeMovingOutEnterProtectedMaintenanceModeJobs': 0, + 'enterProtectedMaintenanceModeCapacityInKb': 0, + 'pendingMovingInNormRebuildJobs': 0, + 'failedVacInKb': 0, + 'primaryReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'fglUncompressedDataSizeInKb': 0, + 'fglCompressedDataSizeInKb': 0, + 'pendingRebalanceCapacityInKb': 0, + 'rfcacheAvgReadTime': 0, + 'semiProtectedCapacityInKb': 0, + 'pendingMovingOutEnterProtectedMaintenanceModeJobs': 0, + 'mgUserDdataCcapacityInKb': 0, + 'snapshotCapacityInKb': 0, + 'netMgUserDataCapacityInKb': 0, + 'fwdRebuildReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheWritesReceived': 0, + 'netUnusedCapacityInKb': 380602368, + 'thinUserDataCapacityInKb': 0, + 'protectedVacInKb': 16777216, + 'activeMovingRebalanceJobs': 0, + 'bckRebuildCapacityInKb': 0, + 'activeMovingInFwdRebuildJobs': 0, + 'netTrimmedUserDataCapacityInKb': 0, + 'pendingMovingRebalanceJobs': 0, + 'numOfMarkedVolumesForReplication': 0, + 'degradedHealthyVacInKb': 0, + 'semiProtectedVacInKb': 0, + 'userDataReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'pendingBckRebuildCapacityInKb': 0, + 'capacityLimitInKb': 845783040, + 'vtreeIds': [ + 'vtree_id_1' + ], + 'activeMovingCapacityInKb': 1, + 'targetWriteLatency': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'pendingExitProtectedMaintenanceModeCapacityInKb': 1, + 'rfcacheIosSkipped': 1, + 'userDataWriteBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'inMaintenanceVacInKb': 1, + 'exitProtectedMaintenanceModeReadBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'netFglSparesInKb': 1, + 'rfcacheReadsSkipped': 1, + 'activeExitProtectedMaintenanceModeCapacityInKb': 1, + 'activeMovingOutExitProtectedMaintenanceModeJobs': 1, + 'numOfUnmappedVolumes': 2, + 'tempCapacityVacInKb': 1, + 'volumeAddressSpaceInKb': 80000, + 'currentFglMigrationSizeInKb': 1, + 'rfcacheWritesSkippedMaxIoSize': 1, + 'netMaxUserDataCapacityInKb': 380600000, + 'numOfMigratingVtrees': 1, + 'atRestCapacityInKb': 1, + 'rfacheWriteHit': 1, + 'bckRebuildReadBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'rfcacheSourceDeviceWrites': 1, + 'spareCapacityInKb': 84578000, + 'enterProtectedMaintenanceModeWriteBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'rfcacheIoErrors': 1, + 'inaccessibleCapacityInKb': 1, + 'normRebuildWriteBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'capacityInUseInKb': 1, + 'rebalanceReadBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'rfcacheReadsSkippedMaxIoSize': 1, + 'activeMovingInExitProtectedMaintenanceModeJobs': 1, + 'secondaryReadFromDevBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'secondaryReadBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'rfcacheWritesSkippedStuckIo': 1, + 'secondaryReadFromRmcacheBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'inMaintenanceCapacityInKb': 1, + 'exposedCapacityInKb': 1, + 'netFglCompressedDataSizeInKb': 1, + 'userDataSdcWriteLatency': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'inUseVacInKb': 16777000, + 'fwdRebuildCapacityInKb': 1, + 'thickCapacityInUseInKb': 1, + 'backgroundScanReadErrorCount': 1, + 'activeMovingInRebalanceJobs': 1, + 'migratingVolumeIds': [ + '1xxx' + ], + 'rfcacheWritesSkippedLowResources': 1, + 'capacityInUseNoOverheadInKb': 1, + 'exitProtectedMaintenanceModeWriteBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'rfcacheSkippedUnlinedWrite': 1, + 'netCapacityInUseInKb': 1, + 'numOfOutgoingMigrations': 1, + 'rfcacheAvgWriteTime': 1, + 'pendingNormRebuildCapacityInKb': 1, + 'pendingMovingOutNormrebuildJobs': 1, + 'rfcacheSourceDeviceReads': 1, + 'rfcacheReadsPending': 1, + 'volumeAllocationLimitInKb': 3791650000, + 'rfcacheReadsSkippedHeavyLoad': 1, + 'fwdRebuildWriteBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'rfcacheReadMiss': 1, + 'targetReadLatency': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'userDataCapacityInKb': 1, + 'activeMovingInBckRebuildJobs': 1, + 'movingCapacityInKb': 1, + 'activeEnterProtectedMaintenanceModeCapacityInKb': 1, + 'backgroundScanCompareErrorCount': 1, + 'pendingMovingInFwdRebuildJobs': 1, + 'rfcacheReadsReceived': 1, + 'spSdsIds': [ + 'sp_id_1', + 'sp_id_2', + 'sp_id_3' + ], + 'pendingEnterProtectedMaintenanceModeCapacityInKb': 1, + 'vtreeAddresSpaceInKb': 8388000, + 'snapCapacityInUseOccupiedInKb': 1, + 'activeFwdRebuildCapacityInKb': 1, + 'rfcacheReadsSkippedStuckIo': 1, + 'activeMovingOutNormRebuildJobs': 1, + 'rfcacheWritePending': 1, + 'numOfThinBaseVolumes': 2, + 'degradedFailedVacInKb': 1, + 'userDataTrimBwc': { + 'numSeconds': 1, + 'totalWeightInKb': 1, + 'numOccured': 1 + }, + 'numOfIncomingVtreeMigrations': 1 + } + + @staticmethod + def get_exception_response(response_type): + if response_type == 'get_details': + return "Failed to get the storage pool test_pool with error " diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_volume_api.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_volume_api.py new file mode 100644 index 000000000..b05cc84d3 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/module_utils/mock_volume_api.py @@ -0,0 +1,548 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" +Mock Api response for Unit tests of volume module on Dell Technologies (Dell) PowerFlex +""" + +from __future__ import (absolute_import, division, print_function) +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_storagepool_api import MockStoragePoolApi + +__metaclass__ = type + + +class MockVolumeApi: + VOLUME_COMMON_ARGS = { + "hostname": "**.***.**.***", + "vol_name": None, + "vol_id": None, + "vol_type": None, + "compression_type": None, + "storage_pool_name": None, + "storage_pool_id": None, + "protection_domain_name": None, + "protection_domain_id": None, + "snapshot_policy_name": None, + "snapshot_policy_id": None, + "auto_snap_remove_type": None, + "use_rmcache": None, + "size": None, + "cap_unit": None, + "vol_new_name": None, + "sdc": {}, + "sdc_state": None, + "delete_snapshots": None, + "state": None + } + + VOLUME_GET_LIST = [ + { + 'storagePoolId': 'test_pool_id_1', + 'dataLayout': 'MediumGranularity', + 'vtreeId': 'vtree_id_1', + 'sizeInKb': 8388608, + 'snplIdOfAutoSnapshot': None, + 'volumeType': 'ThinProvisioned', + 'consistencyGroupId': None, + 'ancestorVolumeId': None, + 'notGenuineSnapshot': False, + 'accessModeLimit': 'ReadWrite', + 'secureSnapshotExpTime': 0, + 'useRmcache': False, + 'managedBy': 'ScaleIO', + 'lockedAutoSnapshot': False, + 'lockedAutoSnapshotMarkedForRemoval': False, + 'autoSnapshotGroupId': None, + 'compressionMethod': 'Invalid', + 'pairIds': None, + 'timeStampIsAccurate': False, + 'mappedSdcInfo': None, + 'originalExpiryTime': 0, + 'retentionLevels': [ + ], + 'snplIdOfSourceVolume': None, + 'volumeReplicationState': 'UnmarkedForReplication', + 'replicationJournalVolume': False, + 'replicationTimeStamp': 0, + 'creationTime': 1655878090, + 'name': 'testing', + 'id': 'test_id_1' + } + ] + + VOLUME_STORAGEPOOL_DETAILS = MockStoragePoolApi.STORAGE_POOL_GET_LIST[0] + + VOLUME_PD_DETAILS = { + 'rebalanceNetworkThrottlingEnabled': False, + 'vtreeMigrationNetworkThrottlingEnabled': False, + 'overallIoNetworkThrottlingEnabled': False, + 'rfcacheEnabled': True, + 'rfcacheAccpId': None, + 'rebuildNetworkThrottlingEnabled': False, + 'sdrSdsConnectivityInfo': { + 'clientServerConnStatus': 'CLIENT_SERVER_CONN_STATUS_ALL_CONNECTED', + 'disconnectedClientId': None, + 'disconnectedClientName': None, + 'disconnectedServerId': None, + 'disconnectedServerName': None, + 'disconnectedServerIp': None + }, + 'protectionDomainState': 'Active', + 'rebuildNetworkThrottlingInKbps': None, + 'rebalanceNetworkThrottlingInKbps': None, + 'overallIoNetworkThrottlingInKbps': None, + 'vtreeMigrationNetworkThrottlingInKbps': None, + 'sdsDecoupledCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'sdsConfigurationFailureCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'mdmSdsNetworkDisconnectionsCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'sdsSdsNetworkDisconnectionsCounterParameters': { + 'shortWindow': { + 'threshold': 300, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 500, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 700, + 'windowSizeInSec': 86400 + } + }, + 'rfcacheOpertionalMode': 'WriteMiss', + 'rfcachePageSizeKb': 64, + 'rfcacheMaxIoSizeKb': 128, + 'sdsReceiveBufferAllocationFailuresCounterParameters': { + 'shortWindow': { + 'threshold': 20000, + 'windowSizeInSec': 60 + }, + 'mediumWindow': { + 'threshold': 200000, + 'windowSizeInSec': 3600 + }, + 'longWindow': { + 'threshold': 2000000, + 'windowSizeInSec': 86400 + } + }, + 'fglDefaultNumConcurrentWrites': 1000, + 'fglMetadataCacheEnabled': False, + 'fglDefaultMetadataCacheSize': 0, + 'protectedMaintenanceModeNetworkThrottlingEnabled': False, + 'protectedMaintenanceModeNetworkThrottlingInKbps': None, + 'rplCapAlertLevel': 'normal', + 'systemId': 'syst_id_1', + 'name': 'domain1', + 'id': '4eeb304600000000', + } + + VOLUME_STATISTICS = { + 'backgroundScanFixedReadErrorCount': 0, + 'pendingMovingOutBckRebuildJobs': 0, + 'degradedHealthyCapacityInKb': 0, + 'activeMovingOutFwdRebuildJobs': 0, + 'bckRebuildWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netFglUncompressedDataSizeInKb': 0, + 'primaryReadFromDevBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'BackgroundScannedInMB': 3209584, + 'volumeIds': [ + '456ad22e00000003' + ], + 'maxUserDataCapacityInKb': 761204736, + 'persistentChecksumBuilderProgress': 100.0, + 'rfcacheReadsSkippedAlignedSizeTooLarge': 0, + 'pendingMovingInRebalanceJobs': 0, + 'rfcacheWritesSkippedHeavyLoad': 0, + 'unusedCapacityInKb': 761204736, + 'userDataSdcReadLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'totalReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'numOfDeviceAtFaultRebuilds': 0, + 'totalWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'persistentChecksumCapacityInKb': 414720, + 'rmPendingAllocatedInKb': 0, + 'numOfVolumes': 1, + 'rfcacheIosOutstanding': 0, + 'capacityAvailableForVolumeAllocationInKb': 377487360, + 'numOfMappedToAllVolumes': 0, + 'netThinUserDataCapacityInKb': 0, + 'backgroundScanFixedCompareErrorCount': 0, + 'volMigrationWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'thinAndSnapshotRatio': 'Infinity', + 'fglUserDataCapacityInKb': 0, + 'pendingMovingInEnterProtectedMaintenanceModeJobs': 0, + 'activeMovingInNormRebuildJobs': 0, + 'aggregateCompressionLevel': 'Uncompressed', + 'targetOtherLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netUserDataCapacityInKb': 0, + 'pendingMovingOutExitProtectedMaintenanceModeJobs': 0, + 'overallUsageRatio': 'Infinity', + 'volMigrationReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netCapacityInUseNoOverheadInKb': 0, + 'pendingMovingInBckRebuildJobs': 0, + 'rfcacheReadsSkippedInternalError': 0, + 'activeBckRebuildCapacityInKb': 0, + 'rebalanceCapacityInKb': 0, + 'pendingMovingInExitProtectedMaintenanceModeJobs': 0, + 'rfcacheReadsSkippedLowResources': 0, + 'rplJournalCapAllowed': 0, + 'thinCapacityInUseInKb': 0, + 'userDataSdcTrimLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'activeMovingInEnterProtectedMaintenanceModeJobs': 0, + 'rfcacheWritesSkippedInternalError': 0, + 'netUserDataCapacityNoTrimInKb': 0, + 'rfcacheWritesSkippedCacheMiss': 0, + 'degradedFailedCapacityInKb': 0, + 'activeNormRebuildCapacityInKb': 0, + 'fglSparesInKb': 0, + 'snapCapacityInUseInKb': 0, + 'numOfMigratingVolumes': 0, + 'compressionRatio': 0.0, + 'rfcacheWriteMiss': 0, + 'primaryReadFromRmcacheBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'migratingVtreeIds': [ + ], + 'numOfVtrees': 1, + 'userDataCapacityNoTrimInKb': 0, + 'rfacheReadHit': 0, + 'compressedDataCompressionRatio': 0.0, + 'rplUsedJournalCap': 0, + 'pendingMovingCapacityInKb': 0, + 'numOfSnapshots': 0, + 'pendingFwdRebuildCapacityInKb': 0, + 'tempCapacityInKb': 0, + 'totalFglMigrationSizeInKb': 0, + 'normRebuildCapacityInKb': 0, + 'logWrittenBlocksInKb': 0, + 'primaryWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'numOfThickBaseVolumes': 0, + 'enterProtectedMaintenanceModeReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'activeRebalanceCapacityInKb': 0, + 'numOfReplicationJournalVolumes': 0, + 'rfcacheReadsSkippedLockIos': 0, + 'unreachableUnusedCapacityInKb': 0, + 'netProvisionedAddressesInKb': 0, + 'trimmedUserDataCapacityInKb': 0, + 'provisionedAddressesInKb': 0, + 'numOfVolumesInDeletion': 0, + 'pendingMovingOutFwdRebuildJobs': 0, + 'maxCapacityInKb': 845783040, + 'rmPendingThickInKb': 0, + 'protectedCapacityInKb': 0, + 'secondaryWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'normRebuildReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'thinCapacityAllocatedInKb': 16777216, + 'netFglUserDataCapacityInKb': 0, + 'metadataOverheadInKb': 0, + 'thinCapacityAllocatedInKm': 16777216, + 'rebalanceWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'primaryVacInKb': 8388608, + 'deviceIds': [ + 'bbd7580800030001', + 'bbd4580a00040001', + 'bbd5580b00050001' + ], + 'netSnapshotCapacityInKb': 0, + 'secondaryVacInKb': 8388608, + 'numOfDevices': 3, + 'rplTotalJournalCap': 0, + 'failedCapacityInKb': 0, + 'netMetadataOverheadInKb': 0, + 'activeMovingOutBckRebuildJobs': 0, + 'rfcacheReadsFromCache': 0, + 'activeMovingOutEnterProtectedMaintenanceModeJobs': 0, + 'enterProtectedMaintenanceModeCapacityInKb': 0, + 'pendingMovingInNormRebuildJobs': 0, + 'failedVacInKb': 0, + 'primaryReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'fglUncompressedDataSizeInKb': 0, + 'fglCompressedDataSizeInKb': 0, + 'pendingRebalanceCapacityInKb': 0, + 'rfcacheAvgReadTime': 0, + 'semiProtectedCapacityInKb': 0, + 'pendingMovingOutEnterProtectedMaintenanceModeJobs': 0, + 'mgUserDdataCcapacityInKb': 0, + 'snapshotCapacityInKb': 0, + 'netMgUserDataCapacityInKb': 0, + 'fwdRebuildReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheWritesReceived': 0, + 'netUnusedCapacityInKb': 380602368, + 'thinUserDataCapacityInKb': 0, + 'protectedVacInKb': 16777216, + 'activeMovingRebalanceJobs': 0, + 'bckRebuildCapacityInKb': 0, + 'activeMovingInFwdRebuildJobs': 0, + 'netTrimmedUserDataCapacityInKb': 0, + 'pendingMovingRebalanceJobs': 0, + 'numOfMarkedVolumesForReplication': 0, + 'degradedHealthyVacInKb': 0, + 'semiProtectedVacInKb': 0, + 'userDataReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'pendingBckRebuildCapacityInKb': 0, + 'capacityLimitInKb': 845783040, + 'vtreeIds': [ + '32b13de900000003' + ], + 'activeMovingCapacityInKb': 0, + 'targetWriteLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'pendingExitProtectedMaintenanceModeCapacityInKb': 0, + 'rfcacheIosSkipped': 0, + 'userDataWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'inMaintenanceVacInKb': 0, + 'exitProtectedMaintenanceModeReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'netFglSparesInKb': 0, + 'rfcacheReadsSkipped': 0, + 'activeExitProtectedMaintenanceModeCapacityInKb': 0, + 'activeMovingOutExitProtectedMaintenanceModeJobs': 0, + 'numOfUnmappedVolumes': 1, + 'tempCapacityVacInKb': 0, + 'volumeAddressSpaceInKb': 8388608, + 'currentFglMigrationSizeInKb': 0, + 'rfcacheWritesSkippedMaxIoSize': 0, + 'netMaxUserDataCapacityInKb': 380602368, + 'numOfMigratingVtrees': 0, + 'atRestCapacityInKb': 0, + 'rfacheWriteHit': 0, + 'bckRebuildReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheSourceDeviceWrites': 0, + 'spareCapacityInKb': 84578304, + 'enterProtectedMaintenanceModeWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheIoErrors': 0, + 'inaccessibleCapacityInKb': 0, + 'normRebuildWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'capacityInUseInKb': 0, + 'rebalanceReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheReadsSkippedMaxIoSize': 0, + 'activeMovingInExitProtectedMaintenanceModeJobs': 0, + 'secondaryReadFromDevBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'secondaryReadBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheWritesSkippedStuckIo': 0, + 'secondaryReadFromRmcacheBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'inMaintenanceCapacityInKb': 0, + 'exposedCapacityInKb': 0, + 'netFglCompressedDataSizeInKb': 0, + 'userDataSdcWriteLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'inUseVacInKb': 16777216, + 'fwdRebuildCapacityInKb': 0, + 'thickCapacityInUseInKb': 0, + 'backgroundScanReadErrorCount': 0, + 'activeMovingInRebalanceJobs': 0, + 'migratingVolumeIds': [ + ], + 'rfcacheWritesSkippedLowResources': 0, + 'capacityInUseNoOverheadInKb': 0, + 'exitProtectedMaintenanceModeWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheSkippedUnlinedWrite': 0, + 'netCapacityInUseInKb': 0, + 'numOfOutgoingMigrations': 0, + 'rfcacheAvgWriteTime': 0, + 'pendingNormRebuildCapacityInKb': 0, + 'pendingMovingOutNormrebuildJobs': 0, + 'rfcacheSourceDeviceReads': 0, + 'rfcacheReadsPending': 0, + 'volumeAllocationLimitInKb': 3791650816, + 'rfcacheReadsSkippedHeavyLoad': 0, + 'fwdRebuildWriteBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'rfcacheReadMiss': 0, + 'targetReadLatency': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'userDataCapacityInKb': 0, + 'activeMovingInBckRebuildJobs': 0, + 'movingCapacityInKb': 0, + 'activeEnterProtectedMaintenanceModeCapacityInKb': 0, + 'backgroundScanCompareErrorCount': 0, + 'pendingMovingInFwdRebuildJobs': 0, + 'rfcacheReadsReceived': 0, + 'spSdsIds': [ + 'abdfe71b00030001', + 'abdce71d00040001', + 'abdde71e00050001' + ], + 'pendingEnterProtectedMaintenanceModeCapacityInKb': 0, + 'vtreeAddresSpaceInKb': 8388608, + 'snapCapacityInUseOccupiedInKb': 0, + 'activeFwdRebuildCapacityInKb': 0, + 'rfcacheReadsSkippedStuckIo': 0, + 'activeMovingOutNormRebuildJobs': 0, + 'rfcacheWritePending': 0, + 'numOfThinBaseVolumes': 1, + 'degradedFailedVacInKb': 0, + 'userDataTrimBwc': { + 'numSeconds': 0, + 'totalWeightInKb': 0, + 'numOccured': 0 + }, + 'numOfIncomingVtreeMigrations': 0 + } + + @staticmethod + def get_exception_response(response_type): + if response_type == 'get_details': + return "Failed to get the volume test_id_1 with error " diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/__init__.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/__init__.py diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_info.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_info.py new file mode 100644 index 000000000..2bd0ff158 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_info.py @@ -0,0 +1,151 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for info module on PowerFlex""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_info_api import MockInfoApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_sdk_response \ + import MockSDKResponse +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() + +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.info import PowerFlexInfo + + +class TestPowerflexInfo(): + + get_module_args = MockInfoApi.INFO_COMMON_ARGS + + @pytest.fixture + def info_module_mock(self, mocker): + info_module_mock = PowerFlexInfo() + info_module_mock.module.check_mode = False + info_module_mock.powerflex_conn.system.api_version = MagicMock( + return_value=3.5 + ) + info_module_mock.powerflex_conn.system.get = MagicMock( + return_value=MockInfoApi.INFO_ARRAY_DETAILS + ) + return info_module_mock + + def test_get_volume_details(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['vol'] + }) + info_module_mock.module.params = self.get_module_args + volume_resp = MockInfoApi.INFO_VOLUME_GET_LIST + info_module_mock.powerflex_conn.volume.get = MagicMock( + return_value=volume_resp + ) + volume_stat_resp = MockInfoApi.INFO_VOLUME_STATISTICS + info_module_mock.powerflex_conn.utility.get_statistics_for_all_volumes = MagicMock( + return_value=volume_stat_resp + ) + info_module_mock.perform_module_operation() + info_module_mock.powerflex_conn.volume.get.assert_called() + info_module_mock.powerflex_conn.utility.get_statistics_for_all_volumes.assert_called() + + def test_get_volume_details_with_exception(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['vol'] + }) + info_module_mock.module.params = self.get_module_args + volume_resp = MockInfoApi.INFO_VOLUME_GET_LIST + info_module_mock.powerflex_conn.volume.get = MagicMock( + return_value=volume_resp + ) + info_module_mock.powerflex_conn.utility.get_statistics_for_all_volumes = MagicMock( + side_effect=MockApiException + ) + info_module_mock.perform_module_operation() + assert MockInfoApi.get_exception_response('volume_get_details') in info_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_sp_details(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['storage_pool'] + }) + info_module_mock.module.params = self.get_module_args + sp_resp = MockInfoApi.INFO_STORAGE_POOL_GET_LIST + info_module_mock.powerflex_conn.storage_pool.get = MagicMock( + return_value=sp_resp + ) + sp_stat_resp = MockInfoApi.INFO_STORAGE_POOL_STATISTICS + info_module_mock.powerflex_conn.utility.get_statistics_for_all_storagepools = MagicMock( + return_value=sp_stat_resp + ) + info_module_mock.perform_module_operation() + info_module_mock.powerflex_conn.storage_pool.get.assert_called() + info_module_mock.powerflex_conn.utility.get_statistics_for_all_storagepools.assert_called() + + def test_get_sp_details_with_exception(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['storage_pool'] + }) + info_module_mock.module.params = self.get_module_args + sp_resp = MockInfoApi.INFO_STORAGE_POOL_GET_LIST + info_module_mock.powerflex_conn.storage_pool.get = MagicMock( + return_value=sp_resp + ) + info_module_mock.powerflex_conn.utility.get_statistics_for_all_storagepools = MagicMock( + side_effect=MockApiException + ) + info_module_mock.perform_module_operation() + assert MockInfoApi.get_exception_response('sp_get_details') in info_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_rcg_details(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['rcg'] + }) + info_module_mock.module.params = self.get_module_args + rcg_resp = MockInfoApi.RCG_LIST + info_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=rcg_resp) + info_module_mock.perform_module_operation() + info_module_mock.powerflex_conn.replication_consistency_group.get.assert_called() + + def test_get_rcg_details_throws_exception(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['rcg'] + }) + info_module_mock.module.params = self.get_module_args + info_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + side_effect=MockApiException + ) + info_module_mock.perform_module_operation() + assert MockInfoApi.get_exception_response('rcg_get_details') in info_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_replication_pair_details(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['replication_pair'] + }) + info_module_mock.module.params = self.get_module_args + info_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockInfoApi.PAIR_LIST) + info_module_mock.perform_module_operation() + info_module_mock.powerflex_conn.replication_pair.get.assert_called() + + def test_get_replication_pair_details_throws_exception(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['replication_pair'] + }) + info_module_mock.module.params = self.get_module_args + info_module_mock.powerflex_conn.replication_pair.get = MagicMock( + side_effect=MockApiException + ) + info_module_mock.perform_module_operation() + assert MockInfoApi.get_exception_response('replication_pair_get_details') in info_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_mdm_cluster.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_mdm_cluster.py new file mode 100644 index 000000000..f8f3cdc2f --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_mdm_cluster.py @@ -0,0 +1,636 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for MDM cluster module on PowerFlex""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_mdm_cluster_api import MockMdmClusterApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_sdk_response \ + import MockSDKResponse +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.mdm_cluster import PowerFlexMdmCluster + + +class TestPowerflexMDMCluster(): + + get_module_args = MockMdmClusterApi.MDM_CLUSTER_COMMON_ARGS + add_mdm_ip = "xx.3x.xx.xx" + + @pytest.fixture + def mdm_cluster_module_mock(self, mocker): + mocker.patch(MockMdmClusterApi.MODULE_UTILS_PATH + '.PowerFlexClient', new=MockApiException) + mdm_cluster_module_mock = PowerFlexMdmCluster() + mdm_cluster_module_mock.module.check_mode = False + return mdm_cluster_module_mock + + def test_get_mdm_cluster(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details.assert_called() + + def test_get_mdm_cluster_with_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.get_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details.assert_called() + + def test_rename_mdm(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": MockMdmClusterApi.MDM_NAME, + "mdm_new_name": "mdm_node_renamed", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.rename_mdm = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.rename_mdm.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_rename_mdm_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": MockMdmClusterApi.MDM_NAME, + "mdm_new_name": "mdm_node_renamed", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.rename_mdm = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.rename_mdm.assert_called() + assert MockMdmClusterApi.rename_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_set_performance_profile_mdm_cluster(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "performance_profile": "Compact", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.set_cluster_mdm_performance_profile = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.set_cluster_mdm_performance_profile.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_set_performance_profile_mdm_cluster_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "performance_profile": "Compact", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.set_cluster_mdm_performance_profile = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.set_cluster_mdm_performance_profile.assert_called() + assert MockMdmClusterApi.perf_profile_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_set_virtual_ip_interface_mdm(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": MockMdmClusterApi.MDM_ID, + "virtual_ip_interfaces": ["ens11"], + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.modify_virtual_ip_interface = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.modify_virtual_ip_interface.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_set_virtual_ip_interface_mdm_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": MockMdmClusterApi.MDM_ID, + "virtual_ip_interfaces": ["ens11"], + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.modify_virtual_ip_interface = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.modify_virtual_ip_interface.assert_called() + assert MockMdmClusterApi.virtual_ip_interface_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_set_virtual_ip_interface_mdm_idempotency(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": MockMdmClusterApi.MDM_ID, + "virtual_ip_interfaces": ["ens1"], + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_remove_standby_mdm(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": MockMdmClusterApi.STB_TB_MDM_ID, + "state": "absent" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.remove_standby_mdm = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.remove_standby_mdm.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_remove_standby_mdm_idempotency(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "non_existing_node", + "state": "absent" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_remove_standby_mdm_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": MockMdmClusterApi.STB_TB_MDM_ID, + "state": "absent" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.remove_standby_mdm = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.remove_standby_mdm.assert_called() + assert MockMdmClusterApi.remove_mdm_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_add_standby_mdm(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "standby_node", + "standby_mdm": { + "mdm_ips": [self.add_mdm_ip], + "role": "Manager", + "port": 9011, + "management_ips": [self.add_mdm_ip], + "virtual_interfaces": ["ens1"], + "allow_multiple_ips": True + }, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS + ) + mdm_cluster_module_mock.powerflex_conn.system.add_standby_mdm = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.add_standby_mdm.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_add_standby_mdm_idempotency(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": MockMdmClusterApi.MDM_NAME, + "standby_mdm": { + "mdm_ips": ["10.x.z.z"], + "role": "TieBreaker", + "port": 9011 + }, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_add_standby_mdm_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "standby_node", + "standby_mdm": { + "mdm_ips": [self.add_mdm_ip], + "role": "Manager", + "port": 9011, + "management_ips": [self.add_mdm_ip], + "virtual_interfaces": ["ens1"], + "allow_multiple_ips": True + }, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS + ) + mdm_cluster_module_mock.powerflex_conn.system.add_standby_mdm = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.add_standby_mdm.assert_called() + assert MockMdmClusterApi.add_mdm_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_change_mdm_cluster_owner(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "sample_mdm1", + "is_primary": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.change_mdm_ownership = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.change_mdm_ownership.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_change_mdm_cluster_owner_idempotency(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": "5908d328581d1400", + "is_primary": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse( + MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_change_mdm_cluster_owner_execption(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "sample_mdm1", + "is_primary": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.change_mdm_ownership = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.change_mdm_ownership.assert_called() + assert MockMdmClusterApi.owner_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_expand_mdm_cluster_mode(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "cluster_mode": "FiveNodes", + "mdm": [ + { + "mdm_name": MockMdmClusterApi.MDM_NAME_STB_MGR, + "mdm_id": None, + "mdm_type": "Secondary" + }, + { + "mdm_id": MockMdmClusterApi.STB_TB_MDM_ID, + "mdm_name": None, + "mdm_type": "TieBreaker" + } + ], + "mdm_state": "present-in-cluster", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.switch_cluster_mode = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.switch_cluster_mode.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_reduce_mdm_cluster_mode_idempotency(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "cluster_mode": "ThreeNodes", + "mdm": [ + { + "mdm_name": None, + "mdm_id": MockMdmClusterApi.STB_MGR_MDM_ID, + "mdm_type": "Secondary" + }, + { + "mdm_id": None, + "mdm_name": MockMdmClusterApi.MDM_NAME, + "mdm_type": "TieBreaker" + } + ], + "mdm_state": "absent-in-cluster", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_expand_mdm_cluster_mode_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "cluster_mode": "FiveNodes", + "mdm": [ + { + "mdm_name": MockMdmClusterApi.MDM_NAME_STB_MGR, + "mdm_id": None, + "mdm_type": "Secondary" + }, + { + "mdm_id": MockMdmClusterApi.STB_TB_MDM_ID, + "mdm_name": None, + "mdm_type": "TieBreaker" + } + ], + "mdm_state": "present-in-cluster", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.switch_cluster_mode = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.switch_cluster_mode.assert_called() + assert MockMdmClusterApi.switch_mode_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_reduce_mdm_cluster_mode(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "cluster_mode": "ThreeNodes", + "mdm": [ + { + "mdm_name": None, + "mdm_id": MockMdmClusterApi.STB_MGR_MDM_ID, + "mdm_type": "Secondary" + }, + { + "mdm_id": None, + "mdm_name": MockMdmClusterApi.MDM_NAME, + "mdm_type": "TieBreaker" + } + ], + "mdm_state": "absent-in-cluster", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.FIVE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.switch_cluster_mode = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.switch_cluster_mode.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_clear_virtual_ip_interface_mdm(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": MockMdmClusterApi.STB_MGR_MDM_ID, + "clear_interfaces": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.modify_virtual_ip_interface = MagicMock() + mdm_cluster_module_mock.perform_module_operation() + mdm_cluster_module_mock.powerflex_conn.system.modify_virtual_ip_interface.assert_called() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_clear_virtual_ip_interface_mdm_idempotency(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "sample_mdm11", + "clear_interfaces": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.FIVE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert mdm_cluster_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_get_system_id_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_module_mock.powerflex_conn.system.get = MagicMock( + side_effect=utils.PowerFlexClient + ) + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.system_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_remove_mdm_cluster_owner_none(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "state": "absent" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.remove_mdm_no_id_name_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_expand_cluster_without_standby(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "cluster_mode": "FiveNodes", + "mdm": [ + { + "mdm_name": None, + "mdm_id": None, + "mdm_type": "Secondary" + }, + { + "mdm_id": None, + "mdm_name": None, + "mdm_type": "TieBreaker" + } + ], + "mdm_state": "present-in-cluster", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS_2) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.without_standby_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_system_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + system_resp = MockSDKResponse(MockMdmClusterApi.PARTIAL_SYSTEM_DETAILS_1) + mdm_cluster_module_mock.powerflex_conn.system.get = MagicMock( + return_value=system_resp.__dict__['data'] + ) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value={} + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.no_cluster_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_clear_virtual_ip_interface_mdm_id_none(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": None, + "clear_interfaces": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.is_mdm_name_id_exists = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS['master'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.id_none_interface_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_rename_mdm_id_none(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": None, + "mdm_new_name": "new_node", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.is_mdm_name_id_exists = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS['master'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.id_none_rename_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_change_owner_id_none(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_id": None, + "is_primary": True, + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.is_mdm_name_id_exists = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS['master'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.id_none_change_owner_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_multiple_system_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + system_resp = MockSDKResponse(MockMdmClusterApi.PARTIAL_SYSTEM_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get = MagicMock( + return_value=system_resp.__dict__['data'] + ) + mdm_cluster_resp = MockSDKResponse(MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS) + mdm_cluster_module_mock.powerflex_conn.system.get_mdm_cluster_details = MagicMock( + return_value=mdm_cluster_resp.__dict__['data'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.multiple_system_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] + + def test_add_standby_mdm_new_name_exception(self, mdm_cluster_module_mock): + self.get_module_args.update({ + "mdm_name": "standby_node", + "standby_mdm": { + "mdm_ips": [self.add_mdm_ip], + "role": "Manager", + "port": 9011, + "management_ips": [self.add_mdm_ip], + "virtual_interfaces": ["ens1"], + "allow_multiple_ips": True + }, + "mdm_new_name": "new_node", + "state": "present" + }) + mdm_cluster_module_mock.module.params = self.get_module_args + mdm_cluster_module_mock.get_mdm_cluster_details = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS + ) + mdm_cluster_module_mock.is_mdm_name_id_exists = MagicMock( + return_value=MockMdmClusterApi.THREE_MDM_CLUSTER_DETAILS['master'] + ) + mdm_cluster_module_mock.perform_module_operation() + assert MockMdmClusterApi.new_name_add_mdm_failed_response() in mdm_cluster_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_protection_domain.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_protection_domain.py new file mode 100644 index 000000000..ced9fc7f7 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_protection_domain.py @@ -0,0 +1,236 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for Protection Domain module on Dell Technologies (Dell) PowerFlex""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_protection_domain_api import MockProtectionDomainApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_sdk_response \ + import MockSDKResponse +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.protection_domain import PowerFlexProtectionDomain + + +class TestPowerflexProtectionDomain(): + + get_module_args = { + 'hostname': '**.***.**.***', + 'protection_domain_id': '7bd6457000000000', + 'protection_domain_name': None, + 'protection_domain_new_name': None, + 'is_active': True, + 'network_limits': { + 'rebuild_limit': 10240, + 'rebalance_limit': 10240, + 'vtree_migration_limit': 10240, + 'overall_limit': 20480, + 'bandwidth_unit': 'KBps', + }, + 'rf_cache_limits': { + 'is_enabled': None, + 'page_size': 4, + 'max_io_limit': 16, + 'pass_through_mode': 'None' + }, + 'state': 'present' + } + + @pytest.fixture + def protection_domain_module_mock(self, mocker): + mocker.patch(MockProtectionDomainApi.MODULE_UTILS_PATH + '.PowerFlexClient', new=MockApiException) + protection_domain_module_mock = PowerFlexProtectionDomain() + return protection_domain_module_mock + + def test_get_protection_domain_response(self, protection_domain_module_mock): + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.perform_module_operation() + protection_domain_module_mock.powerflex_conn.protection_domain.get.assert_called() + + def test_create_protection_domain(self, protection_domain_module_mock): + self.get_module_args.update({ + "protection_domain_name": "test_domain", + "state": "present" + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.get_protection_domain = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'][0] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.create = MagicMock(return_values=None) + protection_domain_module_mock.perform_module_operation() + assert (self.get_module_args['protection_domain_name'] == + protection_domain_module_mock.module.exit_json.call_args[1]["protection_domain_details"]['name']) + assert protection_domain_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_modify_protection_domain(self, protection_domain_module_mock): + self.get_module_args.update({ + 'network_limits': { + 'rebuild_limit': 10, + 'rebalance_limit': 10, + 'vtree_migration_limit': 11, + 'overall_limit': 21, + 'bandwidth_unit': 'GBps', + } + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + sp_resp = MockSDKResponse(MockProtectionDomainApi.STORAGE_POOL) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.get_storage_pools = MagicMock( + return_value=sp_resp.__dict__['data']['storagepool'] + ) + protection_domain_module_mock.perform_module_operation() + protection_domain_module_mock.powerflex_conn.protection_domain.network_limits.assert_called() + + def test_rename_protection_domain(self, protection_domain_module_mock): + self.get_module_args.update({ + 'protection_domain_new_name': 'new_test_domain' + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.perform_module_operation() + protection_domain_module_mock.powerflex_conn.protection_domain.rename.assert_called() + + def test_inactivate_protection_domain(self, protection_domain_module_mock): + self.get_module_args.update({ + 'is_active': False + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.perform_module_operation() + protection_domain_module_mock.powerflex_conn.protection_domain. \ + inactivate.assert_called() + + def test_activate_protection_domain(self, protection_domain_module_mock): + self.get_module_args.update({ + 'is_active': True + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.activate = MagicMock(return_value=None) + protection_domain_module_mock.perform_module_operation() + assert protection_domain_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_delete_protection_domain(self, protection_domain_module_mock): + self.get_module_args.update({ + 'protection_domain_name': 'new_test_domain', + 'state': 'absent' + }) + protection_domain_module_mock.module.params = self.get_module_args + protection_domain_module_mock.get_protection_domain = MagicMock(return_values=None) + protection_domain_module_mock.perform_module_operation() + assert protection_domain_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_delete_protection_domain_throws_exception(self, protection_domain_module_mock): + self.get_module_args.update({ + 'protection_domain_id': '7bd6457000000000', + 'state': 'absent' + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.delete = MagicMock( + side_effect=utils.PowerFlexClient) + protection_domain_module_mock.perform_module_operation() + assert MockProtectionDomainApi.delete_pd_failed_msg(self.get_module_args['protection_domain_id']) in \ + protection_domain_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_with_404_exception(self, protection_domain_module_mock): + MockProtectionDomainApi.status = 404 + self.get_module_args.update({ + "protection_domain_name": "test_domain1" + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.create = MagicMock( + side_effect=utils.PowerFlexClient) + protection_domain_module_mock.perform_module_operation() + assert protection_domain_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_modify_protection_domain_throws_exception(self, protection_domain_module_mock): + self.get_module_args.update({ + "protection_domain_id": "7bd6457000000000", + 'rf_cache_limits': { + 'is_enabled': True, + 'page_size': 64, + 'max_io_limit': 128, + 'pass_through_mode': 'invalid_Read' + } + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.set_rfcache_enabled = MagicMock( + side_effect=utils.PowerFlexClient) + protection_domain_module_mock.perform_module_operation() + assert MockProtectionDomainApi.modify_pd_with_failed_msg(self.get_module_args['protection_domain_id']) in \ + protection_domain_module_mock.module.fail_json.call_args[1]['msg'] + + def test_rename_protection_domain_invalid_value(self, protection_domain_module_mock): + self.get_module_args.update({ + "protection_domain_name": "test_domain", + "protection_domain_new_name": " test domain", + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.rename = MagicMock( + side_effect=utils.PowerFlexClient) + protection_domain_module_mock.perform_module_operation() + assert MockProtectionDomainApi.rename_pd_failed_msg(self.get_module_args['protection_domain_id']) in \ + protection_domain_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_protection_domain_invalid_param(self, protection_domain_module_mock): + self.get_module_args.update({ + "protection_domain_name": "test_domain1", + "protection_domain_new_name": "new_domain", + "state": "present" + }) + protection_domain_module_mock.module.params = self.get_module_args + pd_resp = MockSDKResponse(MockProtectionDomainApi.PROTECTION_DOMAIN) + protection_domain_module_mock.powerflex_conn.protection_domain.get = MagicMock( + return_value=pd_resp.__dict__['data']['protectiondomain'] + ) + protection_domain_module_mock.powerflex_conn.protection_domain.create = MagicMock( + side_effect=utils.PowerFlexClient) + protection_domain_module_mock.perform_module_operation() + assert MockProtectionDomainApi.version_pd_failed_msg() in \ + protection_domain_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_replication_consistency_group.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_replication_consistency_group.py new file mode 100644 index 000000000..334de8942 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_replication_consistency_group.py @@ -0,0 +1,344 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for replication consistency group module on PowerFlex""" + +from __future__ import (absolute_import, division, print_function) +from unittest.mock import Mock + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_replication_consistency_group_api import MockReplicationConsistencyGroupApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() + +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.replication_consistency_group import PowerFlexReplicationConsistencyGroup + + +class TestPowerflexReplicationConsistencyGroup(): + + get_module_args = MockReplicationConsistencyGroupApi.RCG_COMMON_ARGS + + @pytest.fixture + def replication_consistency_group_module_mock(self): + replication_consistency_group_module_mock = PowerFlexReplicationConsistencyGroup() + replication_consistency_group_module_mock.module.check_mode = False + return replication_consistency_group_module_mock + + def test_get_rcg_details(self, replication_consistency_group_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "state": "present" + }) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_resp = MockReplicationConsistencyGroupApi.get_rcg_details() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=replication_consistency_group_resp + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get.assert_called() + + def test_get_rcg_details_with_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "state": "present" + }) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + side_effect=MockApiException) + replication_consistency_group_module_mock.validate_create = MagicMock() + replication_consistency_group_module_mock.perform_module_operation() + assert MockReplicationConsistencyGroupApi.get_exception_response('get_details') in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_rcg_snapshot_response(self, replication_consistency_group_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "create_snapshot": True, + "state": "present" + }) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_resp = MockReplicationConsistencyGroupApi.get_rcg_details() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=replication_consistency_group_resp + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.create_snapshot.assert_called() + + def test_create_rcg_snapshot_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({ + "rcg_id": "aadc17d500000000", + "create_snapshot": True, + "state": "present" + }) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_resp = MockReplicationConsistencyGroupApi.get_rcg_details() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=replication_consistency_group_resp + ) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.create_snapshot = MagicMock( + side_effect=MockApiException + ) + replication_consistency_group_module_mock.perform_module_operation() + assert MockReplicationConsistencyGroupApi.create_snapshot_exception_response('create_snapshot', self.get_module_args['rcg_id']) \ + in replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", "rpo": 60, "protection_domain_name": "domain1", + "protection_domain_id": None, "activity_mode": "active", "state": "present", + "remote_peer": {"hostname": "1.1.1.1", "username": "username", "password": "password", + "verifycert": "verifycert", "port": "port", "protection_domain_name": "None", + "protection_domain_id": "123"}}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=None + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.create.assert_called() + + def test_modify_rpo(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "rpo": 60, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details() + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.modify_rpo.assert_called() + + def test_modify_rpo_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "rpo": 60, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.modify_rpo = MagicMock( + side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Modify rpo for replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_modify_target_volume_access_mode(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "target_volume_access_mode": "Readonly", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details() + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.modify_target_volume_access_mode.assert_called() + + def test_modify_target_volume_access_mode_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "target_volume_access_mode": "Readonly", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.modify_target_volume_access_mode = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Modify target volume access mode for replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID \ + + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_modify_activity_mode(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "activity_mode": "Inactive", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.inactivate.assert_called() + + def test_modify_activity_mode_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "activity_mode": "Active", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details(activity_mode="Inactive")) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.activate = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Modify activity_mode for replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID \ + + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_pause_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "pause": True, + "pause_mode": "StopDataTransfer", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details() + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.pause.assert_called() + + def test_pause_rcg_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "pause": True, + "pause_mode": "StopDataTransfer", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.pause = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Pause replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID \ + + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_resume_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "pause": False, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details(pause_mode="StopDataTransfer")) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.resume.assert_called() + + def test_resume_rcg_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "pause": False, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details(pause_mode="StopDataTransfer")) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.resume = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Resume replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID \ + + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_freeze_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "freeze": True, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.freeze.assert_called() + + def test_freeze_rcg_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "freeze": True, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.freeze = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Freeze replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID \ + + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_unfreeze_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "freeze": False, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details(freeze_state="Frozen") + ) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.unfreeze.assert_called() + + def test_unfreeze_rcg_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "freeze": False, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details(freeze_state="Frozen")) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.unfreeze = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Unfreeze replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID \ + + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_rename_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "new_rcg_name": "test_rcg_rename", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.rename_rcg.assert_called() + + def test_rename_rcg_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "new_rcg_name": "test_rcg_rename", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.rename_rcg = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Renaming replication consistency group to test_rcg_rename failed with error" in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_delete_rcg(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "state": "absent"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.delete.assert_called() + + def test_delete_rcg_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "state": "absent"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.delete = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Delete replication consistency group " + MockReplicationConsistencyGroupApi.RCG_ID + MockReplicationConsistencyGroupApi.FAIL_MSG in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_modify_rcg_as_inconsistent(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "is_consistent": False, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.perform_module_operation() + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.set_as_inconsistent.assert_called() + + def test_modify_rcg_as_consistent_throws_exception(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "is_consistent": True, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details(consistency="InConsistent")) + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.set_as_consistent = \ + MagicMock(side_effect=MockApiException) + replication_consistency_group_module_mock.perform_module_operation() + assert "Modifying consistency of replication consistency group failed with error" in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_pause_rcg_without_pause_mode(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "pause": True, "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.protection_domain.get = MagicMock(return_value=[{"name": "pd_id"}]) + replication_consistency_group_module_mock.perform_module_operation() + assert "Specify pause_mode to perform pause on replication consistency group." in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_rcg_with_invalid_params(self, replication_consistency_group_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", "activity_mode": "active", "state": "present", + "remote_peer": {"hostname": "1.1.1.1", "username": "username", "password": "password", + "verifycert": "verifycert", "port": "port", "protection_domain_name": None, + "protection_domain_id": None}}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=None) + replication_consistency_group_module_mock.perform_module_operation() + assert "Enter remote protection_domain_name or protection_domain_id to create replication consistency group" in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] + + def test_pause_rcg_without_pause(self, replication_consistency_group_module_mock): + self.get_module_args.update({"rcg_name": "test_rcg", "pause_mode": "StopDataTransfer", "state": "present"}) + replication_consistency_group_module_mock.module.params = self.get_module_args + replication_consistency_group_module_mock.powerflex_conn.replication_consistency_group.get = MagicMock( + return_value=MockReplicationConsistencyGroupApi.get_rcg_details()) + replication_consistency_group_module_mock.powerflex_conn.protection_domain.get = MagicMock(return_value=[{"name": "pd_id"}]) + replication_consistency_group_module_mock.perform_module_operation() + assert "Specify pause as True to pause replication consistency group" in \ + replication_consistency_group_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_replication_pair.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_replication_pair.py new file mode 100644 index 000000000..81787de8f --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_replication_pair.py @@ -0,0 +1,237 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for replication pair module on PowerFlex""" + +from __future__ import (absolute_import, division, print_function) +from unittest.mock import Mock + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_replication_pair_api import MockReplicationPairApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() + +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.replication_pair import PowerFlexReplicationPair + + +class TestPowerflexReplicationPair(): + + get_module_args = MockReplicationPairApi.REPLICATION_PAIR_COMMON_ARGS + + @pytest.fixture + def replication_pair_module_mock(self): + replication_pair_module_mock = PowerFlexReplicationPair() + replication_pair_module_mock.get_rcg = MagicMock(return_value={"id": 123}) + replication_pair_module_mock.module.check_mode = False + return replication_pair_module_mock + + def test_get_pair_details(self, replication_pair_module_mock): + self.get_module_args.update({ + "pair_name": "test_pair", + "pairs": None, + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_resp = MockReplicationPairApi.get_pair_details() + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=replication_pair_resp + ) + replication_pair_module_mock.perform_module_operation() + replication_pair_module_mock.powerflex_conn.replication_pair.get.assert_called() + + def test_get_pair_details_with_exception(self, replication_pair_module_mock): + self.get_module_args.update({ + "pair_name": "test_pair", + "pairs": None, + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + side_effect=MockApiException) + replication_pair_module_mock.perform_module_operation() + assert "Failed to get the replication pair" in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_pairs(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": [{"source_volume_id": "123", "target_volume_id": "345", "source_volume_name": None, + "target_volume_name": None, "copy_type": "OnlineCopy", "name": "test_pair"}], + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_resp = MockReplicationPairApi.get_pair_details() + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=replication_pair_resp + ) + replication_pair_module_mock.perform_module_operation() + replication_pair_module_mock.powerflex_conn.replication_pair.add.assert_called() + + def test_create_pairs_with_volume_name(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": [{"source_volume_name": "src_vol", "target_volume_name": "dest_vol", "source_volume_id": None, + "target_volume_id": None, "copy_type": "OnlineCopy", "name": "test_pair"}], + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.volume.get = MagicMock( + return_value=MockReplicationPairApi.get_volume_details() + ) + replication_pair_module_mock.perform_module_operation() + replication_pair_module_mock.powerflex_conn.replication_pair.add.assert_called() + + def test_create_pairs_exception(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": [{"source_volume_id": "123", "target_volume_id": "345", "source_volume_name": None, + "target_volume_name": None, "copy_type": "OnlineCopy", "name": "test_pair"}], + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_resp = MockReplicationPairApi.get_pair_details() + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=replication_pair_resp + ) + replication_pair_module_mock.powerflex_conn.replication_pair.add = MagicMock( + side_effect=MockApiException + ) + replication_pair_module_mock.perform_module_operation() + assert "Create replication pairs failed with error" \ + in replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_pause_replication_pair(self, replication_pair_module_mock): + self.get_module_args.update({"pair_name": "test_pair", "pause": True, "state": "present"}) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details("Uninitialized") + ) + replication_pair_module_mock.perform_module_operation() + replication_pair_module_mock.powerflex_conn.replication_pair.pause.assert_called() + + def test_pause_rcg_throws_exception(self, replication_pair_module_mock): + self.get_module_args.update({"pair_id": MockReplicationPairApi.PAIR_ID, "pause": True, "state": "present"}) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details("Uninitialized")) + replication_pair_module_mock.powerflex_conn.replication_pair.pause = \ + MagicMock(side_effect=MockApiException) + replication_pair_module_mock.perform_module_operation() + assert "Pause replication pair " + MockReplicationPairApi.PAIR_ID \ + + MockReplicationPairApi.FAIL_MSG in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_resume_replication_pair(self, replication_pair_module_mock): + self.get_module_args.update({"pair_name": "test_pair", "pause": False, "state": "present"}) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details("Paused") + ) + replication_pair_module_mock.perform_module_operation() + replication_pair_module_mock.powerflex_conn.replication_pair.resume.assert_called() + + def test_resume_rcg_throws_exception(self, replication_pair_module_mock): + self.get_module_args.update({"pair_id": MockReplicationPairApi.PAIR_ID, "pause": False, "state": "present"}) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details("Paused")) + replication_pair_module_mock.powerflex_conn.replication_pair.resume = \ + MagicMock(side_effect=MockApiException) + replication_pair_module_mock.perform_module_operation() + assert "Resume replication pair " + MockReplicationPairApi.PAIR_ID \ + + MockReplicationPairApi.FAIL_MSG in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_delete_replication_pair(self, replication_pair_module_mock): + self.get_module_args.update({"pair_name": "test_pair", "pairs": None, "state": "absent"}) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details() + ) + replication_pair_module_mock.perform_module_operation() + replication_pair_module_mock.powerflex_conn.replication_pair.remove.assert_called() + + def test_delete_replication_pair_throws_exception(self, replication_pair_module_mock): + self.get_module_args.update({"pair_id": MockReplicationPairApi.PAIR_ID, "pairs": None, "state": "absent"}) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details()) + replication_pair_module_mock.powerflex_conn.replication_pair.remove = \ + MagicMock(side_effect=MockApiException) + replication_pair_module_mock.perform_module_operation() + assert "Delete replication pair " + MockReplicationPairApi.PAIR_ID \ + + MockReplicationPairApi.FAIL_MSG in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_replication_pair_src_validation(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": [{"source_volume_id": "123", "target_volume_id": "345", "source_volume_name": "abc", + "target_volume_name": None, "copy_type": "OnlineCopy", "name": "test_pair"}], + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details()) + replication_pair_module_mock.create_replication_pairs = MagicMock(return_value=None) + replication_pair_module_mock.perform_module_operation() + assert "Specify either source_volume_id or source_volume_name" in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_create_replication_pair_target_validation(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": [{"source_volume_id": "123", "target_volume_id": "345", "source_volume_name": None, + "target_volume_name": "abc", "copy_type": "OnlineCopy", "name": "test_pair"}], + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details()) + replication_pair_module_mock.create_replication_pairs = MagicMock(return_value=None) + replication_pair_module_mock.perform_module_operation() + assert "Specify either target_volume_id or target_volume_name" in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_replication_pair_pause_validation(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": None, "pause": True, + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details()) + replication_pair_module_mock.perform_pause_or_resume = MagicMock() + replication_pair_module_mock.perform_module_operation() + assert "Specify a valid pair_name or pair_id to perform pause or resume" in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_rcg_replication_pairs_throws_exception(self, replication_pair_module_mock): + self.get_module_args.update({ + "rcg_name": "test_rcg", + "pairs": [{"source_volume_id": "123", "target_volume_id": "345", "source_volume_name": None, + "target_volume_name": None, "copy_type": "OnlineCopy", "name": "test_pair"}], + "state": "present" + }) + replication_pair_module_mock.module.params = self.get_module_args + replication_pair_module_mock.powerflex_conn.replication_pair.get = MagicMock( + return_value=MockReplicationPairApi.get_pair_details()) + replication_pair_module_mock.powerflex_conn.replication_consistency_group.get_replication_pairs = MagicMock( + side_effect=MockApiException) + replication_pair_module_mock.create_replication_pairs = MagicMock(return_value=None) + replication_pair_module_mock.perform_module_operation() + assert "Failed to get the replication pairs for replication consistency group" in \ + replication_pair_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_storagepool.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_storagepool.py new file mode 100644 index 000000000..a2c463f66 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_storagepool.py @@ -0,0 +1,72 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for storage pool module on PowerFlex""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_storagepool_api import MockStoragePoolApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_sdk_response \ + import MockSDKResponse +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() + +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.storagepool import PowerFlexStoragePool + + +class TestPowerflexStoragePool(): + + get_module_args = MockStoragePoolApi.STORAGE_POOL_COMMON_ARGS + + @pytest.fixture + def storagepool_module_mock(self, mocker): + storagepool_module_mock = PowerFlexStoragePool() + storagepool_module_mock.module.check_mode = False + return storagepool_module_mock + + def test_get_storagepool_details(self, storagepool_module_mock): + self.get_module_args.update({ + "storage_pool_name": "test_pool", + "state": "present" + }) + storagepool_module_mock.module.params = self.get_module_args + storagepool_resp = MockStoragePoolApi.STORAGE_POOL_GET_LIST + storagepool_module_mock.powerflex_conn.storage_pool.get = MagicMock( + return_value=storagepool_resp + ) + storagepool_statistics_resp = MockStoragePoolApi.STORAGE_POOL_STATISTICS + storagepool_module_mock.powerflex_conn.storage_pool.get_statistics = MagicMock( + return_value=storagepool_statistics_resp + ) + storagepool_module_mock.perform_module_operation() + storagepool_module_mock.powerflex_conn.storage_pool.get.assert_called() + storagepool_module_mock.powerflex_conn.storage_pool.get_statistics.assert_called() + + def test_get_storagepool_details_with_exception(self, storagepool_module_mock): + self.get_module_args.update({ + "storage_pool_name": "test_pool" + }) + storagepool_module_mock.module.params = self.get_module_args + storagepool_resp = MockStoragePoolApi.STORAGE_POOL_GET_LIST + storagepool_module_mock.powerflex_conn.storage_pool.get = MagicMock( + return_value=storagepool_resp + ) + storagepool_module_mock.powerflex_conn.storage_pool.get_statistics = MagicMock( + side_effect=MockApiException + ) + storagepool_module_mock.create_storage_pool = MagicMock(return_value=None) + storagepool_module_mock.perform_module_operation() + assert MockStoragePoolApi.get_exception_response('get_details') in storagepool_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_volume.py b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_volume.py new file mode 100644 index 000000000..53cdcfc0d --- /dev/null +++ b/ansible_collections/dellemc/powerflex/tests/unit/plugins/modules/test_volume.py @@ -0,0 +1,81 @@ +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for volume module on PowerFlex""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_volume_api import MockVolumeApi +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_sdk_response \ + import MockSDKResponse +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils + +utils.get_logger = MagicMock() +utils.get_powerflex_gateway_host_connection = MagicMock() +utils.PowerFlexClient = MagicMock() + +from ansible.module_utils import basic +basic.AnsibleModule = MagicMock() +from ansible_collections.dellemc.powerflex.plugins.modules.volume import PowerFlexVolume + + +class TestPowerflexVolume(): + + get_module_args = MockVolumeApi.VOLUME_COMMON_ARGS + + @pytest.fixture + def volume_module_mock(self, mocker): + volume_module_mock = PowerFlexVolume() + volume_module_mock.module.check_mode = False + return volume_module_mock + + def test_get_volume_details(self, volume_module_mock): + self.get_module_args.update({ + "vol_name": "testing", + "state": "present" + }) + volume_module_mock.module.params = self.get_module_args + volume_resp = MockVolumeApi.VOLUME_GET_LIST + volume_module_mock.powerflex_conn.volume.get = MagicMock( + return_value=volume_resp + ) + volume_sp_resp = MockVolumeApi.VOLUME_STORAGEPOOL_DETAILS + volume_module_mock.get_storage_pool = MagicMock( + return_value=volume_sp_resp + ) + volume_pd_resp = MockVolumeApi.VOLUME_PD_DETAILS + volume_module_mock.get_protection_domain = MagicMock( + return_value=volume_pd_resp + ) + volume_statistics_resp = MockVolumeApi.VOLUME_STATISTICS + volume_module_mock.powerflex_conn.volume.get_statistics = MagicMock( + return_value=volume_statistics_resp + ) + volume_module_mock.perform_module_operation() + volume_module_mock.powerflex_conn.volume.get.assert_called() + volume_module_mock.powerflex_conn.volume.get_statistics.assert_called() + + def test_get_volume_details_with_exception(self, volume_module_mock): + self.get_module_args.update({ + "vol_name": "testing", + "state": "present" + }) + volume_module_mock.module.params = self.get_module_args + volume_resp = MockVolumeApi.VOLUME_GET_LIST + volume_module_mock.powerflex_conn.volume.get = MagicMock( + return_value=volume_resp + ) + volume_module_mock.powerflex_conn.volume.get_statistics = MagicMock( + side_effect=MockApiException + ) + volume_module_mock.create_volume = MagicMock(return_value=None) + volume_module_mock.perform_module_operation() + assert MockVolumeApi.get_exception_response('get_details') in volume_module_mock.module.fail_json.call_args[1]['msg'] |