From: peter_wang Date: Tue, 30 Jun 2015 02:56:40 +0000 (-0400) Subject: Add white list support for target ports in VNX driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=ac2157ecebe2bc9b4014b1e592d728fed90e7eb2;p=openstack-build%2Fcinder-build.git Add white list support for target ports in VNX driver Currently, VNX cinder driver registers host initiator to all configured iSCSI/FC ports on array and chooses ports from all available ports to return in initialize_connection. By introducing a new driver option io_port_list: 1. driver will only register initiator to ports in io_port_list 2. driver will only return ports in io_port_list from initialize_connection Change-Id: Ie64eceb73bc0d70076033a29295c3efc57f34db4 Closes-Bug: 1470003 --- diff --git a/cinder/tests/unit/test_emc_vnxdirect.py b/cinder/tests/unit/test_emc_vnxdirect.py index a951f584e..3aa1a13e4 100644 --- a/cinder/tests/unit/test_emc_vnxdirect.py +++ b/cinder/tests/unit/test_emc_vnxdirect.py @@ -738,7 +738,50 @@ State: Ready "SP: A\n" + "Port ID: 5\n" + "Port WWN: iqn.1992-04.com.emc:cx.fnm00124000215.a5\n" + - "iSCSI Alias: 0215.a5\n", 0) + "iSCSI Alias: 0215.a5\n" + + "SP: A\n" + + "Port ID: 0\n" + + "Port WWN: iqn.1992-04.com.emc:cx.fnm00124000215.a0\n" + + "iSCSI Alias: 0215.a0\n\n" + + "Virtual Port ID: 0\n" + + "VLAN ID: Disabled\n" + + "IP Address: 10.244.214.119\n\n" + + "SP: B\n" + + "Port ID: 2\n" + + "Port WWN: iqn.1992-04.com.emc:cx.fnm00124000215.b2\n" + + "iSCSI Alias: 0215.b2\n\n" + + "Virtual Port ID: 0\n" + + "VLAN ID: Disabled\n" + + "IP Address: 10.244.214.120\n\n", 0) + + WHITE_LIST_PORTS = ("""SP: A +Port ID: 0 +Port WWN: iqn.1992-04.com.emc:cx.fnmxxx.a0 +iSCSI Alias: 0235.a7 + +Virtual Port ID: 0 +VLAN ID: Disabled +IP Address: 192.168.3.52 + +SP: A +Port ID: 9 +Port WWN: iqn.1992-04.com.emc:cx.fnmxxx.a9 +iSCSI Alias: 0235.a9 + +SP: A +Port ID: 4 +Port WWN: iqn.1992-04.com.emc:cx.fnmxxx.a4 +iSCSI Alias: 0235.a4 + +SP: B +Port ID: 2 +Port WWN: iqn.1992-04.com.emc:cx.fnmxxx.b2 +iSCSI Alias: 0235.b6 + +Virtual Port ID: 0 +VLAN ID: Disabled +IP Address: 192.168.4.53 +""", 0) iscsi_connection_info = \ {'data': {'target_discovered': True, @@ -793,7 +836,18 @@ State: Ready "50:06:01:62:08:60:01:95\n" + "Link Status: Down\n" + "Port Status: Online\n" + - "Switch Present: NO\n", 0) + "Switch Present: NO\n" + + "\n" + + "SP Name: SP B\n" + + "SP Port ID: 2\n" + + "SP UID: 50:06:01:60:88:60:08:0F:" + "50:06:01:6A:08:60:08:0F\n" + + "Link Status: Up\n" + + "Port Status: Online\n" + + "Switch Present: YES\n" + + "Switch UID: 10:00:50:EB:1A:03:3F:59:" + "20:11:50:EB:1A:03:3F:59\n" + + "SP Source ID: 69888\n", 0) FAKEHOST_PORTS = ( "Information about each HBA:\n" + @@ -822,6 +876,14 @@ State: Ready " Defined: YES\n" + " Initiator Type: 3\n" + " StorageGroup Name: fakehost\n\n" + + " SP Name: SP B\n" + + " SP Port ID: 2\n" + + " HBA Devicename:\n" + + " Trusted: NO\n" + + " Logged In: YES\n" + + " Defined: YES\n" + + " Initiator Type: 3\n" + + " StorageGroup Name: fakehost\n\n" "Information about each SPPORT:\n" + "\n" + "SP Name: SP A\n" + @@ -849,7 +911,18 @@ State: Ready "50:06:01:62:08:60:01:95\n" + "Link Status: Down\n" + "Port Status: Online\n" + - "Switch Present: NO\n", 0) + "Switch Present: NO\n" + + "\n" + + "SP Name: SP B\n" + + "SP Port ID: 2\n" + + "SP UID: 50:06:01:60:88:60:01:95:" + + "50:06:01:6A:08:60:08:0F\n" + + "Link Status: Up\n" + + "Port Status: Online\n" + + "Switch Present: YES\n" + + "Switch UID: 10:00:00:05:1E:72:EC:A6:" + + "20:46:00:05:1E:72:EC:A6\n" + + "SP Source ID: 272896\n", 0) def LUN_PROPERTY(self, name, is_thin=False, has_snap=False, size=1, state='Ready', faulted='false', operation='None', @@ -886,6 +959,26 @@ State: Ready 'operation': operation, 'is_thin': 'Yes' if is_thin else 'No'}, 0) + def STORAGE_GROUP_ISCSI_FC_HBA(self, sgname): + + return ("""\ + Storage Group Name: %s + Storage Group UID: 54:46:57:0F:15:A2:E3:11:9A:8D:FF:E5:3A:03:FD:6D + HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + iqn.1993-08.org.debian:01:222 SP A 4 + 22:34:56:78:90:12:34:56:12:34:56:78:90:12:34:56 SP B 2 + 22:34:56:78:90:54:32:16:12:34:56:78:90:54:32:16 SP B 2 + + HLU/ALU Pairs: + + HLU Number ALU Number + ---------- ---------- + 1 1 + Shareable: YES""" % sgname, 0) + def STORAGE_GROUP_NO_MAP(self, sgname): return ("""\ Storage Group Name: %s @@ -910,6 +1003,26 @@ State: Ready 1 1 Shareable: YES""" % sgname, 0) + def STORAGE_GROUP_HAS_MAP_ISCSI(self, sgname): + + return ("""\ + Storage Group Name: %s + Storage Group UID: 54:46:57:0F:15:A2:E3:11:9A:8D:FF:E5:3A:03:FD:6D + HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + iqn.1993-08.org.debian:01:222 SP A 2 + iqn.1993-08.org.debian:01:222 SP A 0 + iqn.1993-08.org.debian:01:222 SP B 2 + + HLU/ALU Pairs: + + HLU Number ALU Number + ---------- ---------- + 1 1 + Shareable: YES""" % sgname, 0) + def STORAGE_GROUP_HAS_MAP_MP(self, sgname): return ("""\ @@ -1100,12 +1213,15 @@ class DriverTestCaseBase(test.TestCase): return standard_default def fake_command_execute_for_driver_setup(self, *command, **kwargv): - if command == ('connection', '-getport', '-address', '-vlanid'): + if (command == ('connection', '-getport', '-address', '-vlanid') or + command == ('connection', '-getport', '-vlanid')): return self.testData.ALL_PORTS elif command == ('storagepool', '-list', '-state'): return self.testData.POOL_GET_STATE_RESULT([ {'pool_name': self.testData.test_pool_name, 'state': "Ready"}, {'pool_name': "unit_test_pool2", 'state': "Ready"}]) + if command == self.testData.GETFCPORT_CMD(): + return self.testData.FC_PORTS else: return SUCCEED @@ -1754,6 +1870,14 @@ Time Remaining: 0 second(s) '-hbauid', 'iqn.1993-08.org.debian:01:222', '-sp', 'A', '-spport', 4, '-spvport', 0, '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', '-setpath', + '-hbauid', 'iqn.1993-08.org.debian:01:222', + '-sp', 'A', '-spport', 0, '-spvport', 0, + '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', '-setpath', + '-hbauid', 'iqn.1993-08.org.debian:01:222', + '-sp', 'B', '-spport', 2, '-spvport', 0, + '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), mock.call('storagegroup', '-list', '-gname', 'fakehost', poll=True), mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, @@ -1921,6 +2045,74 @@ Time Remaining: 0 second(s) '10.0.0.2'))] fake_cli.assert_has_calls(expected) + @mock.patch('random.randint', + mock.Mock(return_value=0)) + def test_initialize_connection_iscsi_white_list(self): + self.configuration.io_port_list = 'a-0-0,B-2-0' + test_volume = self.testData.test_volume.copy() + test_volume['provider_location'] = 'system^fakesn|type^lun|id^1' + # Test for auto registration + self.configuration.initiator_auto_registration = True + commands = [('storagegroup', '-list', '-gname', 'fakehost')] + results = [[("No group", 83), + self.testData.STORAGE_GROUP_HAS_MAP_ISCSI('fakehost')]] + fake_cli = self.driverSetup(commands, results) + self.driver.cli.iscsi_targets = {'A': [{'SP': 'A', 'Port ID': 0, + 'Virtual Port ID': 0, + 'Port WWN': 'fake_iqn', + 'IP Address': '192.168.1.1'}], + 'B': [{'SP': 'B', 'Port ID': 2, + 'Virtual Port ID': 0, + 'Port WWN': 'fake_iqn1', + 'IP Address': '192.168.1.2'}]} + self.driver.initialize_connection( + test_volume, + self.testData.connector) + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-create', '-gname', 'fakehost'), + mock.call('storagegroup', '-gname', 'fakehost', '-setpath', + '-hbauid', 'iqn.1993-08.org.debian:01:222', + '-sp', 'A', '-spport', 0, '-spvport', 0, + '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', '-setpath', + '-hbauid', 'iqn.1993-08.org.debian:01:222', + '-sp', 'B', '-spport', 2, '-spvport', 0, + '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, + '-gname', 'fakehost', '-o', + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False)] + fake_cli.assert_has_calls(expected) + + @mock.patch('cinder.volume.drivers.emc.emc_vnx_cli.' + 'EMCVnxCliBase._build_pool_stats', + mock.Mock(return_value=None)) + @mock.patch('cinder.volume.drivers.emc.emc_vnx_cli.' + 'CommandLineHelper.get_pool', + mock.Mock(return_value={'total_capacity_gb': 0.0, + 'free_capacity_gb': 0.0})) + def test_update_iscsi_io_ports(self): + self.configuration.io_port_list = 'a-0-0,B-2-0' + # Test for auto registration + self.configuration.initiator_auto_registration = True + commands = [self.testData.GETPORT_CMD()] + results = [self.testData.WHITE_LIST_PORTS] + fake_cli = self.driverSetup(commands, results) + self.driver.cli.update_volume_stats() + expected = [mock.call(*self.testData.GETPORT_CMD(), poll=False)] + fake_cli.assert_has_calls(expected) + io_ports = self.driver.cli.iscsi_targets + self.assertEqual((0, 'iqn.1992-04.com.emc:cx.fnmxxx.a0'), + (io_ports['A'][0]['Port ID'], + io_ports['A'][0]['Port WWN'])) + self.assertEqual((2, 'iqn.1992-04.com.emc:cx.fnmxxx.b2'), + (io_ports['B'][0]['Port ID'], + io_ports['B'][0]['Port WWN'])) + @mock.patch( "oslo_concurrency.processutils.execute", mock.Mock( @@ -3959,12 +4151,24 @@ class EMCVNXCLIDriverFCTestCase(DriverTestCaseBase): '90:12:34:56', '-sp', 'A', '-spport', '0', '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:12:34:56:12:34:56:78:' + '90:12:34:56', + '-sp', 'B', '-spport', '2', '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), mock.call('storagegroup', '-gname', 'fakehost', '-setpath', '-hbauid', '22:34:56:78:90:54:32:16:12:34:56:78:' '90:54:32:16', '-sp', 'A', '-spport', '0', '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:54:32:16:12:34:56:78:' + '90:54:32:16', + '-sp', 'B', '-spport', '2', '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), mock.call('storagegroup', '-list', '-gname', 'fakehost', poll=True), mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, @@ -4049,6 +4253,106 @@ class EMCVNXCLIDriverFCTestCase(DriverTestCaseBase): mock.call('port', '-list', '-sp')] fake_cli.assert_has_calls(expected) + @mock.patch('random.randint', + mock.Mock(return_value=0)) + def test_initialize_connection_fc_white_list(self): + self.configuration.io_port_list = 'a-0,B-2' + test_volume = self.testData.test_volume.copy() + test_volume['provider_location'] = 'system^fakesn|type^lun|id^1' + self.configuration.initiator_auto_registration = True + commands = [('storagegroup', '-list', '-gname', 'fakehost'), + self.testData.GETFCPORT_CMD(), + ('port', '-list', '-gname', 'fakehost')] + results = [[("No group", 83), + self.testData.STORAGE_GROUP_HAS_MAP_ISCSI('fakehost')], + self.testData.FC_PORTS, + self.testData.FAKEHOST_PORTS] + + fake_cli = self.driverSetup(commands, results) + data = self.driver.initialize_connection( + test_volume, + self.testData.connector) + + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-create', '-gname', 'fakehost'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:12:34:56:' + '12:34:56:78:90:12:34:56', + '-sp', 'A', '-spport', 0, '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:12:34:56:' + '12:34:56:78:90:12:34:56', + '-sp', 'B', '-spport', 2, '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:54:32:16:' + '12:34:56:78:90:54:32:16', + '-sp', 'A', '-spport', 0, '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:54:32:16:' + '12:34:56:78:90:54:32:16', + '-sp', 'B', '-spport', 2, '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, + '-gname', 'fakehost', '-o', + poll=False), + mock.call('port', '-list', '-gname', 'fakehost')] + fake_cli.assert_has_calls(expected) + self.assertEqual(['5006016A0860080F', '5006016008600195'], + data['data']['target_wwn']) + + @mock.patch('random.randint', + mock.Mock(return_value=0)) + def test_initialize_connection_fc_port_registered_wl(self): + self.configuration.io_port_list = 'a-0,B-2' + test_volume = self.testData.test_volume.copy() + test_volume['provider_location'] = 'system^fakesn|type^lun|id^1' + self.configuration.initiator_auto_registration = True + commands = [('storagegroup', '-list', '-gname', 'fakehost'), + self.testData.GETFCPORT_CMD(), + ('port', '-list', '-gname', 'fakehost')] + results = [self.testData.STORAGE_GROUP_ISCSI_FC_HBA('fakehost'), + self.testData.FC_PORTS, + self.testData.FAKEHOST_PORTS] + + fake_cli = self.driverSetup(commands, results) + data = self.driver.initialize_connection( + test_volume, + self.testData.connector) + + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:12:34:56:' + '12:34:56:78:90:12:34:56', + '-sp', 'A', '-spport', 0, '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:54:32:16:' + '12:34:56:78:90:54:32:16', + '-sp', 'A', '-spport', 0, '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, + '-gname', 'fakehost', '-o', + poll=False), + mock.call('port', '-list', '-gname', 'fakehost')] + fake_cli.assert_has_calls(expected) + self.assertEqual(['5006016A0860080F', '5006016008600195'], + data['data']['target_wwn']) + @mock.patch( "cinder.zonemanager.fc_san_lookup_service.FCSanLookupService." + "get_device_mapping_from_network", diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index 3a4e39752..08ffe3783 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -58,6 +58,7 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): Create consistency group from cgsnapshot support Multiple pools support enhancement Manage/unmanage volume revise + White list target ports support """ def __init__(self, *args, **kwargs): diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index 464c8db95..8d5da1322 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -56,6 +56,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): Create consistency group from cgsnapshot support Multiple pools support enhancement Manage/unmanage volume revise + White list target ports support """ def __init__(self, *args, **kwargs): diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 69f68e344..ab371f937 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -99,6 +99,10 @@ loc_opts = [ default='', help='Mapping between hostname and ' 'its iSCSI initiator IP addresses.'), + cfg.StrOpt('io_port_list', + default='*', + help='Comma separated iSCSI or FC ports ' + 'to be used in Nova or Cinder.'), cfg.BoolOpt('initiator_auto_registration', default=False, help='Automatically register initiators. ' @@ -1363,7 +1367,8 @@ class CommandLineHelper(object): return data - def get_status_up_ports(self, storage_group_name, poll=True): + def get_status_up_ports(self, storage_group_name, io_ports=None, + poll=True): """Function to get ports whose status are up.""" cmd_get_hba = ('storagegroup', '-list', '-gname', storage_group_name) out, rc = self.command_execute(*cmd_get_hba, poll=poll) @@ -1379,6 +1384,9 @@ class CommandLineHelper(object): if 0 != rc: self._raise_cli_error(cmd_get_port, rc, out) for i, sp in enumerate(sps): + if io_ports: # Skip ports which are not in io_ports + if (sp.split()[1], int(portid[i])) not in io_ports: + continue wwn = self.get_port_wwn(sp, portid[i], out) if (wwn is not None) and (wwn not in wwns): LOG.debug('Add wwn:%(wwn)s for sg:%(sg)s.', @@ -1392,8 +1400,8 @@ class CommandLineHelper(object): self._raise_cli_error(cmd_get_hba, rc, out) return wwns - def get_login_ports(self, storage_group_name, connector_wwpns): - + def get_login_ports(self, storage_group_name, connector_wwpns, + io_ports=None): cmd_list_hba = ('port', '-list', '-gname', storage_group_name) out, rc = self.command_execute(*cmd_list_hba) ports = [] @@ -1414,9 +1422,14 @@ class CommandLineHelper(object): 'HBA Devicename:.*\n\s*' + 'Trusted:.*\n\s*' + 'Logged In:\s*YES\n') + for each in connector_hba_list: ports.extend(re.findall(port_pat, each)) ports = list(set(ports)) + if io_ports: + ports = filter(lambda po: + (po[0].split()[1], int(po[1])) in io_ports, + ports) for each in ports: wwn = self.get_port_wwn(each[0], each[1], allports) if wwn: @@ -1426,16 +1439,16 @@ class CommandLineHelper(object): return wwns def get_port_wwn(self, sp, port_id, allports=None): + """Returns wwn via sp and port_id + + :param sp: should be in this format 'SP A' + :param port_id: '0' or 0 + """ wwn = None if allports is None: - cmd_get_port = ('port', '-list', '-sp') - out, rc = self.command_execute(*cmd_get_port) - if 0 != rc: - self._raise_cli_error(cmd_get_port, rc, out) - else: - allports = out + allports, rc = self.get_port_output() _re_port_wwn = re.compile('SP Name:\s*' + sp + - '\nSP Port ID:\s*' + port_id + + '\nSP Port ID:\s*' + str(port_id) + '\nSP UID:\s*((\w\w:){15}(\w\w))' + '\nLink Status: Up' + '\nPort Status: Online') @@ -1445,11 +1458,7 @@ class CommandLineHelper(object): return wwn def get_fc_targets(self): - fc_getport = ('port', '-list', '-sp') - out, rc = self.command_execute(*fc_getport) - if rc != 0: - self._raise_cli_error(fc_getport, rc, out) - + out, rc = self.get_port_output() fc_target_dict = {'A': [], 'B': []} _fcport_pat = (r'SP Name: SP\s(\w)\s*' @@ -1465,7 +1474,42 @@ class CommandLineHelper(object): 'Port ID': sp_port_id}) return fc_target_dict - def get_iscsi_targets(self, poll=True): + def get_port_output(self): + cmd_get_port = ('port', '-list', '-sp') + out, rc = self.command_execute(*cmd_get_port) + if 0 != rc: + self._raise_cli_error(cmd_get_port, rc, out) + return out, rc + + def get_connection_getport_output(self): + connection_getport_cmd = ('connection', '-getport', '-vlanid') + out, rc = self.command_execute(*connection_getport_cmd) + if 0 != rc: + self._raise_cli_error(connection_getport_cmd, rc, out) + return out, rc + + def _filter_iscsi_ports(self, all_ports, io_ports): + """Filter ports in white list from all iSCSI ports.""" + new_iscsi_ports = {'A': [], 'B': []} + valid_ports = [] + for sp in all_ports: + for port in all_ports[sp]: + port_tuple = (port['SP'], + port['Port ID'], + port['Virtual Port ID']) + if port_tuple in io_ports: + new_iscsi_ports[sp].append(port) + valid_ports.append(port_tuple) + if len(io_ports) != len(valid_ports): + invalid_port_set = set(io_ports) - set(valid_ports) + for invalid in invalid_port_set: + LOG.warning(_LW('Invalid iSCSI port %(sp)s-%(port)s-%(vlan)s ' + 'found in io_port_list, will be ignored.'), + {'sp': invalid[0], 'port': invalid[1], + 'vlan': invalid[2]}) + return new_iscsi_ports + + def get_iscsi_targets(self, poll=False, io_ports=None): cmd_getport = ('connection', '-getport', '-address', '-vlanid') out, rc = self.command_execute(*cmd_getport, poll=poll) if rc != 0: @@ -1498,7 +1542,8 @@ class CommandLineHelper(object): 'Port WWN': iqn, 'Virtual Port ID': vport_id, 'IP Address': ip_addr}) - + if io_ports: + return self._filter_iscsi_ports(iscsi_target_dict, io_ports) return iscsi_target_dict def get_registered_spport_set(self, initiator_iqn, sgname, sg_raw_out): @@ -1755,8 +1800,11 @@ class EMCVnxCliBase(object): conf_pools = self.configuration.safe_get("storage_vnx_pool_names") self.storage_pools = self._get_managed_storage_pools(conf_pools) self.array_serial = None + self.io_ports = self._parse_ports(self.configuration.io_port_list, + self.protocol) if self.protocol == 'iSCSI': - self.iscsi_targets = self._client.get_iscsi_targets(poll=True) + self.iscsi_targets = self._client.get_iscsi_targets( + poll=True, io_ports=self.io_ports) self.hlu_cache = {} self.force_delete_lun_in_sg = ( self.configuration.force_delete_lun_in_storagegroup) @@ -1802,6 +1850,59 @@ class EMCVnxCliBase(object): "manage all the pools on the VNX system.") return storage_pools + def _parse_ports(self, io_port_list, protocol): + """Validates IO port format, supported format is a-1, b-3, a-3-0.""" + if not io_port_list or io_port_list == '*': + return None + ports = re.split('\s*,\s*', io_port_list) + valid_ports = [] + invalid_ports = [] + if 'iSCSI' == protocol: + out, rc = self._client.get_connection_getport_output() + for port in ports: + port_tuple = port.split('-') + if (re.match('[abAB]-\d+-\d+$', port) and + self._validate_iscsi_port( + port_tuple[0], port_tuple[1], port_tuple[2], out)): + valid_ports.append( + (port_tuple[0].upper(), int(port_tuple[1]), + int(port_tuple[2]))) + else: + invalid_ports.append(port) + elif 'FC' == protocol: + out, rc = self._client.get_port_output() + for port in ports: + port_tuple = port.split('-') + if re.match('[abAB]-\d+$', port) and self._validate_fc_port( + port_tuple[0], port_tuple[1], out): + valid_ports.append( + (port_tuple[0].upper(), int(port_tuple[1]))) + else: + invalid_ports.append(port) + if len(invalid_ports) > 0: + msg = _('Invalid %(protocol)s ports %(port)s specified ' + 'for io_port_list.') % {'protocol': self.protocol, + 'port': ','.join(invalid_ports)} + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + return valid_ports + + def _validate_iscsi_port(self, sp, port_id, vlan_id, cmd_output): + """Validates whether the iSCSI port is existed on VNX""" + iscsi_pattern = ('SP:\s+' + sp.upper() + + '\nPort ID:\s+' + str(port_id) + + '\nPort WWN:\s+.*' + + '\niSCSI Alias:\s+.*\n' + '\nVirtual Port ID:\s+' + str(vlan_id)) + return re.search(iscsi_pattern, cmd_output) + + def _validate_fc_port(self, sp, port_id, cmd_output): + """Validates whether the FC port is existed on VNX""" + fc_pattern = ('SP Name:\s*SP\s*' + sp.upper() + + '\nSP Port ID:\s*' + str(port_id) + + '\nSP UID:\s*((\w\w:){15}(\w\w))') + return re.search(fc_pattern, cmd_output) + def get_array_serial(self): if not self.array_serial: self.array_serial = self._client.get_array_serial() @@ -2275,7 +2376,6 @@ class EMCVnxCliBase(object): self.stats['consistencygroup_support'] = ( 'True' if '-VNXSnapshots' in self.enablers else 'False') - return self.stats def create_snapshot(self, snapshot): @@ -2673,8 +2773,60 @@ class EMCVnxCliBase(object): 'portid': port_id, 'msg': out}) - def _register_iscsi_initiator(self, ip, host, initiator_uids): - iscsi_targets = self.iscsi_targets + def auto_register_with_io_port_filter(self, connector, sgdata, + io_port_filter): + """Automatically register specific IO ports to storage group.""" + initiator = connector['initiator'] + ip = connector['ip'] + host = connector['host'] + new_white = {'A': [], 'B': []} + if self.protocol == 'iSCSI': + if sgdata: + sp_ports = self._client.get_registered_spport_set( + initiator, host, sgdata['raw_output']) + # Normalize io_ports + for sp in ('A', 'B'): + new_ports = filter( + lambda pt: (pt['SP'], pt['Port ID']) not in sp_ports, + self.iscsi_targets[sp]) + new_white[sp] = map(lambda white: + {'SP': white['SP'], + 'Port ID': white['Port ID'], + 'Virtual Port ID': + white['Virtual Port ID']}, + new_ports) + else: + new_white = self.iscsi_targets + self._register_iscsi_initiator(ip, host, [initiator], new_white) + + elif self.protocol == 'FC': + wwns = self._extract_fc_uids(connector) + ports_list = [] + if sgdata: + for wwn in wwns: + for port in io_port_filter: + if ((port not in ports_list) and + (not re.search(wwn + '\s+SP\s+' + + port[0] + '\s+' + str(port[1]), + sgdata['raw_output'], + re.IGNORECASE))): + # Record ports to be added + ports_list.append(port) + new_white[port[0]].append({ + 'SP': port[0], + 'Port ID': port[1]}) + else: + # Need to translate to dict format + for fc_port in io_port_filter: + new_white[fc_port[0]].append({'SP': fc_port[0], + 'Port ID': fc_port[1]}) + self._register_fc_initiator(ip, host, wwns, new_white) + return new_white['A'] or new_white['B'] + + def _register_iscsi_initiator(self, ip, host, initiator_uids, + port_to_register=None): + iscsi_targets = (port_to_register if port_to_register else + self.iscsi_targets) for initiator_uid in initiator_uids: LOG.info(_LI('Get ISCSI targets %(tg)s to register ' 'initiator %(in)s.'), @@ -2698,8 +2850,10 @@ class EMCVnxCliBase(object): self._exec_command_setpath(initiator_uid, sp, port_id, ip, host, vport_id) - def _register_fc_initiator(self, ip, host, initiator_uids): - fc_targets = self._client.get_fc_targets() + def _register_fc_initiator(self, ip, host, initiator_uids, + ports_to_register=None): + fc_targets = (ports_to_register if ports_to_register else + self._client.get_fc_targets()) for initiator_uid in initiator_uids: LOG.info(_LI('Get FC targets %(tg)s to register ' 'initiator %(in)s.'), @@ -2754,7 +2908,7 @@ class EMCVnxCliBase(object): unregistered_initiators.append(initiator_uid) return unregistered_initiators - def auto_register_initiator(self, connector, sgdata): + def auto_register_initiator_to_all(self, connector, sgdata): """Automatically registers available initiators. Returns True if has registered initiator otherwise returns False. @@ -2799,6 +2953,17 @@ class EMCVnxCliBase(object): self._register_fc_initiator(ip, host, itors_toReg) return True + def auto_register_initiator(self, connector, sgdata, io_ports_filter=None): + """Automatically register available initiators. + + :returns: True if has registered initiator otherwise return False + """ + if io_ports_filter: + return self.auto_register_with_io_port_filter(connector, sgdata, + io_ports_filter) + else: + return self.auto_register_initiator_to_all(connector, sgdata) + def assure_host_access(self, volume, connector): hostname = connector['host'] volumename = volume['name'] @@ -2812,7 +2977,7 @@ class EMCVnxCliBase(object): # Storage Group has not existed yet self.assure_storage_group(hostname) if self.itor_auto_reg: - self.auto_register_initiator(connector, None) + self.auto_register_initiator(connector, None, self.io_ports) auto_registration_done = True else: self._client.connect_host_to_storage_group(hostname, hostname) @@ -2821,7 +2986,8 @@ class EMCVnxCliBase(object): poll=True) if self.itor_auto_reg and not auto_registration_done: - new_registerred = self.auto_register_initiator(connector, sgdata) + new_registerred = self.auto_register_initiator(connector, sgdata, + self.io_ports) if new_registerred: sgdata = self._client.get_storage_group(hostname, poll=True) @@ -2909,13 +3075,6 @@ class EMCVnxCliBase(object): properties['target_portal'] = \ "%s:3260" % targets[0]['IP Address'] properties['target_lun'] = hlu - - auth = volume['provider_auth'] - if auth: - (auth_method, auth_username, auth_secret) = auth.split() - properties['auth_method'] = auth_method - properties['auth_username'] = auth_username - properties['auth_password'] = auth_secret else: properties = {'target_discovered': False, 'target_iqns': None, @@ -2940,11 +3099,12 @@ class EMCVnxCliBase(object): 'target_dicovered': True, 'target_wwn': None} if self.zonemanager_lookup_service is None: - fc_properties['target_wwn'] = self.get_login_ports(connector) + fc_properties['target_wwn'] = self.get_login_ports(connector, + self.io_ports) else: target_wwns, itor_tgt_map = self.get_initiator_target_map( connector['wwpns'], - self.get_status_up_ports(connector)) + self.get_status_up_ports(connector, self.io_ports)) fc_properties['target_wwn'] = target_wwns fc_properties['initiator_target_map'] = itor_tgt_map return fc_properties @@ -3094,12 +3254,14 @@ class EMCVnxCliBase(object): self._build_provider_location_for_lun(lun_id)} return model_update - def get_login_ports(self, connector): + def get_login_ports(self, connector, io_ports=None): return self._client.get_login_ports(connector['host'], - connector['wwpns']) + connector['wwpns'], + io_ports) - def get_status_up_ports(self, connector): - return self._client.get_status_up_ports(connector['host']) + def get_status_up_ports(self, connector, io_ports=None): + return self._client.get_status_up_ports(connector['host'], + io_ports=io_ports) def get_initiator_target_map(self, fc_initiators, fc_targets): target_wwns = [] @@ -3272,9 +3434,9 @@ class EMCVnxCliBase(object): def update_volume_stats(self): """Retrieves stats info.""" self.update_enabler_in_volume_stats() - if self.protocol == 'iSCSI': - self.iscsi_targets = self._client.get_iscsi_targets(poll=False) + self.iscsi_targets = self._client.get_iscsi_targets( + poll=False, io_ports=self.io_ports) properties = [self._client.POOL_FREE_CAPACITY, self._client.POOL_TOTAL_CAPACITY,