]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add extend_volume for Huawei drivers
authorzhangchao010 <zhangchao010@huawei.com>
Fri, 18 Oct 2013 08:32:56 +0000 (16:32 +0800)
committerzhangchao010 <zhangchao010@huawei.com>
Fri, 18 Oct 2013 08:34:34 +0000 (16:34 +0800)
This patch adds extend_volume function for Huawei drivers.

Huawei T driver needs to create a slave LUN for the extended LUN.
And accordingly it needs to delete all these slave LUNs before deleting
the extended LUN.

HVS driver just sends a rest command to finish extending volume.

Change-Id: Iae17dba8c4d88ec09eb52b4a14bd17bc5a2c1ccc

cinder/tests/test_huawei_hvs.py
cinder/tests/test_huawei_t_dorado.py
cinder/volume/drivers/huawei/huawei_hvs.py
cinder/volume/drivers/huawei/huawei_t.py
cinder/volume/drivers/huawei/rest_common.py
cinder/volume/drivers/huawei/ssh_common.py

index c05b91f95680c813757298986a9d2823b66a90b0..55d2b4a9035e2d7b3b7e161fb70886488405f0a8 100644 (file)
@@ -64,6 +64,8 @@ FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
                  'host': 'fakehost',
                  'ip': '10.10.0.1'}
 
+volume_size = 3
+
 
 def Fake_sleep(time):
     pass
@@ -466,6 +468,10 @@ class FakeHVSCommon(rest_common.HVSCommon):
             if url == "ioclass/0":
                 data = """{"error":{"code":0}}"""
 
+            if url == "lun/expand":
+                data = """{"error":{"code":0}}"""
+                self.lun_id = '0'
+
         else:
             data = """{"error":{"code":31755596}}"""
 
@@ -528,6 +534,11 @@ class HVSRESTiSCSIDriverTestCase(test.TestCase):
         self.driver.create_volume(test_volume)
         self.assertEqual(self.driver.common.lun_id, "0")
 
+    def test_extend_volume_success(self):
+        self.driver.common.login()
+        self.driver.extend_volume(test_volume, volume_size)
+        self.assertEqual(self.driver.common.lun_id, "0")
+
     def test_create_snapshot_success(self):
         self.driver.common.login()
         self.driver.create_snapshot(test_volume)
@@ -713,6 +724,11 @@ class HVSRESTFCDriverTestCase(test.TestCase):
         self.driver.create_volume(test_volume)
         self.assertEqual(self.driver.common.lun_id, "0")
 
+    def test_extend_volume_success(self):
+        self.driver.common.login()
+        self.driver.extend_volume(test_volume, volume_size)
+        self.assertEqual(self.driver.common.lun_id, "0")
+
     def test_create_snapshot_success(self):
         self.driver.common.login()
         self.driver.create_snapshot(test_volume)
index 4657f0375a14a0d7575584156a3a4956cfbb5f09..31caa97e315bce480636b2aebbe543b25e1c642b 100644 (file)
@@ -332,8 +332,9 @@ def reset_error_flg(cmd):
 
 
 class HuaweiTCLIResSimulator():
-    def _name_translate(self, name):
-        return 'OpenStack_' + str(hash(name))
+    def _paras_name(self, params):
+        index = params.index('-n')
+        return params[index + 1]
 
     def cli_showsys(self, params):
         pass
@@ -341,7 +342,7 @@ class HuaweiTCLIResSimulator():
     def cli_createlun(self, params):
         lun_type = ('THIN' if '-pool' in params else 'THICK')
         if LUN_INFO['ID'] is None:
-            LUN_INFO['Name'] = self._name_translate(FAKE_VOLUME['name'])
+            LUN_INFO['Name'] = self._paras_name(params)
             LUN_INFO['ID'] = VOLUME_SNAP_ID['vol']
             LUN_INFO['Size'] = FAKE_VOLUME['size']
             LUN_INFO['Lun Type'] = lun_type
@@ -350,8 +351,7 @@ class HuaweiTCLIResSimulator():
             LUN_INFO['RAID Group ID'] = POOL_SETTING['ID']
             FAKE_VOLUME['provider_location'] = LUN_INFO['ID']
         else:
-            CLONED_LUN_INFO['Name'] = \
-                self._name_translate(FAKE_CLONED_VOLUME['name'])
+            CLONED_LUN_INFO['Name'] = self._paras_name(params)
             CLONED_LUN_INFO['ID'] = VOLUME_SNAP_ID['vol_copy']
             CLONED_LUN_INFO['Size'] = FAKE_CLONED_VOLUME['size']
             CLONED_LUN_INFO['Lun Type'] = lun_type
@@ -525,8 +525,7 @@ class HuaweiTCLIResSimulator():
         SNAPSHOT_INFO['Source LUN ID'] = LUN_INFO['ID']
         SNAPSHOT_INFO['Source LUN Name'] = LUN_INFO['Name']
         SNAPSHOT_INFO['ID'] = VOLUME_SNAP_ID['snap']
-        SNAPSHOT_INFO['Name'] =\
-            self._name_translate(FAKE_SNAPSHOT['name'])
+        SNAPSHOT_INFO['Name'] = self._paras_name(params)
         SNAPSHOT_INFO['Status'] = 'Disable'
         out = 'command operates successfully'
         return out
@@ -823,6 +822,16 @@ Multipath Type
         out = 'command operates successfully'
         return out
 
+    def cli_addluntoextlun(self, params):
+        LUN_INFO['Size'] = int(LUN_INFO['Size']) + int(CLONED_LUN_INFO['Size'])
+        out = 'command operates successfully'
+        return out
+
+    def cli_rmlunfromextlun(self, patams):
+        LUN_INFO['Size'] = int(LUN_INFO['Size']) - int(CLONED_LUN_INFO['Size'])
+        out = 'command operates successfully'
+        return out
+
 
 class HuaweiDorado5100CLIResSimulator(HuaweiTCLIResSimulator):
     def cli_showsys(self, params):
@@ -928,7 +937,7 @@ class HuaweiDorado2100G2CLIResSimulator(HuaweiTCLIResSimulator):
         lun_type = ('THIN' if params[params.index('-type') + 1] == '2' else
                     'THICK')
         if LUN_INFO['ID'] is None:
-            LUN_INFO['Name'] = self._name_translate(FAKE_VOLUME['name'])
+            LUN_INFO['Name'] = self._paras_name(params)
             LUN_INFO['ID'] = VOLUME_SNAP_ID['vol']
             LUN_INFO['Size'] = FAKE_VOLUME['size']
             LUN_INFO['Lun Type'] = lun_type
@@ -937,8 +946,7 @@ class HuaweiDorado2100G2CLIResSimulator(HuaweiTCLIResSimulator):
             LUN_INFO['RAID Group ID'] = POOL_SETTING['ID']
             FAKE_VOLUME['provider_location'] = LUN_INFO['ID']
         else:
-            CLONED_LUN_INFO['Name'] = \
-                self._name_translate(FAKE_CLONED_VOLUME['name'])
+            CLONED_LUN_INFO['Name'] = self._paras_name(params)
             CLONED_LUN_INFO['ID'] = VOLUME_SNAP_ID['vol_copy']
             CLONED_LUN_INFO['Size'] = FAKE_CLONED_VOLUME['size']
             CLONED_LUN_INFO['Lun Type'] = lun_type
@@ -1208,6 +1216,25 @@ class HuaweiTISCSIDriverTestCase(test.TestCase):
         self.driver.delete_volume(FAKE_VOLUME)
         self.assertIsNone(LUN_INFO['ID'])
 
+    def test_extend_volume(self):
+        VOLUME_SIZE = 5
+        # Test no extended volume
+        self.assertRaises(exception.VolumeNotFound,
+                          self.driver.extend_volume, FAKE_VOLUME, VOLUME_SIZE)
+
+        self.driver.create_volume(FAKE_VOLUME)
+        self.assertEqual(LUN_INFO['Size'], '2')
+        # Test extend volume cli exception
+        set_error_flg('addluntoextlun')
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.extend_volume, FAKE_VOLUME, VOLUME_SIZE)
+        self.assertEqual(CLONED_LUN_INFO['Name'], None)
+
+        self.driver.extend_volume(FAKE_VOLUME, VOLUME_SIZE)
+        self.assertEqual(LUN_INFO['Size'], VOLUME_SIZE)
+        self.driver.delete_volume(FAKE_VOLUME)
+        self.assertEqual(LUN_INFO['Name'], None)
+
     def test_create_delete_snapshot(self):
         # Test no resource pool
         RESPOOL_A_SIM['Valid Size'] = '0'
@@ -1538,6 +1565,12 @@ class HuaweiDorado2100G2FCDriverTestCase(HuaweiTFCDriverTestCase):
                           self.driver.create_volume_from_snapshot,
                           FAKE_CLONED_VOLUME, FAKE_SNAPSHOT)
 
+    def test_extend_volume(self):
+        NEWSIZE = 5
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.extend_volume,
+                          FAKE_VOLUME, NEWSIZE)
+
 
 class HuaweiDorado5100ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase):
     def __init__(self, *args, **kwargs):
@@ -1613,6 +1646,12 @@ class HuaweiDorado2100G2ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase):
         self.driver.delete_volume(FAKE_VOLUME)
         self.assertIsNone(LUN_INFO['ID'])
 
+    def test_extend_volume(self):
+        NEWSIZE = 5
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.extend_volume,
+                          FAKE_VOLUME, NEWSIZE)
+
 
 class SSHMethodTestCase(test.TestCase):
     def __init__(self, *args, **kwargs):
index 513f733c981aa921fa7e64da1c6b5b51c11f1859..0d70948ddeaff36635d407ddda6cfb672c74c486 100644 (file)
@@ -52,6 +52,10 @@ class HuaweiHVSISCSIDriver(driver.ISCSIDriver):
         """Create a clone of the specified volume."""
         self.common.create_cloned_volume(volume, src_vref)
 
+    def extend_volume(self, volume, new_size):
+        """Extend a volume."""
+        self.common.extend_volume(volume, new_size)
+
     def delete_volume(self, volume):
         """Delete a volume."""
         self.common.delete_volume(volume)
@@ -123,6 +127,10 @@ class HuaweiHVSFCDriver(driver.FibreChannelDriver):
         """Create a clone of the specified volume."""
         self.common.create_cloned_volume(volume, src_vref)
 
+    def extend_volume(self, volume, new_size):
+        """Extend a volume."""
+        self.common.extend_volume(volume, new_size)
+
     def delete_volume(self, volume):
         """Delete a volume."""
         self.common.delete_volume(volume)
index 478b9a11489ea7ac7f3d1258c654fcd801dfe7c2..68551752dd4e6e272042dc30cad304f5d3e0c142 100644 (file)
@@ -69,6 +69,10 @@ class HuaweiTISCSIDriver(driver.ISCSIDriver):
         volume_id = self.common.create_cloned_volume(volume, src_vref)
         return {'provider_location': volume_id}
 
+    def extend_volume(self, volume, new_size):
+        """Extend a volume."""
+        self.common.extend_volume(volume, new_size)
+
     def delete_volume(self, volume):
         """Delete a volume."""
         self.common.delete_volume(volume)
@@ -399,6 +403,10 @@ class HuaweiTFCDriver(driver.FibreChannelDriver):
         volume_id = self.common.create_cloned_volume(volume, src_vref)
         return {'provider_location': volume_id}
 
+    def extend_volume(self, volume, new_size):
+        """Extend a volume."""
+        self.common.extend_volume(volume, new_size)
+
     def delete_volume(self, volume):
         """Delete a volume."""
         self.common.delete_volume(volume)
index daeb054c0dbb484fd1de07859d195d917a0726d5..34f684e8bd4031dc53327e62af5ca80027d0e7b6 100644 (file)
@@ -1286,3 +1286,17 @@ class HVSCommon():
         target_iqn = self._get_tgt_iqn(target_ip)
 
         return (target_iqn, target_ip)
+
+    def extend_volume(self, volume, new_size):
+        name = self._encode_name(volume['id'])
+        lun_id = self._get_volume_by_name(name)
+        if lun_id:
+            url = self.url + "/lun/expand"
+            capacity = int(new_size) * units.GiB / 512
+            data = json.dumps({"TYPE": "11",
+                               "ID": lun_id,
+                               "CAPACITY": capacity})
+            result = self.call(url, data, "PUT")
+            self._assert_rest_result(result, 'Extend lun error.')
+        else:
+            LOG.warn(_('Can not find lun in array'))
index ec30edd36fb86399dd4c59bd9e7c614edb6d3780..6609809808c8eec1291093222ee10a1c67fb01e4 100644 (file)
@@ -65,7 +65,7 @@ def ssh_read(user, channel, cmd, timeout):
                 if result.startswith(cmd) and result.endswith(user + ':/>'):
                     break
                 # Some commands need to send 'y'.
-                elif re.search('(y/n)', result):
+                elif re.search('(y/n)|y or n', result):
                     break
                 # Reach maximum limit of SSH connection.
                 elif re.search('No response message', result):
@@ -96,6 +96,7 @@ class TseriesCommon():
         self.ssh_pool = None
         self.lock_ip = threading.Lock()
         self.luncopy_list = []  # to store LUNCopy name
+        self.extended_lun_dict = {}
 
     def do_setup(self, context):
         """Check config file."""
@@ -103,9 +104,11 @@ class TseriesCommon():
 
         self._check_conf_file()
         self.login_info = self._get_login_info()
-        self.lun_distribution = self._get_lun_distribution_info()
+        exist_luns = self._get_all_luns_info()
+        self.lun_distribution = self._get_lun_distribution_info(exist_luns)
         self.luncopy_list = self._get_all_luncopy_name()
         self.hostgroup_id = self._get_hostgroup_id(HOST_GROUP_NAME)
+        self.extended_lun_dict = self._get_extended_lun(exist_luns)
 
     def _check_conf_file(self):
         """Check config file, make sure essential items are set."""
@@ -172,7 +175,7 @@ class TseriesCommon():
     def _change_file_mode(self, filepath):
         utils.execute('chmod', '777', filepath, run_as_root=True)
 
-    def _get_lun_distribution_info(self):
+    def _get_lun_distribution_info(self, luns):
         """Get LUN distribution information.
 
         For we have two controllers for each array, we want to make all
@@ -182,7 +185,6 @@ class TseriesCommon():
 
         """
 
-        luns = self._get_all_luns_info()
         ctr_info = [0, 0]
         for lun in luns:
             if (lun[6].startswith(VOL_AND_SNAP_NAME_PREFIX) and
@@ -207,6 +209,16 @@ class TseriesCommon():
                     luncopy_ids.append(tmp_line[0])
         return luncopy_ids
 
+    def _get_extended_lun(self, luns):
+        extended_dict = {}
+        for lun in luns:
+            if lun[6].startswith('ext'):
+                vol_name = lun[6].split('_')[1]
+                add_ids = extended_dict.get(vol_name, [])
+                add_ids = add_ids.append(lun[0])
+                extended_dict[vol_name] = add_ids
+        return extended_dict
+
     def create_volume(self, volume):
         """Create a new volume."""
         volume_name = self._name_translate(volume['name'])
@@ -474,7 +486,7 @@ class TseriesCommon():
                 while True:
                     ssh_client.chan.send(cmd + '\n')
                     out = ssh_read(user, ssh_client.chan, cmd, 20)
-                    if out.find('(y/n)') > -1:
+                    if out.find('(y/n)') > -1 or out.find('y or n') > -1:
                         cmd = 'y'
                     else:
                         # Put SSH client back into SSH pool.
@@ -502,18 +514,40 @@ class TseriesCommon():
 
         self._update_login_info()
         volume_id = volume.get('provider_location', None)
-        if (volume_id is not None) and self._check_volume_created(volume_id):
-            self._delete_volume(volume_id)
-        else:
+        if volume_id is None or not self._check_volume_created(volume_id):
             err_msg = (_('delete_volume: Volume %(name)s does not exist.')
                        % {'name': volume['name']})
             LOG.warn(err_msg)
+            return
+        else:
+            name = volume_name[len(VOL_AND_SNAP_NAME_PREFIX):]
+            added_vol_ids = self.extended_lun_dict.get(name, None)
+            if added_vol_ids:
+                self._del_lun_from_extended_lun(volume_id, added_vol_ids)
+                self.extended_lun_dict.pop(name)
+            self._delete_volume(volume_id)
 
     def _check_volume_created(self, volume_id):
         cli_cmd = 'showlun -lun %s' % volume_id
         out = self._execute_cli(cli_cmd)
         return (True if re.search('LUN Information', out) else False)
 
+    def _del_lun_from_extended_lun(self, extended_id, added_ids):
+        cli_cmd = 'rmlunfromextlun -ext %s' % extended_id
+        out = self._execute_cli(cli_cmd)
+
+        self._assert_cli_operate_out('_del_lun_from_extended_lun',
+                                     ('Failed to remove LUN from extended '
+                                      'LUN: %s' % extended_id),
+                                     cli_cmd, out)
+        for id in added_ids:
+            cli_cmd = 'dellun -lun %s' % id
+            out = self._execute_cli(cli_cmd)
+
+            self._assert_cli_operate_out('_del_lun_from_extended_lun',
+                                         'Failed to delete LUN: %s' % id,
+                                         cli_cmd, out)
+
     def _delete_volume(self, volumeid):
         """Run CLI command to delete volume."""
         cli_cmd = 'dellun -force -lun %s' % volumeid
@@ -696,6 +730,50 @@ class TseriesCommon():
                     return lun[0]
         return None
 
+    def extend_volume(self, volume, new_size):
+        extended_vol_name = self._name_translate(volume['name'])
+        name = extended_vol_name[len(VOL_AND_SNAP_NAME_PREFIX):]
+        added_vol_ids = self.extended_lun_dict.get(name, [])
+        added_vol_name = ('ext_' + extended_vol_name.split('_')[1] + '_' +
+                          str(len(added_vol_ids)))
+        added_vol_size = str(int(new_size) - int(volume['size'])) + 'G'
+
+        LOG.debug(_('extend_volume: extended volume name: %(extended_name)s '
+                    'new added volume name: %(added_name)s '
+                    'new added volume size: %(added_size)s')
+                  % {'extended_name': extended_vol_name,
+                     'added_name': added_vol_name,
+                     'added_size': added_vol_size})
+
+        if not volume['provider_location']:
+            err_msg = (_('extend_volume: volume %s does not exist.')
+                       % extended_vol_name)
+            LOG.error(err_msg)
+            raise exception.VolumeNotFound(volume_id=extended_vol_name)
+
+        type_id = volume['volume_type_id']
+        parameters = self._parse_volume_type(type_id)
+        added_vol_id = self._create_volume(added_vol_name, added_vol_size,
+                                           parameters)
+        try:
+            self._extend_volume(volume['provider_location'], added_vol_id)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                self._delete_volume(added_vol_id)
+
+        added_vol_ids.append(added_vol_id)
+        self.extended_lun_dict[name] = added_vol_ids
+
+    def _extend_volume(self, extended_vol_id, added_vol_id):
+        cli_cmd = ('addluntoextlun -extlun %(extended_vol)s '
+                   '-lun %(added_vol)s' % {'extended_vol': extended_vol_id,
+                                           'added_vol': added_vol_id})
+        out = self._execute_cli(cli_cmd)
+        self._assert_cli_operate_out('_extend_volume',
+                                     ('Failed to extend volume %s'
+                                      % extended_vol_id),
+                                     cli_cmd, out)
+
     def create_snapshot(self, snapshot):
         snapshot_name = self._name_translate(snapshot['name'])
         volume_name = self._name_translate(snapshot['volume_name'])
@@ -1173,8 +1251,10 @@ class DoradoCommon(TseriesCommon):
         LOG.debug(_('do_setup'))
 
         self._check_conf_file()
-        self.lun_distribution = self._get_lun_ctr_info()
+        exist_luns = self._get_all_luns_info()
+        self.lun_distribution = self._get_lun_distribution_info(exist_luns)
         self.hostgroup_id = self._get_hostgroup_id(HOST_GROUP_NAME)
+        self.extended_lun_dict = self._get_extended_lun(exist_luns)
 
     def _check_conf_file(self):
         """Check the config file, make sure the key elements are set."""
@@ -1234,8 +1314,7 @@ class DoradoCommon(TseriesCommon):
                                 'Dorado5100 and Dorado 2100 G2 now.'))
                     raise exception.InvalidResults()
 
-    def _get_lun_ctr_info(self):
-        luns = self._get_all_luns_info()
+    def _get_lun_distribution_info(self, luns):
         ctr_info = [0, 0]
         (c, n) = ((2, 4) if self.device_type == 'Dorado2100 G2' else (3, 5))
         for lun in luns:
@@ -1246,6 +1325,17 @@ class DoradoCommon(TseriesCommon):
                     ctr_info[1] += 1
         return ctr_info
 
+    def _get_extended_lun(self, luns):
+        extended_dict = {}
+        n = 4 if self.device_type == 'Dorado2100 G2' else 5
+        for lun in luns:
+            if lun[n].startswith('ext'):
+                vol_name = lun[n].split('_')[1]
+                add_ids = extended_dict.get(vol_name, [])
+                add_ids.append(lun[0])
+                extended_dict[vol_name] = add_ids
+        return extended_dict
+
     def _create_volume(self, name, size, params):
         """Create a new volume with the given name and size."""
         cli_cmd = ('createlun -n %(name)s -lunsize %(size)s '
@@ -1311,6 +1401,15 @@ class DoradoCommon(TseriesCommon):
         LOG.error(err_msg)
         raise exception.VolumeBackendAPIException(data=err_msg)
 
+    def extend_volume(self, volume, new_size):
+        if self.device_type == 'Dorado2100 G2':
+            err_msg = (_('extend_volume: %(device)s does not support '
+                         'extend volume.') % {'device': self.device_type})
+            LOG.error(err_msg)
+            raise exception.VolumeBackendAPIException(data=err_msg)
+        else:
+            return TseriesCommon.extend_volume(self, volume, new_size)
+
     def create_snapshot(self, snapshot):
         if self.device_type == 'Dorado2100 G2':
             err_msg = (_('create_snapshot: %(device)s does not support '