diff --git a/cinder/tests/test_hds.py b/cinder/tests/test_hds.py index 20365eb8d1..7491d60c08 100644 --- a/cinder/tests/test_hds.py +++ b/cinder/tests/test_hds.py @@ -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 @@ def create_lu(self, cmd, ver, ip0, ip1, user, pw, id, hdp, start, 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 @@ def add_iscsi_conn(self, cmd, ver, ip0, ip1, user, pw, id, lun, ctl, port, 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 @@ def setUp(self): 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 @@ def test_delete_volume(self): 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 @@ def test_create_snapshot(self): 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). diff --git a/cinder/volume/drivers/hds/hds.py b/cinder/volume/drivers/hds/hds.py index 13efb0bb59..f26051d6f3 100644 --- a/cinder/volume/drivers/hds/hds.py +++ b/cinder/volume/drivers/hds/hds.py @@ -29,7 +29,7 @@ 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 @@ def create_volume(self, volume): '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 @@ def delete_volume(self, volume): 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 @@ def initialize_connection(self, volume, connector): """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 @@ def initialize_connection(self, volume, connector): 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 @@ def initialize_connection(self, volume, connector): @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 @@ def terminate_connection(self, volume, connector, **kwargs): 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 @@ def create_volume_from_snapshot(self, volume, snapshot): 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 @@ def create_snapshot(self, snapshot): """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 @@ def delete_snapshot(self, snapshot): 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 diff --git a/cinder/volume/drivers/hds/hus_backend.py b/cinder/volume/drivers/hds/hus_backend.py index 860e908eef..fd8753d087 100644 --- a/cinder/volume/drivers/hds/hus_backend.py +++ b/cinder/volume/drivers/hds/hus_backend.py @@ -92,6 +92,7 @@ def delete_lu(self, cmd, ver, ip0, ip1, user, pw, id, lun): '--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 @@ def create_dup(self, cmd, ver, ip0, ip1, user, pw, id, src_lun, 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 @@ def add_iscsi_conn(self, cmd, ver, ip0, ip1, user, pw, id, lun, ctl, port, 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 @@ def del_iscsi_conn(self, cmd, ver, ip0, ip1, user, pw, id, lun, ctl, port, '--port', port, '--target', iqn, '--initiator', initiator, - '--force', force, + '--force', 1, check_exit_code=True) LOG.debug('del_iscsi_conn: ' + out + ' -- ' + err) return out