Add minimum features in HDS driver (for Havana & Icehouse)
* Add create_cloned_volume() api. * Add extend_volume() api. * Reorganized some connection state keeping into _loc_info() function. Earlier this logic was spread out in various calls. * New self tests for #1 and #2 above. Change-Id: I6a88164ee0a427519c7fab6f6a852d46ec46176c Fixes: bug #1207560
This commit is contained in:
@@ -72,6 +72,7 @@ class SimulatedHusBackend:
|
||||
init_index = 0 # initiator index
|
||||
target_index = 0 # target index
|
||||
hlun = 0 # hlun index
|
||||
out = ''
|
||||
|
||||
def __init__(self):
|
||||
self.start_lun = 0
|
||||
@@ -103,6 +104,11 @@ class SimulatedHusBackend:
|
||||
self.start_lun += 1
|
||||
return out
|
||||
|
||||
def extend_vol(self, cmd, ver, ip0, ip1, user, pw, id, lu, size):
|
||||
out = ("LUN: %s successfully extended to %s MB" % (lu, size))
|
||||
SimulatedHusBackend.out = out
|
||||
return out
|
||||
|
||||
def delete_lu(self, cmd, ver, ip0, ip1, user, pw, id, lun):
|
||||
out = ""
|
||||
if lun in self.alloc_lun:
|
||||
@@ -132,7 +138,7 @@ class SimulatedHusBackend:
|
||||
return out
|
||||
|
||||
def del_iscsi_conn(self, cmd, ver, ip0, ip1, user, pw, id, lun, ctl, port,
|
||||
iqn, initiator, force):
|
||||
iqn, initiator):
|
||||
conn = ()
|
||||
for connection in SimulatedHusBackend.connections:
|
||||
if (connection[1] == lun):
|
||||
@@ -165,6 +171,7 @@ class HUSiSCSIDriverTest(test.TestCase):
|
||||
os.close(handle)
|
||||
SimulatedHusBackend.alloc_lun = []
|
||||
SimulatedHusBackend.connections = []
|
||||
SimulatedHusBackend.out = ''
|
||||
self.mox = mox.Mox()
|
||||
self.mox.StubOutWithMock(hds, 'factory_bend')
|
||||
hds.factory_bend().AndReturn(SimulatedHusBackend())
|
||||
@@ -209,6 +216,13 @@ class HUSiSCSIDriverTest(test.TestCase):
|
||||
num_luns_after = len(SimulatedHusBackend.alloc_lun)
|
||||
self.assertTrue(num_luns_before > num_luns_after)
|
||||
|
||||
def test_extend_volume(self):
|
||||
vol = self.test_create_volume()
|
||||
new_size = _VOLUME['size'] * 2
|
||||
self.driver.extend_volume(vol, new_size)
|
||||
self.assertTrue(str(new_size * 1024) in
|
||||
SimulatedHusBackend.out)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
vol = self.test_create_volume()
|
||||
self.mox.StubOutWithMock(self.driver, '_id_to_vol')
|
||||
@@ -221,6 +235,18 @@ class HUSiSCSIDriverTest(test.TestCase):
|
||||
svol['provider_location'] = loc['provider_location']
|
||||
return svol
|
||||
|
||||
def test_create_clone(self):
|
||||
vol = self.test_create_volume()
|
||||
self.mox.StubOutWithMock(self.driver, '_id_to_vol')
|
||||
self.driver._id_to_vol(vol['volume_id']).AndReturn(vol)
|
||||
self.mox.ReplayAll()
|
||||
svol = vol.copy()
|
||||
svol['volume_size'] = svol['size']
|
||||
loc = self.driver.create_snapshot(svol)
|
||||
self.assertNotEqual(loc, None)
|
||||
svol['provider_location'] = loc['provider_location']
|
||||
return svol
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
"""Delete a snapshot (test).
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from cinder import utils
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.hds.hus_backend import HusBackend
|
||||
|
||||
HDS_VERSION = '1.0.1'
|
||||
HDS_VERSION = '1.0.2'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@@ -53,6 +53,18 @@ def factory_bend():
|
||||
return HusBackend()
|
||||
|
||||
|
||||
def _loc_info(loc):
|
||||
"""Parse info from location string."""
|
||||
info = {}
|
||||
tup = loc.split(',')
|
||||
if len(tup) < 5:
|
||||
info['id_lu'] = tup[0].split('.')
|
||||
return info
|
||||
info['id_lu'] = tup[2].split('.')
|
||||
info['tgt'] = tup
|
||||
return info
|
||||
|
||||
|
||||
def _do_lu_range_check(start, end, maxlun):
|
||||
"""Validate array allocation range."""
|
||||
LOG.debug(_("Range: start LU: %(start)s, end LU: %(end)s")
|
||||
@@ -315,20 +327,68 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
'sz': sz})
|
||||
return {'provider_location': lun}
|
||||
|
||||
@utils.synchronized('hds_hus', external=True)
|
||||
def create_cloned_volume(self, dst, src):
|
||||
"""Create a clone of a volume."""
|
||||
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) = service
|
||||
size = int(src['size']) * 1024
|
||||
source_vol = self._id_to_vol(src['id'])
|
||||
(arid, slun) = _loc_info(source_vol['provider_location'])['id_lu']
|
||||
out = self.bend.create_dup(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
self.config['mgmt_ip0'],
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
arid, slun,
|
||||
hdp,
|
||||
self.start, self.end,
|
||||
'%s' % (size))
|
||||
lun = self.arid + '.' + out.split()[1]
|
||||
size = int(out.split()[5])
|
||||
LOG.debug(_("LUN %(lun)s of size %(size)s MB is cloned.")
|
||||
% {'lun': lun,
|
||||
'size': size})
|
||||
return {'provider_location': lun}
|
||||
|
||||
@utils.synchronized('hds_hus', external=True)
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume."""
|
||||
(arid, lun) = _loc_info(volume['provider_location'])['id_lu']
|
||||
out = self.bend.extend_vol(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
self.config['mgmt_ip0'],
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
arid, lun,
|
||||
'%s' % (new_size * 1024))
|
||||
LOG.debug(_("LUN %(lun)s extended to %(size)s GB.")
|
||||
% {'lun': lun,
|
||||
'size': new_size})
|
||||
|
||||
@utils.synchronized('hds_hus', external=True)
|
||||
def delete_volume(self, volume):
|
||||
"""Delete an LU on HUS."""
|
||||
loc = volume['provider_location']
|
||||
if loc is None: # to take care of spurious input
|
||||
return # which could cause exception.
|
||||
(arid, lun) = loc.split('.')
|
||||
myid = self.arid
|
||||
if arid != myid:
|
||||
LOG.error(_("Array Mismatch %(myid)s vs %(arid)s")
|
||||
% {'myid': myid,
|
||||
'arid': arid})
|
||||
msg = 'Array id mismatch in volume delete'
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
prov_loc = volume['provider_location']
|
||||
if prov_loc is None:
|
||||
return
|
||||
info = _loc_info(prov_loc)
|
||||
(arid, lun) = info['id_lu']
|
||||
if 'tgt' in info.keys(): # connected?
|
||||
(_portal, iqn, loc, ctl, port) = info['tgt']
|
||||
_out = self.bend.del_iscsi_conn(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
self.config['mgmt_ip0'],
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
arid, lun, ctl, port, iqn,
|
||||
'')
|
||||
name = self.hus_name
|
||||
LOG.debug(_("delete lun %(lun)s on %(name)s")
|
||||
% {'lun': lun,
|
||||
@@ -339,7 +399,7 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
self.arid, lun)
|
||||
arid, lun)
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Disconnect a volume from an attached instance."""
|
||||
@@ -350,8 +410,11 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
"""Map the created volume to connector['initiator']."""
|
||||
service = self._get_service(volume)
|
||||
(ip, ipp, ctl, port, _hdp) = service
|
||||
loc = volume['provider_location']
|
||||
(_array_id, lun) = loc.split('.')
|
||||
info = _loc_info(volume['provider_location'])
|
||||
if 'tgt' in info.keys(): # spurious repeat connection
|
||||
return
|
||||
(arid, lun) = info['id_lu']
|
||||
loc = arid + '.' + lun
|
||||
iqn = HI_IQN + connector['host']
|
||||
out = self.bend.add_iscsi_conn(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
@@ -359,7 +422,7 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
self.arid, lun, ctl, port, iqn,
|
||||
arid, lun, ctl, port, iqn,
|
||||
connector['initiator'])
|
||||
hus_portal = ip + ':' + ipp
|
||||
tgt = hus_portal + ',' + iqn + ',' + loc + ',' + ctl + ',' + port
|
||||
@@ -377,19 +440,20 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
@utils.synchronized('hds_hus', external=True)
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Terminate a connection to a volume."""
|
||||
info = volume['provider_location'].split(',')
|
||||
if len(info) < 5: # connection not setup properly. bail out
|
||||
info = _loc_info(volume['provider_location'])
|
||||
if 'tgt' not in info.keys(): # spurious disconnection
|
||||
return
|
||||
(_portal, iqn, loc, ctl, port) = info
|
||||
(_array_id, lun) = loc.split('.')
|
||||
(arid, lun) = info['id_lu']
|
||||
(_portal, iqn, loc, ctl, port) = info['tgt']
|
||||
|
||||
_out = self.bend.del_iscsi_conn(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
self.config['mgmt_ip0'],
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
self.arid, lun, ctl, port, iqn,
|
||||
connector['initiator'], 1)
|
||||
arid, lun, ctl, port, iqn,
|
||||
connector['initiator'])
|
||||
self._update_vol_location(volume['id'], loc)
|
||||
return {'provider_location': loc}
|
||||
|
||||
@@ -397,7 +461,7 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Create a volume from a snapshot."""
|
||||
size = int(snapshot['volume_size']) * 1024
|
||||
(_arid, slun) = snapshot['provider_location'].split('.')
|
||||
(arid, slun) = _loc_info(snapshot['provider_location'])['id_lu']
|
||||
service = self._get_service(volume)
|
||||
(_ip, _ipp, _ctl, _port, hdp) = service
|
||||
out = self.bend.create_dup(self.config['hus_cmd'],
|
||||
@@ -406,7 +470,7 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
self.arid, slun, hdp,
|
||||
arid, slun, hdp,
|
||||
self.start, self.end,
|
||||
'%s' % (size))
|
||||
lun = self.arid + '.' + out.split()[1]
|
||||
@@ -421,20 +485,20 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
"""Create a snapshot."""
|
||||
source_vol = self._id_to_vol(snapshot['volume_id'])
|
||||
size = int(snapshot['volume_size']) * 1024
|
||||
(_arid, slun) = source_vol['provider_location'].split('.')
|
||||
(arid, slun) = _loc_info(source_vol['provider_location'])['id_lu']
|
||||
out = self.bend.create_dup(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
self.config['mgmt_ip0'],
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
self.arid, slun,
|
||||
arid, slun,
|
||||
self.config['snapshot_hdp'],
|
||||
self.start, self.end,
|
||||
'%s' % (size))
|
||||
lun = self.arid + '.' + out.split()[1]
|
||||
size = int(out.split()[5])
|
||||
LOG.debug(_("LUN %(lun)s of size %(size)s MB is created.")
|
||||
LOG.debug(_("LUN %(lun)s of size %(size)s MB is created as snapshot.")
|
||||
% {'lun': lun,
|
||||
'size': size})
|
||||
return {'provider_location': lun}
|
||||
@@ -446,20 +510,13 @@ class HUSDriver(driver.ISCSIDriver):
|
||||
if loc is None: # to take care of spurious input
|
||||
return # which could cause exception.
|
||||
(arid, lun) = loc.split('.')
|
||||
myid = self.arid
|
||||
if arid != myid:
|
||||
LOG.error(_('Array mismatch %(myid)s vs %(arid)s')
|
||||
% {'myid': myid,
|
||||
'arid': arid})
|
||||
msg = 'Array id mismatch in delete snapshot'
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
_out = self.bend.delete_lu(self.config['hus_cmd'],
|
||||
HDS_VERSION,
|
||||
self.config['mgmt_ip0'],
|
||||
self.config['mgmt_ip1'],
|
||||
self.config['username'],
|
||||
self.config['password'],
|
||||
self.arid, lun)
|
||||
arid, lun)
|
||||
LOG.debug(_("LUN %s is deleted.") % lun)
|
||||
return
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ class HusBackend:
|
||||
'--delete-lun', '1',
|
||||
'--array-id', id,
|
||||
'--lun', lun,
|
||||
'--force', 1,
|
||||
check_exit_code=True)
|
||||
LOG.debug('delete_lu: ' + out + ' -- ' + err)
|
||||
return out
|
||||
@@ -115,6 +116,21 @@ class HusBackend:
|
||||
LOG.debug('create_dup: ' + out + ' -- ' + err)
|
||||
return out
|
||||
|
||||
def extend_vol(self, cmd, ver, ip0, ip1, user, pw, id, lun, new_size):
|
||||
out, err = utils.execute(cmd,
|
||||
'--driver-version', ver,
|
||||
'--ip0', ip0,
|
||||
'--ip1', ip1,
|
||||
'--user', user,
|
||||
'--password', pw,
|
||||
'--extend-lun', '1',
|
||||
'--array-id', id,
|
||||
'--lun', lun,
|
||||
'--size', new_size,
|
||||
check_exit_code=True)
|
||||
LOG.debug('extend_vol: ' + out + ' -- ' + err)
|
||||
return out
|
||||
|
||||
def add_iscsi_conn(self, cmd, ver, ip0, ip1, user, pw, id, lun, ctl, port,
|
||||
iqn, initiator):
|
||||
out, err = utils.execute(cmd,
|
||||
@@ -135,7 +151,7 @@ class HusBackend:
|
||||
return out
|
||||
|
||||
def del_iscsi_conn(self, cmd, ver, ip0, ip1, user, pw, id, lun, ctl, port,
|
||||
iqn, initiator, force):
|
||||
iqn, initiator):
|
||||
out, err = utils.execute(cmd,
|
||||
'--driver-version', ver,
|
||||
'--ip0', ip0,
|
||||
@@ -149,7 +165,7 @@ class HusBackend:
|
||||
'--port', port,
|
||||
'--target', iqn,
|
||||
'--initiator', initiator,
|
||||
'--force', force,
|
||||
'--force', 1,
|
||||
check_exit_code=True)
|
||||
LOG.debug('del_iscsi_conn: ' + out + ' -- ' + err)
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user