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
This commit is contained in:
Shunei Shiono 2017-12-20 18:21:06 +09:00
parent 3b86eb7a82
commit d4dd162bcd
5 changed files with 152 additions and 16 deletions

View File

@ -1048,25 +1048,54 @@ class NonDisruptiveBackup_test(volume_helper.MStorageDSVDriver,
self._validate_ld_exist( self._validate_ld_exist(
self.lds, self.vol.id, self._properties['ld_name_format']) 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): def test_validate_iscsildset_exist(self):
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"} connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}
ldset = self._validate_iscsildset_exist(self.ldsets, connector) ldset = self._validate_iscsildset_exist(self.ldsets, connector)
self.assertEqual('LX:OpenStack0', ldset['ldsetname']) self.assertEqual('LX:OpenStack0', ldset['ldsetname'])
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255XX"} connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f232XX"}
with self.assertRaisesRegexp(exception.NotFound, mock_data = {'ldsetname': 'LX:redhatd1d8e8f23',
'Appropriate Logical Disk Set' 'protocol': 'iSCSI',
' could not be found.'): 'portal_list': ['1.1.1.1:3260', '2.2.2.2:3260'],
self._validate_iscsildset_exist(self.ldsets, connector) '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): def test_validate_fcldset_exist(self):
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]} connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
ldset = self._validate_fcldset_exist(self.ldsets, connector) ldset = self._validate_fcldset_exist(self.ldsets, connector)
self.assertEqual('LX:OpenStack1', ldset['ldsetname']) self.assertEqual('LX:OpenStack1', ldset['ldsetname'])
connector = {'wwpns': ["10000090FAA0786X", "10000090FAA0786Y"]} connector = {'wwpns': ["10000090FAA0786X", "10000090FAA0786Y"]}
with self.assertRaisesRegexp(exception.NotFound, mock_data = {'ldsetname': 'LX:10000090FAA0786X',
'Appropriate Logical Disk Set' 'lds': {},
' could not be found.'): 'protocol': 'FC',
self._validate_fcldset_exist(self.ldsets, connector) '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): def test_enumerate_iscsi_portals(self):
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"} connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}

View File

@ -31,7 +31,7 @@ from cinder import ssh_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
retry_msgids = ['iSM31005', 'iSM31015', 'iSM42408', 'iSM42412'] retry_msgids = ['iSM31005', 'iSM31015', 'iSM42408', 'iSM42412', 'iSM19411']
class MStorageISMCLI(object): class MStorageISMCLI(object):
@ -227,6 +227,41 @@ class MStorageISMCLI(object):
% {'ldn': ldn, 'capacity': capacity}) % {'ldn': ldn, 'capacity': capacity})
self._execute(cmd) 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): def addldsetld(self, ldset, ldname, lun=None):
"""Add an LD to specified LD Set.""" """Add an LD to specified LD Set."""
if lun is None: if lun is None:
@ -611,6 +646,14 @@ class MStorageISMCLI(object):
% {'lvname': lvname}) % {'lvname': lvname})
self._execute(cmd) 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): class UnpairWait(object):

View File

@ -96,6 +96,12 @@ mstorage_opts = [
cfg.IntOpt('nec_iscsi_portals_per_cont', cfg.IntOpt('nec_iscsi_portals_per_cont',
default=1, default=1,
help='Number of iSCSI portals.'), 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) FLAGS.register_opts(mstorage_opts, group=configuration.SHARED_CONF_GROUP)
@ -147,7 +153,7 @@ def convert_to_id(value62):
class MStorageVolumeCommon(object): class MStorageVolumeCommon(object):
"""M-Series Storage volume common class.""" """M-Series Storage volume common class."""
VERSION = '1.9.2' VERSION = '1.10.1'
WIKI_NAME = 'NEC_Cinder_CI' WIKI_NAME = 'NEC_Cinder_CI'
def do_setup(self, context): def do_setup(self, context):
@ -250,7 +256,9 @@ class MStorageVolumeCommon(object):
confobj.safe_get('nec_ssh_pool_port_number'), confobj.safe_get('nec_ssh_pool_port_number'),
'diskarray_name': confobj.safe_get('nec_diskarray_name'), 'diskarray_name': confobj.safe_get('nec_diskarray_name'),
'queryconfig_view': confobj.safe_get('nec_queryconfig_view'), '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): def _set_properties(self):

View File

@ -217,8 +217,24 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
ldset = tldset ldset = tldset
break break
if ldset is None: if ldset is None:
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.') msg = _('Appropriate Logical Disk Set could not be found.')
raise exception.NotFound(msg) raise exception.NotFound(msg)
if len(ldset['portal_list']) < 1: if len(ldset['portal_list']) < 1:
msg = (_('Logical Disk Set `%s` has no portal.') % msg = (_('Logical Disk Set `%s` has no portal.') %
ldset['ldsetname']) ldset['ldsetname'])
@ -240,8 +256,21 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
if ldset is not None: if ldset is not None:
break break
if ldset is None: if ldset is None:
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.') msg = _('Appropriate Logical Disk Set could not be found.')
raise exception.NotFound(msg) raise exception.NotFound(msg)
return ldset return ldset
def _enumerate_iscsi_portals(self, hostports, ldset, prefered_director=0): 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) lvldn = self._select_ldnumber(used_ldns, max_ld_count)
LOG.debug('configure backend.') 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.lvbind(bvname, lvname[3:], lvldn)
self._cli.lvlink(svname[3:], lvname[3:]) self._cli.lvlink(svname[3:], lvname[3:])
self._cli.addldsetld(ldset['ldsetname'], lvname) self._cli.addldsetld(ldset['ldsetname'], lvname)
@ -882,6 +921,17 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
lvldn = self._select_ldnumber(used_ldns, max_ld_count) lvldn = self._select_ldnumber(used_ldns, max_ld_count)
LOG.debug('configure backend.') 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.lvbind(bvname, lvname[3:], lvldn)
self._cli.lvlink(svname[3:], lvname[3:]) self._cli.lvlink(svname[3:], lvname[3:])
@ -889,6 +939,8 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
ldsetlds = ldset['lds'] ldsetlds = ldset['lds']
for ld in ldsetlds.values(): for ld in ldsetlds.values():
luns.append(ld['lun']) luns.append(ld['lun'])
if 0 not in luns:
luns.append(0)
target_lun = 0 target_lun = 0
for lun in sorted(luns): for lun in sorted(luns):
if target_lun < lun: if target_lun < lun:

View File

@ -0,0 +1,4 @@
---
upgrade:
Added automatic configuration of SAN access control for the NEC
volume driver.