HNAS_RESULT22 = "Failed to establish SSC connection"
+HNAS_RESULT23 = "\n\
+Alias : cinder-Gold\n\
+Globally unique name: iqn.2015-06.10.10.10.10:evstest1.cinder-gold\n\
+Comment :\n\
+Secret : None\n\
+Authentication : Enabled\n\
+Logical units : No logical units.\n\
+Access configuration :\n\
+\n\
+Alias : cinder-GoldIsh\n\
+Globally unique name: iqn.2015-06.10.10.10.10:evstest1.cinder-goldish\n\
+Comment :\n\
+Secret : None\n\
+Authentication : Enabled\n\
+Logical units : No logical units.\n\
+Access configuration :\n\
+\n\
+Alias : cinder-default\n\
+Globally unique name: iqn.2014-12.10.10.10.10:evstest1.cinder-default\n\
+Comment :\n\
+Secret : pxr6U37LZZJBoMc\n\
+Authentication : Disabled\n\
+Logical units : Logical units :\n\
+\n\
+ LUN Logical Unit\n\
+ ---- --------------------------------\n\
+ 0 volume-8ddd1a54-9daf-4fa5-842...\n\
+ 1 volume-99da7ae7-1e7f-4d57-8bf...\n\
+\n\
+Access configuration :\n\
+"
+
HNAS_CMDS = {
('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'evsfs', 'list'):
["%s" % HNAS_RESULT1, ""],
'/.cinder/test_clone.iscsi'):
["%s" % HNAS_RESULT16, ""],
('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'evsipaddr', '-e', '1'):
- ["%s" % HNAS_RESULT17, ""]
+ ["%s" % HNAS_RESULT17, ""],
+ ('ssh', '0.0.0.0', 'supervisor', 'supervisor',
+ 'console-context', '--evs', '1', 'iscsi-target', 'list'):
+ ["%s" % HNAS_RESULT23, ""],
+ ('ssh', '0.0.0.0', 'supervisor', 'supervisor', 'console-context', '--evs',
+ '1', 'iscsi-target', 'addlu', 'cinder-default',
+ 'volume-8ddd1a54-0000-0000-0000', '2'):
+ ["%s" % HNAS_RESULT13, ""]
}
DRV_CONF = {'ssh_enabled': 'True',
side_effect=m_run_cmd)
def test_add_iscsi_conn(self, m_cmd):
out = self.hnas_bend.add_iscsi_conn("ssh", "0.0.0.0", "supervisor",
- "supervisor", "test_lun",
+ "supervisor",
+ "volume-8ddd1a54-0000-0000-0000",
"test_hdp", "test_port",
- "test_iqn", "test_init")
+ "cinder-default", "test_init")
self.assertIn('successfully paired', out)
self.assertIn('already deleted', out)
- @mock.patch.object(hnas_backend.HnasBackend, '_get_evs', return_value=0)
+ @mock.patch.object(hnas_backend.HnasBackend, 'get_evs', return_value=0)
@mock.patch.object(hnas_backend.HnasBackend, 'run_cmd')
def test_get_targetiqn(self, m_cmd, m_get_evs):
"supervisor", "test_iqn",
"test_hdp")
self.assertEqual('', out)
+
+ @mock.patch.object(hnas_backend.HnasBackend, 'run_cmd')
+ def test_get_targets(self, m_run_cmd):
+ # Test normal behaviour
+ m_run_cmd.return_value = (HNAS_RESULT23, "")
+ tgt_list = self.hnas_bend._get_targets("ssh", "0.0.0.0", "supervisor",
+ "supervisor", 1)
+ self.assertEqual(3, len(tgt_list))
+ self.assertEqual(2, len(tgt_list[2]['luns']))
+
+ # Test calling with parameter
+ tgt_list = self.hnas_bend._get_targets("ssh", "0.0.0.0", "supervisor",
+ "supervisor", 1,
+ 'cinder-default')
+ self.assertEqual(1, len(tgt_list))
+ self.assertEqual(2, len(tgt_list[0]['luns']))
+
+ # Test error in BE command
+ m_run_cmd.side_effect = putils.ProcessExecutionError
+ tgt_list = self.hnas_bend._get_targets("ssh", "0.0.0.0", "supervisor",
+ "supervisor", 1)
+ self.assertEqual(0, len(tgt_list))
+
+ @mock.patch.object(hnas_backend.HnasBackend,
+ 'run_cmd', side_effect=m_run_cmd)
+ def test_check_targets(self, m_run_cmd):
+ result, tgt = self.hnas_bend.check_target("ssh", "0.0.0.0",
+ "supervisor",
+ "supervisor", "test_hdp",
+ "cinder-default")
+ self.assertTrue(result)
+ self.assertEqual('cinder-default', tgt['alias'])
+
+ result, tgt = self.hnas_bend.check_target("ssh", "0.0.0.0",
+ "supervisor",
+ "supervisor", "test_hdp",
+ "cinder-no-target")
+ self.assertFalse(result)
+ self.assertIsNone(tgt)
+
+ @mock.patch.object(hnas_backend.HnasBackend,
+ 'run_cmd', side_effect=m_run_cmd)
+ def test_check_lu(self, m_run_cmd):
+ ret = self.hnas_bend.check_lu("ssh", "0.0.0.0", "supervisor",
+ "supervisor",
+ "volume-8ddd1a54-9daf-4fa5-842",
+ "test_hdp")
+ result, lunid, tgt = ret
+ self.assertTrue(result)
+ self.assertEqual('0', lunid)
+
+ ret = self.hnas_bend.check_lu("ssh", "0.0.0.0", "supervisor",
+ "supervisor",
+ "volume-8ddd1a54-0000-0000-000",
+ "test_hdp")
+ result, lunid, tgt = ret
+ self.assertFalse(result)
# The following information is passed on to tests, when creating a volume
_VOLUME = {'name': 'testvol', 'volume_id': '1234567890', 'size': 128,
'volume_type': 'silver', 'volume_type_id': '1',
- 'provider_location': None, 'id': 'abcdefg',
+ 'provider_location': '83-68-96-AA-DA-5D.volume-2dfe280e-470a-4182'
+ '-afb8-1755025c35b8', 'id': 'abcdefg',
'host': 'host1@hnas-iscsi-backend#silver'}
self.out = """wGkJhTpXaaYJ5Rv"""
return self.out
+ def get_evs(self, cmd, ip0, user, pw, fsid):
+ return '1'
+
+ def check_lu(self, cmd, ip0, user, pw, volume_name, hdp):
+ return True, 1, {'alias': 'cinder-default', 'secret': 'mysecret',
+ 'iqn': 'iqn.1993-08.org.debian:01:11f90746eb2'}
+
+ def check_target(self, cmd, ip0, user, pw, hdp, target_alias):
+ return False, None
+
class HNASiSCSIDriverTest(test.TestCase):
"""Test HNAS iSCSI volume driver."""
def test_get_pool(self, m_ext_spec):
label = self.driver.get_pool(_VOLUME)
self.assertEqual('silver', label)
+
+ @mock.patch.object(time, 'sleep')
+ @mock.patch.object(iscsi.HDSISCSIDriver, '_update_vol_location')
+ def test_get_service_target(self, m_update_vol_location, m_sleep):
+
+ vol = _VOLUME.copy()
+ self.backend.check_lu = mock.MagicMock()
+ self.backend.check_target = mock.MagicMock()
+
+ # Test the case where volume is not already mapped - CHAP enabled
+ self.backend.check_lu.return_value = (False, 0, None)
+ self.backend.check_target.return_value = (False, None)
+ ret = self.driver._get_service_target(vol)
+ iscsi_ip, iscsi_port, ctl, svc_port, hdp, alias, secret = ret
+ self.assertEqual('evs1-tgt0', alias)
+
+ # Test the case where volume is not already mapped - CHAP disabled
+ self.driver.config['chap_enabled'] = 'False'
+ ret = self.driver._get_service_target(vol)
+ iscsi_ip, iscsi_port, ctl, svc_port, hdp, alias, secret = ret
+ self.assertEqual('evs1-tgt0', alias)
+
+ # Test the case where all targets are full
+ fake_tgt = {'alias': 'fake', 'luns': range(0, 32)}
+ self.backend.check_lu.return_value = (False, 0, None)
+ self.backend.check_target.return_value = (True, fake_tgt)
+ self.assertRaises(exception.NoMoreTargets,
+ self.driver._get_service_target, vol)
from oslo_utils import units
import six
-from cinder.i18n import _, _LW, _LI
+from cinder.i18n import _, _LW, _LI, _LE
from cinder import exception
from cinder import ssh_utils
from cinder import utils
def run_cmd(self, cmd, ip0, user, pw, *args, **kwargs):
"""Run a command on SMU or using SSH
- :param cmd: the command that will be run on SMU
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
- :returns: formated string with version information
+ :return: formated string with version information
"""
LOG.debug('Enable ssh: %s',
six.text_type(self.drv_configs['ssh_enabled']))
def get_version(self, cmd, ver, ip0, user, pw):
"""Gets version information from the storage unit
+ :param cmd: ssc command name
:param ver: string driver version
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
- :returns: formated string with version information
+ :return: formated string with version information
"""
if (self.drv_configs['ssh_enabled'] == 'True' and
self.drv_configs['cluster_admin_ip0'] is not None):
def get_iscsi_info(self, cmd, ip0, user, pw):
"""Gets IP addresses for EVSs, use EVSID as controller.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
- :returns: formated string with iSCSI information
+ :return: formated string with iSCSI information
"""
out, err = self.run_cmd(cmd, ip0, user, pw,
def get_hdp_info(self, cmd, ip0, user, pw, fslabel=None):
"""Gets the list of filesystems and fsids.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param fslabel: filesystem label we want to get info
- :returns: formated string with filesystems and fsids
+ :return: formated string with filesystems and fsids
"""
if fslabel is None:
{'out': newout, 'err': err})
return newout
- def _get_evs(self, cmd, ip0, user, pw, fsid):
- """Gets the EVSID for the named filesystem."""
+ def get_evs(self, cmd, ip0, user, pw, fsid):
+ """Gets the EVSID for the named filesystem.
+
+ :param cmd: ssc command name
+ :param ip0: string IP address of controller
+ :param user: string user authentication for array
+ :param pw: string password authentication for array
+ :return: EVS id of the file system
+ """
out, err = self.run_cmd(cmd, ip0, user, pw, "evsfs", "list",
check_exit_code=True)
- LOG.debug('get_evs: out %s', out)
+ LOG.debug('get_evs: out %s.', out)
lines = out.split('\n')
for line in lines:
{'out': out, 'fslabel': fslabel})
return 0
+ def _get_targets(self, cmd, ip0, user, pw, evsid, tgtalias=None):
+ """Get the target list of an EVS.
+
+ Get the target list of an EVS. Optionally can return the target
+ list of a specific target.
+ """
+
+ LOG.debug("Getting target list for evs %s, tgtalias: %s.",
+ evsid, tgtalias)
+
+ try:
+ out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
+ "--evs", evsid, 'iscsi-target', 'list',
+ check_exit_code=True)
+ except putils.ProcessExecutionError as e:
+ LOG.error(_LE('Error getting iSCSI target info '
+ 'from EVS %(evs)s.'), {'evs': evsid})
+ LOG.debug("_get_targets out: %(out)s, err: %(err)s.",
+ {'out': e.stdout, 'err': e.stderr})
+ return []
+
+ tgt_list = []
+ if 'No targets' in out:
+ LOG.debug("No targets found in EVS %(evsid)s.", {'evsid': evsid})
+ return tgt_list
+
+ tgt_raw_list = out.split('Alias')[1:]
+ for tgt_raw_info in tgt_raw_list:
+ tgt = {}
+ tgt['alias'] = tgt_raw_info.split('\n')[0].split(' ').pop()
+ tgt['iqn'] = tgt_raw_info.split('\n')[1].split(' ').pop()
+ tgt['secret'] = tgt_raw_info.split('\n')[3].split(' ').pop()
+ tgt['auth'] = tgt_raw_info.split('\n')[4].split(' ').pop()
+ luns = []
+ tgt_raw_info = tgt_raw_info.split('\n\n')[1]
+ tgt_raw_list = tgt_raw_info.split('\n')[2:]
+
+ for lun_raw_line in tgt_raw_list:
+ lun_raw_line = lun_raw_line.strip()
+ lun_raw_line = lun_raw_line.split(' ')
+ lun = {}
+ lun['id'] = lun_raw_line[0]
+ lun['name'] = lun_raw_line.pop()
+ luns.append(lun)
+
+ tgt['luns'] = luns
+
+ if tgtalias == tgt['alias']:
+ return [tgt]
+
+ tgt_list.append(tgt)
+
+ if tgtalias is not None:
+ # We tried to find 'tgtalias' but didn't find. Return a empty
+ # list.
+ LOG.debug("There's no target %(alias)s in EVS %(evsid)s.",
+ {'alias': tgtalias, 'evsid': evsid})
+ return []
+
+ LOG.debug("Targets in EVS %(evs)s: %(tgtl)s.",
+ {'evs': evsid, 'tgtl': tgt_list})
+ return tgt_list
+
+ def _get_unused_lunid(self, cmd, ip0, user, pw, tgt_info):
+
+ if len(tgt_info['luns']) == 0:
+ return 0
+
+ free_lun = 0
+ for lun in tgt_info['luns']:
+ if int(lun['id']) == free_lun:
+ free_lun += 1
+
+ if int(lun['id']) > free_lun:
+ # Found a free LUN number
+ break
+
+ return free_lun
+
def get_nfs_info(self, cmd, ip0, user, pw):
"""Gets information on each NFS export.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
fs = inf[3]
if 'Transfer setting' in line and fs != "":
fsid = self._get_fsid(cmd, ip0, user, pw, fs)
- evsid = self._get_evs(cmd, ip0, user, pw, fsid)
+ evsid = self.get_evs(cmd, ip0, user, pw, fsid)
ips = self._get_evsips(cmd, ip0, user, pw, evsid)
newout += "Export: %s Path: %s HDP: %s FSID: %s \
EVS: %s IPS: %s\n" \
If the operation can not be performed for some reason, utils.execute()
throws an error and aborts the operation. Used for iSCSI only
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
successfully created'
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'add', "-e",
out = "LUN %s HDP: %s size: %s MB, is successfully created" \
% (name, hdp, size)
- LOG.debug('create_lu: %s', out)
+ LOG.debug('create_lu: %s.', out)
return out
def delete_lu(self, cmd, ip0, user, pw, hdp, lun):
"""Delete an logical unit. Used for iSCSI only
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:returns: formated string 'Logical unit deleted successfully.'
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'del', '-d',
'-f', lun,
check_exit_code=True)
- LOG.debug('delete_lu: %(out)s -- %(err)s', {'out': out, 'err': err})
+ LOG.debug('delete_lu: %(out)s -- %(err)s.', {'out': out, 'err': err})
return out
def create_dup(self, cmd, ip0, user, pw, src_lun, hdp, size, name):
Clone primitive used to support all iSCSI snapshot/cloning functions.
Used for iSCSI only.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:returns: formated string
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'clone', '-e',
out = "LUN %s HDP: %s size: %s MB, is successfully created" \
% (name, hdp, size)
- LOG.debug('create_dup: %(out)s -- %(err)s', {'out': out, 'err': err})
+ LOG.debug('create_dup: %(out)s -- %(err)s.', {'out': out, 'err': err})
return out
def file_clone(self, cmd, ip0, user, pw, fslabel, src, name):
Clone primitive used to support all NFS snapshot/cloning functions.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
"""
_fsid = self._get_fsid(cmd, ip0, user, pw, fslabel)
- _evsid = self._get_evs(cmd, ip0, user, pw, _fsid)
+ _evsid = self.get_evs(cmd, ip0, user, pw, _fsid)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'file-clone-create', '-f', fslabel,
out = "LUN %s HDP: %s Clone: %s -> %s" % (name, _fsid, src, name)
- LOG.debug('file_clone: %(out)s -- %(err)s', {'out': out, 'err': err})
+ LOG.debug('file_clone: %(out)s -- %(err)s.', {'out': out, 'err': err})
return out
def extend_vol(self, cmd, ip0, user, pw, hdp, lun, new_size, name):
"""Extend a iSCSI volume.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param name: formated string
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'expand',
out = ("LUN: %s successfully extended to %s MB" % (name, new_size))
- LOG.debug('extend_vol: %s', out)
+ LOG.debug('extend_vol: %s.', out)
return out
@utils.retry(putils.ProcessExecutionError, retries=HNAS_SSC_RETRIES)
- def add_iscsi_conn(self, cmd, ip0, user, pw, lun, hdp,
- port, iqn, initiator):
+ def add_iscsi_conn(self, cmd, ip0, user, pw, lun_name, hdp,
+ port, tgtalias, initiator):
"""Setup the lun on on the specified target port
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
- :param lun: id of the logical unit being extended
+ :param lun_name: id of the logical unit being extended
:param hdp: data pool of the logical unit
:param port: iSCSI port
- :param iqn: iSCSI qualified name
+ :param tgtalias: iSCSI qualified name
:param initiator: initiator address
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
- out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
- "--evs", _evsid,
- 'iscsi-target', 'list', iqn,
- check_exit_code=True)
-
- # even though ssc uses the target alias, need to return the full iqn
- fulliqn = ""
- lines = out.split('\n')
- for line in lines:
- if 'Globally unique name' in line:
- fulliqn = line.split()[3]
-
- # find first free hlun
- hlun = 0
- for line in lines:
- if line.startswith(' '):
- lunline = line.split()[0]
- vol = line.split()[1]
- if lunline[0].isdigit():
- # see if already mounted
- if vol[:29] == lun[:29]:
- LOG.info(_LI('lun: %(lun)s already mounted %(lline)s'),
- {'lun': lun, 'lline': lunline})
- conn = (int(lunline), lun, initiator, hlun, fulliqn,
- hlun, hdp, port)
- out = "H-LUN: %d alreadymapped LUN: %s, iSCSI \
- Initiator: %s @ index: %d, and Target: %s \
- @ index %d is successfully paired @ CTL: \
- %s, Port: %s" % conn
- LOG.debug('add_iscsi_conn: returns %s', out)
- return out
-
- if int(lunline) == hlun:
- hlun += 1
- if int(lunline) > hlun:
- # found a hole
- break
+ LOG.debug('Adding %(lun)s to %(tgt)s returns %(tgt)s.',
+ {'lun': lun_name, 'tgt': tgtalias})
+ found, lunid, tgt = self.check_lu(cmd, ip0, user, pw, lun_name, hdp)
+ evsid = self.get_evs(cmd, ip0, user, pw, hdp)
+
+ if found:
+ conn = (int(lunid), lun_name, initiator, int(lunid), tgt['iqn'],
+ int(lunid), hdp, port)
+ out = ("H-LUN: %d mapped LUN: %s, iSCSI Initiator: %s "
+ "@ index: %d, and Target: %s @ index %d is "
+ "successfully paired @ CTL: %s, Port: %s.") % conn
+ else:
+ tgt = self._get_targets(cmd, ip0, user, pw, evsid, tgtalias)
+ lunid = self._get_unused_lunid(cmd, ip0, user, pw, tgt[0])
- out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
- "--evs", _evsid,
- 'iscsi-target', 'addlu',
- iqn, lun, six.text_type(hlun),
- check_exit_code=True)
+ out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
+ "--evs", evsid,
+ 'iscsi-target', 'addlu',
+ tgtalias, lun_name, six.text_type(lunid),
+ check_exit_code=True)
- conn = (int(hlun), lun, initiator, int(hlun), fulliqn, int(hlun),
- hdp, port)
- out = "H-LUN: %d mapped LUN: %s, iSCSI Initiator: %s \
- @ index: %d, and Target: %s @ index %d is \
- successfully paired @ CTL: %s, Port: %s" % conn
+ conn = (int(lunid), lun_name, initiator, int(lunid), tgt[0]['iqn'],
+ int(lunid), hdp, port)
+ out = ("H-LUN: %d mapped LUN: %s, iSCSI Initiator: %s "
+ "@ index: %d, and Target: %s @ index %d is "
+ "successfully paired @ CTL: %s, Port: %s.") % conn
- LOG.debug('add_iscsi_conn: returns %s', out)
+ LOG.debug('add_iscsi_conn: returns %s.', out)
return out
def del_iscsi_conn(self, cmd, ip0, user, pw, evsid, iqn, hlun):
"""Remove the lun on on the specified target port
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
if out != "":
# hlun wasn't found
- LOG.info(_LI('del_iscsi_conn: hlun not found %s'), out)
+ LOG.info(_LI('del_iscsi_conn: hlun not found %s.'), out)
return out
# remove the LU from the target
out = "H-LUN: %d successfully deleted from target %s" \
% (int(hlun), iqn)
- LOG.debug('del_iscsi_conn: %s', out)
+ LOG.debug('del_iscsi_conn: %s.', out)
return out
def get_targetiqn(self, cmd, ip0, user, pw, targetalias, hdp, secret):
"""Obtain the targets full iqn
- Return the target's full iqn rather than its alias.
-
+ Returns the target's full iqn rather than its alias.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:return: string with full IQN
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list', targetalias,
def set_targetsecret(self, cmd, ip0, user, pw, targetalias, hdp, secret):
"""Sets the chap secret for the specified target.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param secret: CHAP secret of the target
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list',
def get_targetsecret(self, cmd, ip0, user, pw, targetalias, hdp):
"""Returns the chap secret for the specified target.
+ :param cmd: ssc command name
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:return secret: CHAP secret of the target
"""
- _evsid = self._get_evs(cmd, ip0, user, pw, hdp)
+ _evsid = self.get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list', targetalias,
return secret
else:
return ""
+
+ def check_target(self, cmd, ip0, user, pw, hdp, target_alias):
+ """Checks if a given target exists and gets its info
+
+ :param cmd: ssc command name
+ :param ip0: string IP address of controller
+ :param user: string user authentication for array
+ :param pw: string password authentication for array
+ :param hdp: pool name used
+ :param target_alias: alias of the target
+ :return True if target exists
+ :return list with the target info
+ """
+
+ LOG.debug("Checking if target %(tgt)s exists.", {'tgt': target_alias})
+ evsid = self.get_evs(cmd, ip0, user, pw, hdp)
+ tgt_list = self._get_targets(cmd, ip0, user, pw, evsid)
+
+ for tgt in tgt_list:
+ if tgt['alias'] == target_alias:
+ attached_luns = len(tgt['luns'])
+ LOG.debug("Target %(tgt)s has %(lun)s volumes.",
+ {'tgt': target_alias, 'lun': attached_luns})
+ return True, tgt
+
+ LOG.debug("Target %(tgt)s does not exist.", {'tgt': target_alias})
+ return False, None
+
+ def check_lu(self, cmd, ip0, user, pw, volume_name, hdp):
+ """Checks if a given LUN is already mapped
+
+ :param cmd: ssc command name
+ :param ip0: string IP address of controller
+ :param user: string user authentication for array
+ :param pw: string password authentication for array
+ :param volume_name: number of the LUN
+ :param hdp: storage pool of the LUN
+ :return True if the lun is attached
+ :return the LUN id
+ :return Info related to the target
+ """
+
+ LOG.debug("Checking if vol %s (hdp: %s) is attached.",
+ volume_name, hdp)
+ evsid = self.get_evs(cmd, ip0, user, pw, hdp)
+ tgt_list = self._get_targets(cmd, ip0, user, pw, evsid)
+
+ for tgt in tgt_list:
+ if len(tgt['luns']) == 0:
+ continue
+
+ for lun in tgt['luns']:
+ lunid = lun['id']
+ lunname = lun['name']
+ if lunname[:29] == volume_name[:29]:
+ LOG.debug("LUN %(lun)s attached on %(lunid)s, "
+ "target: %(tgt)s.",
+ {'lun': volume_name, 'lunid': lunid, 'tgt': tgt})
+ return True, lunid, tgt
+
+ LOG.debug("LUN %(lun)s not attached.", {'lun': volume_name})
+ return False, 0, None
iSCSI Cinder Volume driver for Hitachi Unified Storage (HUS-HNAS) platform.
"""
import os
+import six
from xml.etree import ElementTree as ETree
from oslo_concurrency import processutils
from oslo_utils import excutils
from oslo_utils import units
+
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder import utils as cinder_utils
from cinder.volume import volume_types
-HDS_HNAS_ISCSI_VERSION = '3.1.0'
+HDS_HNAS_ISCSI_VERSION = '3.3.0'
LOG = logging.getLogger(__name__)
HNAS_DEFAULT_CONFIG = {'hnas_cmd': 'ssc',
'chap_enabled': 'True',
'ssh_port': '22'}
+MAX_HNAS_ISCSI_TARGETS = 32
def factory_bend(drv_configs):
"""HDS HNAS volume driver.
Version 1.0.0: Initial driver version
- Version 2.2.0: Added support to SSH authentication
+ Version 2.2.0: Added support to SSH authentication
+ Version 3.2.0: Added pool aware scheduling
+ Fixed concurrency errors
+ Version 3.3.0: Fixed iSCSI target limitation error
"""
def __init__(self, *args, **kwargs):
return conf
def _get_service(self, volume):
- """Get available service parameters.
+ """Get the available service parameters
- Get the available service parameters for a given volume using its type.
- :param volume: dictionary volume reference
+ Get the available service parametersfor a given volume using its
+ type.
+ :param volume: dictionary volume reference
+ :return HDP related to the service
"""
label = utils.extract_host(volume['host'], level='pool')
if label in self.config['services'].keys():
svc = self.config['services'][label]
- # HNAS - one time lookup
- # see if the client supports CHAP authentication and if
- # iscsi_secret has already been set, retrieve the secret if
- # available, otherwise generate and store
- if self.config['chap_enabled'] == 'True':
- # it may not exist, create and set secret
- if 'iscsi_secret' not in svc:
- LOG.info(_LI("Retrieving secret for service: %s"), label)
-
- out = self.bend.get_targetsecret(self.config['hnas_cmd'],
- self.config['mgmt_ip0'],
- self.config['username'],
- self.config['password'],
- 'cinder-' + label,
- svc['hdp'])
- svc['iscsi_secret'] = out
- if svc['iscsi_secret'] == "":
- svc['iscsi_secret'] = utils.generate_password()[0:15]
- self.bend.set_targetsecret(self.config['hnas_cmd'],
- self.config['mgmt_ip0'],
- self.config['username'],
- self.config['password'],
- 'cinder-' + label,
- svc['hdp'],
- svc['iscsi_secret'])
-
- LOG.info(_LI("Set tgt CHAP secret for service: %s"),
- label)
- else:
- # We set blank password when the client does not
- # support CHAP. Later on, if the client tries to create a new
- # target that does not exists in the backend, we check for this
- # value and use a temporary dummy password.
- if 'iscsi_secret' not in svc:
- # Warns in the first time
- LOG.info(_LI("CHAP authentication disabled"))
-
- svc['iscsi_secret'] = ""
-
- if 'iscsi_target' not in svc:
- LOG.info(_LI("Retrieving target for service: %s"), label)
-
- out = self.bend.get_targetiqn(self.config['hnas_cmd'],
- self.config['mgmt_ip0'],
- self.config['username'],
- self.config['password'],
- 'cinder-' + label,
- svc['hdp'],
- svc['iscsi_secret'])
- svc['iscsi_target'] = out
-
- self.config['services'][label] = svc
-
- service = (svc['iscsi_ip'], svc['iscsi_port'], svc['ctl'],
- svc['port'], svc['hdp'], svc['iscsi_target'],
- svc['iscsi_secret'])
+ return svc['hdp']
else:
- LOG.info(_LI("Available services: %s"),
+ LOG.info(_LI("Available services: %s."),
self.config['services'].keys())
- LOG.error(_LE("No configuration found for service: %s"), label)
+ LOG.error(_LE("No configuration found for service: %s."), label)
raise exception.ParameterNotFound(param=label)
+ def _get_service_target(self, volume):
+ """Get the available service parameters
+
+ Get the available service parameters for a given volume using
+ its type.
+ :param volume: dictionary volume reference
+ """
+
+ hdp = self._get_service(volume)
+ info = _loc_info(volume['provider_location'])
+ (arid, lun_name) = info['id_lu']
+
+ evsid = self.bend.get_evs(self.config['hnas_cmd'],
+ self.config['mgmt_ip0'],
+ self.config['username'],
+ self.config['password'],
+ hdp)
+ svc_label = utils.extract_host(volume['host'], level='pool')
+ svc = self.config['services'][svc_label]
+
+ LOG.info(_LI("_get_service_target hdp: %s."), hdp)
+ LOG.info(_LI("config[services]: %s."), self.config['services'])
+
+ mapped, lunid, tgt = self.bend.check_lu(self.config['hnas_cmd'],
+ self.config['mgmt_ip0'],
+ self.config['username'],
+ self.config['password'],
+ lun_name, hdp)
+
+ LOG.info(_LI("Target is %(map)s! Targetlist = %(tgtl)s."),
+ {'map': "mapped" if mapped else "not mapped", 'tgtl': tgt})
+
+ # The volume is already mapped to a LUN, so no need to create any
+ # targets
+ if mapped:
+ service = (svc['iscsi_ip'], svc['iscsi_port'], svc['ctl'],
+ svc['port'], hdp, tgt['alias'], tgt['secret'])
+ return service
+
+ # Each EVS can have up to 32 targets. Each target can have up to 32
+ # LUNs attached and have the name format 'evs<id>-tgt<0-N>'. We run
+ # from the first 'evs1-tgt0' until we find a target that is not already
+ # created in the BE or is created but have slots to place new targets.
+ found_tgt = False
+ for i in range(0, MAX_HNAS_ISCSI_TARGETS):
+ tgt_alias = 'evs' + evsid + '-tgt' + six.text_type(i)
+ # TODO(erlon): we need to go to the BE 32 times here
+ tgt_exist, tgt = self.bend.check_target(self.config['hnas_cmd'],
+ self.config['mgmt_ip0'],
+ self.config['username'],
+ self.config['password'],
+ hdp, tgt_alias)
+ if tgt_exist and len(tgt['luns']) < 32 or not tgt_exist:
+ # Target exists and has free space or, target does not exist
+ # yet. Proceed and use the target or create a target using this
+ # name.
+ found_tgt = True
+ break
+
+ # If we've got here and found_tgt is not True, we run out of targets,
+ # raise and go away.
+ if not found_tgt:
+ LOG.error(_LE("No more targets avaliable."))
+ raise exception.NoMoreTargets(param=tgt_alias)
+
+ LOG.info(_LI("Using target label: %s."), tgt_alias)
+
+ # Check if we have a secret stored for this target so we don't have to
+ # go to BE on every query
+ if 'targets' not in self.config.keys():
+ self.config['targets'] = {}
+
+ if tgt_alias not in self.config['targets'].keys():
+ self.config['targets'][tgt_alias] = {}
+
+ tgt_info = self.config['targets'][tgt_alias]
+
+ # HNAS - one time lookup
+ # see if the client supports CHAP authentication and if
+ # iscsi_secret has already been set, retrieve the secret if
+ # available, otherwise generate and store
+ if self.config['chap_enabled'] == 'True':
+ # It may not exist, create and set secret.
+ if 'iscsi_secret' not in tgt_info.keys():
+ LOG.info(_LI("Retrieving secret for service: %s."),
+ tgt_alias)
+
+ out = self.bend.get_targetsecret(self.config['hnas_cmd'],
+ self.config['mgmt_ip0'],
+ self.config['username'],
+ self.config['password'],
+ tgt_alias, hdp)
+ tgt_info['iscsi_secret'] = out
+ if tgt_info['iscsi_secret'] == "":
+ randon_secret = utils.generate_password()[0:15]
+ tgt_info['iscsi_secret'] = randon_secret
+ self.bend.set_targetsecret(self.config['hnas_cmd'],
+ self.config['mgmt_ip0'],
+ self.config['username'],
+ self.config['password'],
+ tgt_alias, hdp,
+ tgt_info['iscsi_secret'])
+
+ LOG.info(_LI("Set tgt CHAP secret for service: %s."),
+ tgt_alias)
+ else:
+ # We set blank password when the client does not
+ # support CHAP. Later on, if the client tries to create a new
+ # target that does not exists in the backend, we check for this
+ # value and use a temporary dummy password.
+ if 'iscsi_secret' not in tgt_info.keys():
+ # Warns in the first time
+ LOG.info(_LI("CHAP authentication disabled."))
+
+ tgt_info['iscsi_secret'] = ""
+
+ if 'tgt_iqn' not in tgt_info:
+ LOG.info(_LI("Retrieving target for service: %s."), tgt_alias)
+
+ out = self.bend.get_targetiqn(self.config['hnas_cmd'],
+ self.config['mgmt_ip0'],
+ self.config['username'],
+ self.config['password'],
+ tgt_alias, hdp,
+ tgt_info['iscsi_secret'])
+ tgt_info['tgt_iqn'] = out
+
+ self.config['targets'][tgt_alias] = tgt_info
+
+ service = (svc['iscsi_ip'], svc['iscsi_port'], svc['ctl'],
+ svc['port'], hdp, tgt_alias, tgt_info['iscsi_secret'])
+
return service
def _get_stats(self):
self.config['password'],
pool['hdp'])
- LOG.debug('Query for pool %(pool)s: %(out)s',
+ LOG.debug('Query for pool %(pool)s: %(out)s.',
{'pool': pool['pool_name'], 'out': out})
(hdp, size, _ign, used) = out.split()[1:5] # in MB
hnas_stat['pools'] = self.pools
- LOG.info(_LI("stats: stats: %s"), hnas_stat)
+ LOG.info(_LI("stats: stats: %s."), hnas_stat)
return hnas_stat
def _get_hdp_list(self):
:param volume: dictionary volume reference
"""
- service = self._get_service(volume)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(volume)
out = self.bend.create_lu(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
if src['size'] != dst['size']:
msg = 'clone volume size mismatch'
raise exception.VolumeBackendAPIException(data=msg)
- service = self._get_service(dst)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(dst)
size = int(src['size']) * units.Ki
source_vol = self._id_to_vol(src['id'])
(arid, slun) = _loc_info(source_vol['provider_location'])['id_lu']
:param new_size: int size in GB to extend
"""
- service = self._get_service(volume)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(volume)
(arid, lun) = _loc_info(volume['provider_location'])['id_lu']
self.bend.extend_vol(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
LOG.debug("delete lun %(lun)s on %(name)s", {'lun': lun, 'name': name})
- service = self._get_service(volume)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(volume)
self.bend.delete_lu(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
{'vol': volume, 'conn': connector})
# connector[ip, host, wwnns, unititator, wwp/
- service = self._get_service(volume)
- (ip, ipp, ctl, port, _hdp, target, secret) = service
+
+ service_info = self._get_service_target(volume)
+ (ip, ipp, ctl, port, _hdp, tgtalias, secret) = service_info
info = _loc_info(volume['provider_location'])
if 'tgt' in info.keys(): # spurious repeat connection
# print info.keys()
LOG.debug("initiate_conn: tgt already set %s", info['tgt'])
- (arid, lun) = info['id_lu']
- loc = arid + '.' + lun
+ (arid, lun_name) = info['id_lu']
+ loc = arid + '.' + lun_name
# sps, use target if provided
- iqn = target
-
try:
out = self.bend.add_iscsi_conn(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
- lun, _hdp, port, iqn,
+ lun_name, _hdp, port, tgtalias,
connector['initiator'])
except processutils.ProcessExecutionError:
msg = _("Error attaching volume %s. "
# sps need hlun, fulliqn
hlun = out.split()[1]
fulliqn = out.split()[13]
- tgt = hnas_portal + ',' + iqn + ',' + loc + ',' + ctl + ','
+ tgt = hnas_portal + ',' + tgtalias + ',' + loc + ',' + ctl + ','
tgt += port + ',' + hlun
LOG.info(_LI("initiate: connection %s"), tgt)
properties['auth_method'] = 'CHAP'
properties['auth_password'] = secret
- return {'driver_volume_type': 'iscsi', 'data': properties}
+ conn_info = {'driver_volume_type': 'iscsi', 'data': properties}
+ LOG.debug("initialize_connection: conn_info: %s.", conn_info)
+ return conn_info
@cinder_utils.synchronized('volume_mapping')
def terminate_connection(self, volume, connector, **kwargs):
LOG.warning(_LW("terminate_conn: provider location empty."))
return
(arid, lun) = info['id_lu']
- (_portal, iqn, loc, ctl, port, hlun) = info['tgt']
+ (_portal, tgtalias, loc, ctl, port, hlun) = info['tgt']
LOG.info(_LI("terminate: connection %s"), volume['provider_location'])
self.bend.del_iscsi_conn(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
self.config['password'],
- ctl, iqn, hlun)
+ ctl, tgtalias, hlun)
self._update_vol_location(volume['id'], loc)
return {'provider_location': loc}
size = int(snapshot['volume_size']) * units.Ki
(arid, slun) = _loc_info(snapshot['provider_location'])['id_lu']
- service = self._get_service(volume)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(volume)
out = self.bend.create_dup(self.config['hnas_cmd'],
self.config['mgmt_ip0'],
self.config['username'],
"""
source_vol = self._id_to_vol(snapshot['volume_id'])
- service = self._get_service(source_vol)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(source_vol)
size = int(snapshot['volume_size']) * units.Ki
(arid, slun) = _loc_info(source_vol['provider_location'])['id_lu']
out = self.bend.create_dup(self.config['hnas_cmd'],
(arid, lun) = loc.split('.')
source_vol = self._id_to_vol(snapshot['volume_id'])
- service = self._get_service(source_vol)
- (_ip, _ipp, _ctl, _port, hdp, target, secret) = service
+ hdp = self._get_service(source_vol)
myid = self.arid
if arid != myid: