From a21d1fe1145487d8f20c4425d1d78807eba16716 Mon Sep 17 00:00:00 2001 From: Joseph Vazhappilly Date: Wed, 12 Jan 2022 15:08:48 +0530 Subject: [PATCH] Add hostmapping support to VMAX driver --- delfin/drivers/dell_emc/vmax/client.py | 205 +++++++ delfin/drivers/dell_emc/vmax/rest.py | 197 ++++++ delfin/drivers/dell_emc/vmax/vmax.py | 18 + .../unit/drivers/dell_emc/vmax/test_vmax.py | 564 +++++++++++++++++- 4 files changed, 983 insertions(+), 1 deletion(-) diff --git a/delfin/drivers/dell_emc/vmax/client.py b/delfin/drivers/dell_emc/vmax/client.py index b5635e1cf..b55de0126 100644 --- a/delfin/drivers/dell_emc/vmax/client.py +++ b/delfin/drivers/dell_emc/vmax/client.py @@ -427,6 +427,211 @@ def list_disks(self, storage_id): LOG.error("Failed to get disk details from VMAX") raise + def list_storage_host_initiators(self, storage_id): + try: + # Get list of initiators + initiators = self.rest.get_initiator_list(self.array_id, + self.uni_version) + switcher = { + 'FIBRE': 'fc', + 'ISCSI': 'iscsi', + 'ROCE': 'roce', + 'OTHER': 'other', + } + + initiator_list = [] + for initiator in initiators: + initiator_info = self.rest.get_initiator( + self.array_id, self.uni_version, initiator) + type_key = initiator_info.get('type', '').upper() + initiator_type = switcher.get(type_key, 'OTHER') + initiator_status = constants.InitiatorStatus.ONLINE + if not initiator_info.get('on_fabric', False): + initiator_status = constants.InitiatorStatus.OFFLINE + + initiator_item = { + 'name': initiator, + 'storage_id': storage_id, + 'native_storage_host_initiator_id': initiator, + 'alias': initiator_info.get('alias'), + 'wwn': initiator_info.get('initiatorId'), + 'type': initiator_type, + 'status': initiator_status, + 'native_storage_host_id': initiator_info.get('host'), + } + initiator_list.append(initiator_item) + return initiator_list + + except Exception: + LOG.error("Failed to get host initiator details from VMAX") + raise + + def list_storage_hosts(self, storage_id): + try: + # Get list of storage hosts + hosts = self.rest.get_host_list(self.array_id, + self.uni_version) + host_list = [] + for host in hosts: + host_info = self.rest.get_host( + self.array_id, self.uni_version, host) + + host_item = { + 'storage_id': storage_id, + 'native_storage_host_id': host_info.get('hostId'), + 'name': host_info.get('hostId'), + 'os_type': constants.HostOSTypes.UNKNOWN, + 'status': constants.HostStatus.NORMAL, + } + host_list.append(host_item) + return host_list + + except Exception: + LOG.error("Failed to get storage host details from VMAX") + raise + + def list_storage_host_groups(self, storage_id): + try: + # Get list of storage host groups + host_groups = self.rest.get_host_group_list(self.array_id, + self.uni_version) + host_group_list = [] + storage_host_grp_relation_list = [] + for host_group in host_groups: + host_group_info = self.rest.get_host_group( + self.array_id, self.uni_version, host_group) + host_group_item = { + 'name': host_group, + 'storage_id': storage_id, + 'native_storage_host_group_id': host_group, + } + host_group_list.append(host_group_item) + + for storage_host in host_group_info['host']: + storage_host_group_relation = { + 'storage_id': storage_id, + 'native_storage_host_group_id': host_group, + 'native_storage_host_id': storage_host.get('hostId') + } + storage_host_grp_relation_list \ + .append(storage_host_group_relation) + + result = { + 'storage_host_groups': host_group_list, + 'storage_host_grp_host_rels': storage_host_grp_relation_list + } + + return result + + except Exception: + LOG.error("Failed to get storage host group details from VMAX") + raise + + def list_port_groups(self, storage_id): + try: + # Get list of port groups + port_groups = self.rest.get_port_group_list(self.array_id, + self.uni_version) + port_group_list = [] + port_group_relation_list = [] + for port_group in port_groups: + port_group_info = self.rest.get_port_group( + self.array_id, self.uni_version, port_group) + port_group_item = { + 'name': port_group, + 'storage_id': storage_id, + 'native_port_group_id': port_group, + } + port_group_list.append(port_group_item) + + for port in port_group_info['symmetrixPortKey']: + port_name = port['directorId'] + ':' + port['portId'] + port_group_relation = { + 'storage_id': storage_id, + 'native_port_group_id': port_group, + 'native_port_id': port_name + } + port_group_relation_list.append(port_group_relation) + result = { + 'port_groups': port_group_list, + 'port_grp_port_rels': port_group_relation_list + } + return result + + except Exception: + LOG.error("Failed to get port group details from VMAX") + raise + + def list_volume_groups(self, storage_id): + try: + # Get list of volume groups + volume_groups = self.rest.get_volume_group_list(self.array_id, + self.uni_version) + volume_group_list = [] + volume_group_relation_list = [] + for volume_group in volume_groups: + # volume_group_info = self.rest.get_volume_group( + # self.array_id, self.uni_version, volume_group) + + volume_group_item = { + 'name': volume_group, + 'storage_id': storage_id, + 'native_volume_group_id': volume_group, + } + volume_group_list.append(volume_group_item) + + # List all volumes except data volumes + volumes = self.rest.get_volume_list( + self.array_id, version=self.uni_version, + params={'data_volume': 'false', + 'storageGroupId': volume_group}) + if not volumes: + continue + for volume in volumes: + volume_group_relation = { + 'storage_id': storage_id, + 'native_volume_group_id': volume_group, + 'native_volume_id': volume + } + volume_group_relation_list.append(volume_group_relation) + + result = { + 'volume_groups': volume_group_list, + 'vol_grp_vol_rels': volume_group_relation_list + } + return result + + except Exception: + LOG.error("Failed to get volume group details from VMAX") + raise + + def list_masking_views(self, storage_id): + try: + # Get list of masking_views + masking_views = self.rest.get_masking_view_list(self.array_id, + self.uni_version) + masking_view_list = [] + for masking_view in masking_views: + mv_info = self.rest.get_masking_view( + self.array_id, self.uni_version, masking_view) + + masking_view_item = { + 'name': masking_view, + 'storage_id': storage_id, + 'native_masking_view_id': mv_info['maskingViewId'], + 'native_storage_host_id': mv_info.get('hostId'), + 'native_storage_host_group_id': mv_info.get( + 'hostGroupId'), + 'native_volume_group_id': mv_info.get('storageGroupId'), + 'native_port_group_id': mv_info.get('portGroupId'), + } + masking_view_list.append(masking_view_item) + return masking_view_list + + except Exception: + LOG.error("Failed to get masking views details from VMAX") + raise + def list_alerts(self, query_para): """Get all alerts from an array.""" return self.rest.get_alerts(query_para, version=self.uni_version, diff --git a/delfin/drivers/dell_emc/vmax/rest.py b/delfin/drivers/dell_emc/vmax/rest.py index b7f27d1ed..a1e92cd1d 100644 --- a/delfin/drivers/dell_emc/vmax/rest.py +++ b/delfin/drivers/dell_emc/vmax/rest.py @@ -733,6 +733,203 @@ def get_disk_list(self, array, version, params=None): array, SYSTEM, 'disk', version=version, params=params) return disk_dict_list.get('disk_ids', []) + def get_initiator(self, array, version, initiator_id): + """Get a VMax initiator from array. + :param array: the array serial number + :param version: the unisphere version -- int + :param initiator_id: the initiator id + :returns: initiator dict + :raises: StorageHostInitiatorNotFound + """ + initiator_dict = self.get_resource( + array, SLOPROVISIONING, 'initiator', resource_name=initiator_id, + version=version) + if not initiator_dict: + exception_message = (_("Initiator %(initiator_id)s not found.") + % {'initiator_id': initiator_id}) + LOG.error(exception_message) + raise exception.StorageHostInitiatorNotFound(initiator_id) + return initiator_dict + + def get_initiator_list(self, array, version, params=None): + """Get a filtered list of VMax initiators from array. + Filter parameters are required as the unfiltered initiator list + could bevery large and could affect performance if called often. + :param array: the array serial number + :param version: the unisphere version + :param params: filter parameters + :returns: initiatorId -- list + """ + initiator_dict_list = self.get_resource( + array, SLOPROVISIONING, 'initiator', + version=version, params=params) + return initiator_dict_list.get('initiatorId', []) + + def get_host(self, array, version, host_id): + """Get a VMax host from array. + :param array: the array serial number + :param version: the unisphere version -- int + :param host_id: the host id + :returns: host dict + :raises: StorageHostNotFound + """ + host_dict = self.get_resource( + array, SLOPROVISIONING, 'host', + resource_name=host_id, + version=version) + if not host_dict: + exception_message = (_("Host %(host_id)s not found.") + % {'host_id': host_id}) + LOG.error(exception_message) + raise exception.StorageHostNotFound(host_id) + return host_dict + + def get_host_list(self, array, version, params=None): + """Get a filtered list of VMax hosts from array. + Filter parameters are required as the unfiltered host list + could bevery large and could affect performance if called often. + :param array: the array serial number + :param version: the unisphere version + :param params: filter parameters + :returns: hostId -- list + """ + host_dict_list = self.get_resource( + array, SLOPROVISIONING, 'host', + version=version, params=params) + return host_dict_list.get('hostId', []) + + def get_host_group(self, array, version, host_group_id): + """Get a VMax host group from array. + :param array: the array serial number + :param version: the unisphere version -- int + :param host_group_id: the host group id + :returns: host group dict + :raises: StorageHostGroupNotFound + """ + host_group_dict = self.get_resource( + array, SLOPROVISIONING, 'hostgroup', + resource_name=host_group_id, + version=version) + if not host_group_dict: + exception_message = (_("HostGroup %(host_group_id)s not found.") + % {'host_group_id': host_group_id}) + LOG.error(exception_message) + raise exception.StorageHostGroupNotFound(host_group_id) + return host_group_dict + + def get_host_group_list(self, array, version, params=None): + """Get a filtered list of VMax host groups from array. + Filter parameters are required as the unfiltered host list + could bevery large and could affect performance if called often. + :param array: the array serial number + :param version: the unisphere version + :param params: filter parameters + :returns: hostGroupId -- list + """ + host_group_dict_list = self.get_resource( + array, SLOPROVISIONING, 'hostgroup', + version=version, params=params) + return host_group_dict_list.get('hostGroupId', []) + + def get_port_group(self, array, version, port_group_id): + """Get a VMax port group from array. + :param array: the array serial number + :param version: the unisphere version -- int + :param port_group_id: the port group id + :returns: port group dict + :raises: PortGroupNotFound + """ + port_group_dict = self.get_resource( + array, SLOPROVISIONING, 'portgroup', + resource_name=port_group_id, + version=version) + if not port_group_dict: + exception_message = (_("PortGroup %(port_group_id)s not found.") + % {'port_group_id': port_group_id}) + LOG.error(exception_message) + raise exception.PortGroupNotFound(port_group_id) + return port_group_dict + + def get_port_group_list(self, array, version, params=None): + """Get a filtered list of VMax port groups from array. + Filter parameters are required as the unfiltered host list + could bevery large and could affect performance if called often. + :param array: the array serial number + :param version: the unisphere version + :param params: filter parameters + :returns: portGroupId -- list + """ + port_group_dict_list = self.get_resource( + array, SLOPROVISIONING, 'portgroup', + version=version, params=params) + return port_group_dict_list.get('portGroupId', []) + + def get_volume_group(self, array, version, storage_group_id): + """Get a VMax storage/volume group from array. + :param array: the array serial number + :param version: the unisphere version -- int + :param storage_group_id: the storage group id + :returns: volume group dict + :raises: VolumeGroupNotFound + """ + storage_group_dict = self.get_resource( + array, SLOPROVISIONING, 'storagegroup', + resource_name=storage_group_id, + version=version) + if not storage_group_dict: + exception_message = (_("StorageGroup %(sid)s not found.") + % {'id': storage_group_id}) + LOG.error(exception_message) + raise exception.VolumeGroupNotFound(storage_group_id) + return storage_group_dict + + def get_volume_group_list(self, array, version, params=None): + """Get a filtered list of VMax storage groups from array. + Filter parameters are required as the unfiltered host list + could bevery large and could affect performance if called often. + :param array: the array serial number + :param version: the unisphere version + :param params: filter parameters + :returns: storageGroupId -- list + """ + storage_group_dict_list = self.get_resource( + array, SLOPROVISIONING, 'storagegroup', + version=version, params=params) + return storage_group_dict_list.get('storageGroupId', []) + + def get_masking_view(self, array, version, masking_view_id): + """Get a VMax masking view from array. + :param array: the array serial number + :param version: the unisphere version -- int + :param masking_view_id: the masking view id + :returns: masking view dict + :raises: MaskingViewNotFound + """ + masking_view_dict = self.get_resource( + array, SLOPROVISIONING, 'maskingview', + resource_name=masking_view_id, + version=version) + if not masking_view_dict: + exception_message = (_("Masking View %(id)s not found.") + % {'id': masking_view_id}) + LOG.error(exception_message) + raise exception.MaskingViewNotFound(masking_view_id) + return masking_view_dict + + def get_masking_view_list(self, array, version, params=None): + """Get a filtered list of VMax masking views from array. + Filter parameters are required as the unfiltered initiator list + could bevery large and could affect performance if called often. + :param array: the array serial number + :param version: the unisphere version + :param params: filter parameters + :returns: maskingViewId -- list + """ + masking_view_dict_list = self.get_resource( + array, SLOPROVISIONING, 'maskingview', + version=version, params=params) + return masking_view_dict_list.get('maskingViewId', []) + def post_request(self, target_uri, payload): """Generate a POST request. :param target_uri: the uri to query from unipshere REST API diff --git a/delfin/drivers/dell_emc/vmax/vmax.py b/delfin/drivers/dell_emc/vmax/vmax.py index f3cc7161b..c92f718cc 100644 --- a/delfin/drivers/dell_emc/vmax/vmax.py +++ b/delfin/drivers/dell_emc/vmax/vmax.py @@ -83,6 +83,24 @@ def list_ports(self, context): def list_disks(self, context): return self.client.list_disks(self.storage_id) + def list_storage_host_initiators(self, context): + return self.client.list_storage_host_initiators(self.storage_id) + + def list_storage_hosts(self, context): + return self.client.list_storage_hosts(self.storage_id) + + def list_storage_host_groups(self, context): + return self.client.list_storage_host_groups(self.storage_id) + + def list_port_groups(self, context): + return self.client.list_port_groups(self.storage_id) + + def list_volume_groups(self, context): + return self.client.list_volume_groups(self.storage_id) + + def list_masking_views(self, context): + return self.client.list_masking_views(self.storage_id) + def add_trap_config(self, context, trap_config): pass diff --git a/delfin/tests/unit/drivers/dell_emc/vmax/test_vmax.py b/delfin/tests/unit/drivers/dell_emc/vmax/test_vmax.py index 066810565..553cf4d21 100644 --- a/delfin/tests/unit/drivers/dell_emc/vmax/test_vmax.py +++ b/delfin/tests/unit/drivers/dell_emc/vmax/test_vmax.py @@ -624,7 +624,6 @@ def test_list_disks(self, mock_unisphere_version, self.assertEqual(driver.storage_id, "12345") self.assertEqual(driver.client.array_id, "00112233") ret = driver.list_disks(context) - print("return", ret) self.assertDictEqual(ret[0], expected[0]) self.assertDictEqual(ret[1], expected[1]) @@ -644,6 +643,569 @@ def test_list_disks(self, mock_unisphere_version, self.assertIn('Exception from Storage Backend', str(exc.exception)) + @mock.patch.object(VMaxRest, 'get_initiator') + @mock.patch.object(VMaxRest, 'get_initiator_list') + @mock.patch.object(VMaxRest, 'get_array_detail') + @mock.patch.object(VMaxRest, 'get_uni_version') + @mock.patch.object(VMaxRest, 'get_unisphere_version') + def test_list_storage_host_initiators(self, mock_unisphere_version, + mock_version, mock_array, + mock_initiators, mock_initiator): + expected = \ + [ + { + 'name': '1001', + 'storage_id': '12345', + 'native_storage_host_initiator_id': '1001', + 'alias': 'I1', + 'wwn': '1001', + 'type': 'fc', + 'status': 'online', + 'native_storage_host_id': 'host1', + }, + { + 'name': '1002', + 'storage_id': '12345', + 'native_storage_host_initiator_id': '1002', + 'alias': 'I2', + 'wwn': '1002', + 'type': 'iscsi', + 'status': 'offline', + 'native_storage_host_id': 'host2', + }, + { + 'name': '1003', + 'storage_id': '12345', + 'native_storage_host_initiator_id': '1003', + 'alias': 'I3', + 'wwn': '1003', + 'type': 'fc', + 'status': 'offline', + 'native_storage_host_id': 'host3', + } + ] + init_1 = { + 'initiatorId': '1001', + 'wwn': '1001', + 'alias': 'I1', + 'host': 'host1', + 'on_fabric': True, + 'type': 'FIBRE' + } + init_2 = { + 'initiatorId': '1002', + 'wwn': '1002', + 'alias': 'I2', + 'host': 'host2', + 'type': 'ISCSI' + } + init_3 = { + 'initiatorId': '1003', + 'wwn': '1003', + 'alias': 'I3', + 'host': 'host3', + 'type': 'FIBRE' + } + + kwargs = VMAX_STORAGE_CONF + mock_version.return_value = ['V9.2.2.7', '92'] + mock_unisphere_version.return_value = ['V9.2.2.7', '92'] + mock_array.return_value = {'symmetrixId': ['00112233']} + mock_initiators.side_effect = [['1001', '1002', '1003']] + mock_initiator.side_effect = [init_1, init_2, init_3] + + driver = VMAXStorageDriver(**kwargs) + self.assertEqual(driver.storage_id, "12345") + self.assertEqual(driver.client.array_id, "00112233") + ret = driver.list_storage_host_initiators(context) + self.assertDictEqual(ret[0], expected[0]) + self.assertDictEqual(ret[1], expected[1]) + self.assertDictEqual(ret[2], expected[2]) + + mock_initiators.side_effect = [['1001']] + mock_initiator.side_effect = [exception.StorageBackendException] + with self.assertRaises(Exception) as exc: + driver.list_storage_host_initiators(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + mock_initiators.side_effect = [exception.StorageBackendException] + mock_initiator.side_effect = [init_1] + with self.assertRaises(Exception) as exc: + driver.list_storage_host_initiators(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + @mock.patch.object(VMaxRest, 'get_host') + @mock.patch.object(VMaxRest, 'get_host_list') + @mock.patch.object(VMaxRest, 'get_array_detail') + @mock.patch.object(VMaxRest, 'get_uni_version') + @mock.patch.object(VMaxRest, 'get_unisphere_version') + def test_list_storage_hosts(self, mock_unisphere_version, + mock_version, mock_array, + mock_hosts, mock_host): + expected = \ + [ + { + 'storage_id': '12345', + 'name': 'h1', + 'native_storage_host_id': 'h1', + 'os_type': 'Unknown', + 'status': 'normal', + }, + { + 'storage_id': '12345', + 'name': 'h2', + 'native_storage_host_id': 'h2', + 'os_type': 'Unknown', + 'status': 'normal', + }, + { + 'storage_id': '12345', + 'name': 'h3', + 'native_storage_host_id': 'h3', + 'os_type': 'Unknown', + 'status': 'normal', + } + ] + host_1 = { + 'hostId': 'h1', + } + host_2 = { + 'hostId': 'h2', + } + host_3 = { + 'hostId': 'h3', + } + + kwargs = VMAX_STORAGE_CONF + mock_version.return_value = ['V9.2.2.7', '92'] + mock_unisphere_version.return_value = ['V9.2.2.7', '92'] + mock_array.return_value = {'symmetrixId': ['00112233']} + mock_hosts.side_effect = [['h1', 'h2', 'h3']] + mock_host.side_effect = [host_1, host_2, host_3] + + driver = VMAXStorageDriver(**kwargs) + self.assertEqual(driver.storage_id, "12345") + self.assertEqual(driver.client.array_id, "00112233") + ret = driver.list_storage_hosts(context) + self.assertDictEqual(ret[0], expected[0]) + self.assertDictEqual(ret[1], expected[1]) + self.assertDictEqual(ret[2], expected[2]) + + mock_hosts.side_effect = [['h1']] + mock_host.side_effect = [exception.StorageBackendException] + with self.assertRaises(Exception) as exc: + driver.list_storage_hosts(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + mock_hosts.side_effect = [exception.StorageBackendException] + mock_host.side_effect = [host_1] + with self.assertRaises(Exception) as exc: + driver.list_storage_hosts(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + @mock.patch.object(VMaxRest, 'get_host_group') + @mock.patch.object(VMaxRest, 'get_host_group_list') + @mock.patch.object(VMaxRest, 'get_array_detail') + @mock.patch.object(VMaxRest, 'get_uni_version') + @mock.patch.object(VMaxRest, 'get_unisphere_version') + def test_list_storage_host_groups(self, mock_unisphere_version, + mock_version, mock_array, + mock_host_groups, mock_host_group): + expected = \ + [ + { + 'name': 'hg1', + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg1', + }, + { + 'name': 'hg2', + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg2', + }, + { + 'name': 'hg3', + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg3', + } + ] + expected_rel = [ + { + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg1', + 'native_storage_host_id': 'h1', + }, + { + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg1', + 'native_storage_host_id': 'h2', + }, + { + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg2', + 'native_storage_host_id': 'h2', + }, + { + 'storage_id': '12345', + 'native_storage_host_group_id': 'hg3', + 'native_storage_host_id': 'h1', + }, + ] + hg_1 = { + 'hostGroupId': 'hg1', + 'host': [{'hostId': 'h1'}, {'hostId': 'h2'}], + } + hg_2 = { + 'hostGroupId': 'hg2', + 'host': [{'hostId': 'h2'}], + } + hg_3 = { + 'hostGroupId': 'hg3', + 'host': [{'hostId': 'h1'}], + } + + kwargs = VMAX_STORAGE_CONF + mock_version.return_value = ['V9.2.2.7', '92'] + mock_unisphere_version.return_value = ['V9.2.2.7', '92'] + mock_array.return_value = {'symmetrixId': ['00112233']} + mock_host_groups.side_effect = [['hg1', 'hg2', 'hg3']] + mock_host_group.side_effect = [hg_1, hg_2, hg_3] + + driver = VMAXStorageDriver(**kwargs) + self.assertEqual(driver.storage_id, "12345") + self.assertEqual(driver.client.array_id, "00112233") + ret = driver.list_storage_host_groups(context) + ret_hgs = ret['storage_host_groups'] + ret_hg_rels = ret['storage_host_grp_host_rels'] + self.assertDictEqual(ret_hgs[0], expected[0]) + self.assertDictEqual(ret_hgs[1], expected[1]) + self.assertDictEqual(ret_hgs[2], expected[2]) + self.assertDictEqual(ret_hg_rels[0], expected_rel[0]) + self.assertDictEqual(ret_hg_rels[1], expected_rel[1]) + self.assertDictEqual(ret_hg_rels[2], expected_rel[2]) + self.assertDictEqual(ret_hg_rels[3], expected_rel[3]) + + mock_host_groups.side_effect = [['hg1']] + mock_host_group.side_effect = [exception.StorageBackendException] + with self.assertRaises(Exception) as exc: + driver.list_storage_host_groups(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + mock_host_groups.side_effect = [exception.StorageBackendException] + mock_host_group.side_effect = [hg_1] + with self.assertRaises(Exception) as exc: + driver.list_storage_host_groups(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + @mock.patch.object(VMaxRest, 'get_port_group') + @mock.patch.object(VMaxRest, 'get_port_group_list') + @mock.patch.object(VMaxRest, 'get_array_detail') + @mock.patch.object(VMaxRest, 'get_uni_version') + @mock.patch.object(VMaxRest, 'get_unisphere_version') + def test_list_port_groups(self, mock_unisphere_version, + mock_version, mock_array, + mock_port_groups, mock_port_group): + expected = \ + [ + { + 'name': 'pg1', + 'storage_id': '12345', + 'native_port_group_id': 'pg1', + }, + { + 'name': 'pg2', + 'storage_id': '12345', + 'native_port_group_id': 'pg2', + }, + { + 'name': 'pg3', + 'storage_id': '12345', + 'native_port_group_id': 'pg3', + } + ] + expected_rel = [ + { + 'storage_id': '12345', + 'native_port_group_id': 'pg1', + 'native_port_id': 'FA-1D:1', + }, + { + 'storage_id': '12345', + 'native_port_group_id': 'pg1', + 'native_port_id': 'FA-1D:2', + }, + { + 'storage_id': '12345', + 'native_port_group_id': 'pg2', + 'native_port_id': 'FA-2D:2', + }, + { + 'storage_id': '12345', + 'native_port_group_id': 'pg3', + 'native_port_id': 'FA-3D:1', + }, + ] + pg_1 = { + 'hostGroupId': 'hg1', + 'symmetrixPortKey': [ + { + "directorId": "FA-1D", + "portId": "1" + }, + { + "directorId": "FA-1D", + "portId": "2" + } + ], + } + pg_2 = { + 'hostGroupId': 'hg2', + 'symmetrixPortKey': [ + { + "directorId": "FA-2D", + "portId": "2" + } + ], + } + pg_3 = { + 'hostGroupId': 'hg3', + 'symmetrixPortKey': [ + { + "directorId": "FA-3D", + "portId": "1" + }, + ], + } + + kwargs = VMAX_STORAGE_CONF + mock_version.return_value = ['V9.2.2.7', '92'] + mock_unisphere_version.return_value = ['V9.2.2.7', '92'] + mock_array.return_value = {'symmetrixId': ['00112233']} + mock_port_groups.side_effect = [['pg1', 'pg2', 'pg3']] + mock_port_group.side_effect = [pg_1, pg_2, pg_3] + + driver = VMAXStorageDriver(**kwargs) + self.assertEqual(driver.storage_id, "12345") + self.assertEqual(driver.client.array_id, "00112233") + ret = driver.list_port_groups(context) + ret_pgs = ret['port_groups'] + ret_pg_rels = ret['port_grp_port_rels'] + self.assertDictEqual(ret_pgs[0], expected[0]) + self.assertDictEqual(ret_pgs[1], expected[1]) + self.assertDictEqual(ret_pgs[2], expected[2]) + self.assertDictEqual(ret_pg_rels[0], expected_rel[0]) + self.assertDictEqual(ret_pg_rels[1], expected_rel[1]) + self.assertDictEqual(ret_pg_rels[2], expected_rel[2]) + self.assertDictEqual(ret_pg_rels[3], expected_rel[3]) + + mock_port_groups.side_effect = [['pg1']] + mock_port_group.side_effect = [exception.StorageBackendException] + with self.assertRaises(Exception) as exc: + driver.list_port_groups(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + mock_port_groups.side_effect = [exception.StorageBackendException] + mock_port_group.side_effect = [pg_1] + with self.assertRaises(Exception) as exc: + driver.list_port_groups(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + @mock.patch.object(VMaxRest, 'get_volume_list') + @mock.patch.object(VMaxRest, 'get_volume_group_list') + @mock.patch.object(VMaxRest, 'get_array_detail') + @mock.patch.object(VMaxRest, 'get_uni_version') + @mock.patch.object(VMaxRest, 'get_unisphere_version') + def test_list_volume_groups(self, mock_unisphere_version, + mock_version, mock_array, + mock_volume_groups, mock_volumes): + expected = \ + [ + { + 'name': 'vg1', + 'storage_id': '12345', + 'native_volume_group_id': 'vg1', + }, + { + 'name': 'vg2', + 'storage_id': '12345', + 'native_volume_group_id': 'vg2', + }, + { + 'name': 'vg3', + 'storage_id': '12345', + 'native_volume_group_id': 'vg3', + } + ] + expected_rel = [ + { + 'storage_id': '12345', + 'native_volume_group_id': 'vg1', + 'native_volume_id': 'volume1', + }, + { + 'storage_id': '12345', + 'native_volume_group_id': 'vg1', + 'native_volume_id': 'volume2', + }, + { + 'storage_id': '12345', + 'native_volume_group_id': 'vg2', + 'native_volume_id': 'volume2', + }, + { + 'storage_id': '12345', + 'native_volume_group_id': 'vg3', + 'native_volume_id': 'volume1', + }, + ] + v_1 = ['volume1', 'volume2'] + v_2 = ['volume2'] + v_3 = ['volume1'] + + kwargs = VMAX_STORAGE_CONF + mock_version.return_value = ['V9.2.2.7', '92'] + mock_unisphere_version.return_value = ['V9.2.2.7', '92'] + mock_array.return_value = {'symmetrixId': ['00112233']} + mock_volume_groups.side_effect = [['vg1', 'vg2', 'vg3']] + mock_volumes.side_effect = [v_1, v_2, v_3] + + driver = VMAXStorageDriver(**kwargs) + self.assertEqual(driver.storage_id, "12345") + self.assertEqual(driver.client.array_id, "00112233") + ret = driver.list_volume_groups(context) + ret_vgs = ret['volume_groups'] + ret_vg_rels = ret['vol_grp_vol_rels'] + self.assertDictEqual(ret_vgs[0], expected[0]) + self.assertDictEqual(ret_vgs[1], expected[1]) + self.assertDictEqual(ret_vgs[2], expected[2]) + self.assertDictEqual(ret_vg_rels[0], expected_rel[0]) + self.assertDictEqual(ret_vg_rels[1], expected_rel[1]) + self.assertDictEqual(ret_vg_rels[2], expected_rel[2]) + self.assertDictEqual(ret_vg_rels[3], expected_rel[3]) + + mock_volume_groups.side_effect = [['vg1']] + mock_volumes.side_effect = [exception.StorageBackendException] + with self.assertRaises(Exception) as exc: + driver.list_volume_groups(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + mock_volume_groups.side_effect = [exception.StorageBackendException] + mock_volumes.side_effect = [v_1] + with self.assertRaises(Exception) as exc: + driver.list_volume_groups(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + @mock.patch.object(VMaxRest, 'get_masking_view') + @mock.patch.object(VMaxRest, 'get_masking_view_list') + @mock.patch.object(VMaxRest, 'get_array_detail') + @mock.patch.object(VMaxRest, 'get_uni_version') + @mock.patch.object(VMaxRest, 'get_unisphere_version') + def test_list_masking_views(self, mock_unisphere_version, + mock_version, mock_array, + mock_masking_views, mock_masking_view): + expected = \ + [ + { + 'storage_id': '12345', + 'native_storage_host_id': 'host1', + 'native_storage_host_group_id': 'hg1', + 'native_volume_group_id': 'sg1', + 'native_port_group_id': 'pg1', + 'native_masking_view_id': 'mv1', + 'name': 'mv1', + }, + { + 'storage_id': '12345', + 'native_storage_host_id': 'host2', + 'native_storage_host_group_id': 'hg2', + 'native_volume_group_id': 'sg2', + 'native_port_group_id': 'pg2', + 'native_masking_view_id': 'mv2', + 'name': 'mv2', + }, + { + 'storage_id': '12345', + 'native_storage_host_id': 'host3', + 'native_storage_host_group_id': 'hg3', + 'native_volume_group_id': 'sg3', + 'native_port_group_id': 'pg3', + 'native_masking_view_id': 'mv3', + 'name': 'mv3', + } + ] + mv_1 = { + 'maskingViewId': 'mv1', + 'hostId': 'host1', + 'hostGroupId': 'hg1', + 'storageGroupId': 'sg1', + 'portGroupId': 'pg1', + } + mv_2 = { + 'maskingViewId': 'mv2', + 'hostId': 'host2', + 'hostGroupId': 'hg2', + 'storageGroupId': 'sg2', + 'portGroupId': 'pg2', + } + mv_3 = { + 'maskingViewId': 'mv3', + 'hostId': 'host3', + 'hostGroupId': 'hg3', + 'storageGroupId': 'sg3', + 'portGroupId': 'pg3', + } + + kwargs = VMAX_STORAGE_CONF + mock_version.return_value = ['V9.2.2.7', '92'] + mock_unisphere_version.return_value = ['V9.2.2.7', '92'] + mock_array.return_value = {'symmetrixId': ['00112233']} + mock_masking_views.side_effect = [['mv1', 'mv2', 'mv3']] + mock_masking_view.side_effect = [mv_1, mv_2, mv_3] + + driver = VMAXStorageDriver(**kwargs) + self.assertEqual(driver.storage_id, "12345") + self.assertEqual(driver.client.array_id, "00112233") + ret = driver.list_masking_views(context) + self.assertDictEqual(ret[0], expected[0]) + self.assertDictEqual(ret[1], expected[1]) + self.assertDictEqual(ret[2], expected[2]) + + mock_masking_views.side_effect = [['mv1']] + mock_masking_view.side_effect = [exception.StorageBackendException] + with self.assertRaises(Exception) as exc: + driver.list_masking_views(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + + mock_masking_views.side_effect = [exception.StorageBackendException] + mock_masking_view.side_effect = [mv_1] + with self.assertRaises(Exception) as exc: + driver.list_masking_views(context) + + self.assertIn('Exception from Storage Backend', + str(exc.exception)) + @mock.patch.object(Session, 'request') @mock.patch.object(VMaxRest, 'get_array_detail') @mock.patch.object(VMaxRest, 'get_uni_version')