From d4dd162bcddba85dee5920e147e0b9ce189be276 Mon Sep 17 00:00:00 2001 From: Shunei Shiono Date: Wed, 20 Dec 2017 18:21:06 +0900 Subject: [PATCH] NEC driver: add automatic configuration of SAN access control. NEC driver user has to register WWPNs or an initiator name of every node to storage. This is inconvenient for the user. This patch enables NEC driver to configure SAN access control automatically. Change-Id: I92b42600b71fbdfe1b84046c59ab485f333be889 Closes-Bug: #1737452 --- .../unit/volume/drivers/nec/test_volume.py | 47 ++++++++++++--- cinder/volume/drivers/nec/cli.py | 45 +++++++++++++- cinder/volume/drivers/nec/volume_common.py | 12 +++- cinder/volume/drivers/nec/volume_helper.py | 60 +++++++++++++++++-- ...c-auto-accesscontrol-55f4b090e8128f5e.yaml | 4 ++ 5 files changed, 152 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/nec-auto-accesscontrol-55f4b090e8128f5e.yaml diff --git a/cinder/tests/unit/volume/drivers/nec/test_volume.py b/cinder/tests/unit/volume/drivers/nec/test_volume.py index 73405165d8e..d2890265755 100644 --- a/cinder/tests/unit/volume/drivers/nec/test_volume.py +++ b/cinder/tests/unit/volume/drivers/nec/test_volume.py @@ -1048,25 +1048,54 @@ class NonDisruptiveBackup_test(volume_helper.MStorageDSVDriver, self._validate_ld_exist( self.lds, self.vol.id, self._properties['ld_name_format']) + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI._execute', + patch_execute) + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', new=mock.Mock()) def test_validate_iscsildset_exist(self): connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"} ldset = self._validate_iscsildset_exist(self.ldsets, connector) self.assertEqual('LX:OpenStack0', ldset['ldsetname']) - connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255XX"} - with self.assertRaisesRegexp(exception.NotFound, - 'Appropriate Logical Disk Set' - ' could not be found.'): - self._validate_iscsildset_exist(self.ldsets, connector) + connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f232XX"} + mock_data = {'ldsetname': 'LX:redhatd1d8e8f23', + 'protocol': 'iSCSI', + 'portal_list': ['1.1.1.1:3260', '2.2.2.2:3260'], + 'lds': {}, + 'initiator_list': + ['iqn.1994-05.com.redhat:d1d8e8f232XX']} + mock_ldset = {} + mock_ldset['LX:redhatd1d8e8f23'] = mock_data + mock_configs = mock.Mock() + self.configs = mock_configs + self.configs.return_value = None, None, mock_ldset, None, None, None + ldset = self._validate_iscsildset_exist(self.ldsets, connector) + self.assertEqual('LX:redhatd1d8e8f23', ldset['ldsetname']) + self.assertEqual('iqn.1994-05.com.redhat:d1d8e8f232XX', + ldset['initiator_list'][0]) + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI._execute', + patch_execute) + @mock.patch('cinder.volume.drivers.nec.cli.MStorageISMCLI.' + 'view_all', new=mock.Mock()) def test_validate_fcldset_exist(self): connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]} ldset = self._validate_fcldset_exist(self.ldsets, connector) self.assertEqual('LX:OpenStack1', ldset['ldsetname']) connector = {'wwpns': ["10000090FAA0786X", "10000090FAA0786Y"]} - with self.assertRaisesRegexp(exception.NotFound, - 'Appropriate Logical Disk Set' - ' could not be found.'): - self._validate_fcldset_exist(self.ldsets, connector) + mock_data = {'ldsetname': 'LX:10000090FAA0786X', + 'lds': {}, + 'protocol': 'FC', + 'wwpn': ["1000-0090-FAA0-786X", "1000-0090-FAA0-786Y"], + 'port': []} + mock_ldset = {} + mock_ldset['LX:10000090FAA0786X'] = mock_data + mock_configs = mock.Mock() + self.configs = mock_configs + self.configs.return_value = None, None, mock_ldset, None, None, None + ldset = self._validate_fcldset_exist(self.ldsets, connector) + self.assertEqual('LX:10000090FAA0786X', ldset['ldsetname']) + self.assertEqual('1000-0090-FAA0-786X', ldset['wwpn'][0]) + self.assertEqual('1000-0090-FAA0-786Y', ldset['wwpn'][1]) def test_enumerate_iscsi_portals(self): connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"} diff --git a/cinder/volume/drivers/nec/cli.py b/cinder/volume/drivers/nec/cli.py index 00810401644..6201c4ff4d2 100644 --- a/cinder/volume/drivers/nec/cli.py +++ b/cinder/volume/drivers/nec/cli.py @@ -31,7 +31,7 @@ from cinder import ssh_utils LOG = logging.getLogger(__name__) -retry_msgids = ['iSM31005', 'iSM31015', 'iSM42408', 'iSM42412'] +retry_msgids = ['iSM31005', 'iSM31015', 'iSM42408', 'iSM42412', 'iSM19411'] class MStorageISMCLI(object): @@ -227,6 +227,41 @@ class MStorageISMCLI(object): % {'ldn': ldn, 'capacity': capacity}) self._execute(cmd) + def addldset_fc(self, ldsetname, connector): + """Create new FC LD Set.""" + cmd = 'iSMcfg addldset -ldset LX:%s -type fc' % ldsetname + out, err, status = self._execute(cmd, [0], False) + if status != 0: + return False + for wwpn in connector['wwpns']: + length = len(wwpn) + setwwpn = '-'.join([wwpn[i:i + 4] + for i in range(0, length, 4)]) + setwwpn = setwwpn.upper() + cmd = ('iSMcfg addldsetpath -ldset LX:%(name)s -path %(path)s' + % {'name': ldsetname, 'path': setwwpn}) + out, err, status = self._execute(cmd, [0], False) + if status != 0: + return False + + return True + + def addldset_iscsi(self, ldsetname, connector): + """Create new iSCSI LD Set.""" + cmd = ('iSMcfg addldset -ldset LX:%s -multitarget on' + ' -type iscsi' % ldsetname) + out, err, status = self._execute(cmd, [0], False) + if status != 0: + return False + cmd = ('iSMcfg addldsetinitiator' + ' -ldset LX:%(name)s -initiatorname %(initiator)s' + % {'name': ldsetname, 'initiator': connector['initiator']}) + out, err, status = self._execute(cmd, [0], False) + if status != 0: + return False + + return True + def addldsetld(self, ldset, ldname, lun=None): """Add an LD to specified LD Set.""" if lun is None: @@ -611,6 +646,14 @@ class MStorageISMCLI(object): % {'lvname': lvname}) self._execute(cmd) + def cvbind(self, poolnumber, cvnumber): + """Create Control Volume.""" + cmd = ('iSMcfg ldbind -poolnumber %(poolnumber)d ' + '-ldattr cv -ldn %(cvnumber)d' + % {'poolnumber': poolnumber, + 'cvnumber': cvnumber}) + self._execute(cmd) + class UnpairWait(object): diff --git a/cinder/volume/drivers/nec/volume_common.py b/cinder/volume/drivers/nec/volume_common.py index 0723ae9e77b..fa9b20f73b9 100644 --- a/cinder/volume/drivers/nec/volume_common.py +++ b/cinder/volume/drivers/nec/volume_common.py @@ -96,6 +96,12 @@ mstorage_opts = [ cfg.IntOpt('nec_iscsi_portals_per_cont', default=1, help='Number of iSCSI portals.'), + cfg.BoolOpt('nec_auto_accesscontrol', + default=True, + help='Configure access control automatically.'), + cfg.StrOpt('nec_cv_ldname_format', + default='LX:__ControlVolume_%xh', + help='M-Series Storage Control Volume name format.'), ] FLAGS.register_opts(mstorage_opts, group=configuration.SHARED_CONF_GROUP) @@ -147,7 +153,7 @@ def convert_to_id(value62): class MStorageVolumeCommon(object): """M-Series Storage volume common class.""" - VERSION = '1.9.2' + VERSION = '1.10.1' WIKI_NAME = 'NEC_Cinder_CI' def do_setup(self, context): @@ -250,7 +256,9 @@ class MStorageVolumeCommon(object): confobj.safe_get('nec_ssh_pool_port_number'), 'diskarray_name': confobj.safe_get('nec_diskarray_name'), 'queryconfig_view': confobj.safe_get('nec_queryconfig_view'), - 'portal_number': confobj.safe_get('nec_iscsi_portals_per_cont') + 'portal_number': confobj.safe_get('nec_iscsi_portals_per_cont'), + 'auto_accesscontrol': confobj.safe_get('nec_auto_accesscontrol'), + 'cv_name_format': confobj.safe_get('nec_cv_ldname_format') } def _set_properties(self): diff --git a/cinder/volume/drivers/nec/volume_helper.py b/cinder/volume/drivers/nec/volume_helper.py index ac352b9a761..c9bab703fb1 100644 --- a/cinder/volume/drivers/nec/volume_helper.py +++ b/cinder/volume/drivers/nec/volume_helper.py @@ -217,8 +217,24 @@ class MStorageDriver(volume_common.MStorageVolumeCommon): ldset = tldset break if ldset is None: - msg = _('Appropriate Logical Disk Set could not be found.') - raise exception.NotFound(msg) + if self._properties['auto_accesscontrol']: + authname = connector['initiator'].strip() + authname = authname.replace((":"), "") + authname = authname.replace(("."), "") + new_ldsetname = authname[-16:] + ret = self._cli.addldset_iscsi(new_ldsetname, connector) + if ret is False: + msg = _('Appropriate Logical Disk Set' + ' could not be found.') + raise exception.NotFound(msg) + xml = self._cli.view_all(self._properties['ismview_path']) + pools, lds, ldsets, used_ldns, hostports, max_ld_count = ( + self.configs(xml)) + ldset = self._validate_iscsildset_exist(ldsets, connector) + else: + msg = _('Appropriate Logical Disk Set could not be found.') + raise exception.NotFound(msg) + if len(ldset['portal_list']) < 1: msg = (_('Logical Disk Set `%s` has no portal.') % ldset['ldsetname']) @@ -240,8 +256,21 @@ class MStorageDriver(volume_common.MStorageVolumeCommon): if ldset is not None: break if ldset is None: - msg = _('Appropriate Logical Disk Set could not be found.') - raise exception.NotFound(msg) + if self._properties['auto_accesscontrol']: + new_ldsetname = connector['wwpns'][0][:16] + ret = self._cli.addldset_fc(new_ldsetname, connector) + if ret is False: + msg = _('Appropriate Logical Disk Set' + ' could not be found.') + raise exception.NotFound(msg) + xml = self._cli.view_all(self._properties['ismview_path']) + pools, lds, ldsets, used_ldns, hostports, max_ld_count = ( + self.configs(xml)) + ldset = self._validate_fcldset_exist(ldsets, connector) + else: + msg = _('Appropriate Logical Disk Set could not be found.') + raise exception.NotFound(msg) + return ldset def _enumerate_iscsi_portals(self, hostports, ldset, prefered_director=0): @@ -819,6 +848,16 @@ class MStorageDriver(volume_common.MStorageVolumeCommon): lvldn = self._select_ldnumber(used_ldns, max_ld_count) LOG.debug('configure backend.') + if not ldset['lds']: + LOG.debug('create and attach control volume.') + used_ldns.append(lvldn) + cvldn = self._select_ldnumber(used_ldns, max_ld_count) + self._cli.cvbind(lds[bvname]['pool_num'], cvldn) + self._cli.changeldname(cvldn, + self._properties['cv_name_format'] % cvldn) + self._cli.addldsetld(ldset['ldsetname'], + self._properties['cv_name_format'] % cvldn) + self._cli.lvbind(bvname, lvname[3:], lvldn) self._cli.lvlink(svname[3:], lvname[3:]) self._cli.addldsetld(ldset['ldsetname'], lvname) @@ -882,6 +921,17 @@ class MStorageDriver(volume_common.MStorageVolumeCommon): lvldn = self._select_ldnumber(used_ldns, max_ld_count) LOG.debug('configure backend.') + lun0 = [ld for (ldn, ld) in ldset['lds'].items() if ld['lun'] == 0] + if not lun0: + LOG.debug('create and attach control volume.') + used_ldns.append(lvldn) + cvldn = self._select_ldnumber(used_ldns, max_ld_count) + self._cli.cvbind(lds[bvname]['pool_num'], cvldn) + self._cli.changeldname(cvldn, + self._properties['cv_name_format'] % cvldn) + self._cli.addldsetld(ldset['ldsetname'], + self._properties['cv_name_format'] % cvldn, 0) + self._cli.lvbind(bvname, lvname[3:], lvldn) self._cli.lvlink(svname[3:], lvname[3:]) @@ -889,6 +939,8 @@ class MStorageDriver(volume_common.MStorageVolumeCommon): ldsetlds = ldset['lds'] for ld in ldsetlds.values(): luns.append(ld['lun']) + if 0 not in luns: + luns.append(0) target_lun = 0 for lun in sorted(luns): if target_lun < lun: diff --git a/releasenotes/notes/nec-auto-accesscontrol-55f4b090e8128f5e.yaml b/releasenotes/notes/nec-auto-accesscontrol-55f4b090e8128f5e.yaml new file mode 100644 index 00000000000..040aaaf7183 --- /dev/null +++ b/releasenotes/notes/nec-auto-accesscontrol-55f4b090e8128f5e.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + Added automatic configuration of SAN access control for the NEC + volume driver.