]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add multiple pools support for Huawei driver
authorchenzongliang <chenzongliang@huawei.com>
Thu, 4 Jun 2015 03:47:17 +0000 (11:47 +0800)
committerliuxinguo <liuxinguo@huawei.com>
Sat, 25 Jul 2015 06:54:06 +0000 (14:54 +0800)
Pool aware scheduler support was introduced in Juno.
This patch proposes to add multiple pools support
for Huawei driver.

Co-Authored-By: Bob-OpenStack liuxinguo@huawei.com
Change-Id: Ibe3b2a123ea5c700d2c2f66d262a8161c5d3e129
Implements: blueprint huawei-storage-multiple-pools-support

cinder/tests/unit/test_huawei_drivers.py
cinder/volume/drivers/huawei/constants.py
cinder/volume/drivers/huawei/huawei_driver.py
cinder/volume/drivers/huawei/huawei_utils.py
cinder/volume/drivers/huawei/rest_client.py

index 8991f77b8566e4632dbd83e580aa5c0cc68cd01d..c037710cbe9b4e6d0a701260bfda8dc2b246f502 100644 (file)
@@ -26,6 +26,7 @@ from oslo_log import log as logging
 from cinder import exception
 from cinder import test
 from cinder.volume import configuration as conf
+from cinder.volume.drivers.huawei import constants
 from cinder.volume.drivers.huawei import huawei_driver
 from cinder.volume.drivers.huawei import huawei_utils
 from cinder.volume.drivers.huawei import rest_client
@@ -46,6 +47,20 @@ test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
                'provider_location': '11',
                }
 
+error_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0637',
+                'size': 2,
+                'volume_name': 'vol2',
+                'id': '21ec7341-9256-497b-97d9-ef48edcf0637',
+                'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0637',
+                'provider_auth': None,
+                'project_id': 'project',
+                'display_name': 'vol2',
+                'display_description': 'test error_volume',
+                'volume_type_id': None,
+                'host': 'ubuntu@huawei#OpenStack_Pool_error',
+                'provider_location': '12',
+                }
+
 test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
              'size': 1,
              'volume_name': 'vol1',
@@ -593,6 +608,26 @@ FAKE_PORT_GROUP_RESPONSE = """
 }
 """
 
+FAKE_ISCSI_INITIATOR_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":[{
+        "CHAPNAME":"mm-user",
+        "HEALTHSTATUS":"1",
+        "ID":"iqn.1993-08.org.debian:01:9073aba6c6f",
+        "ISFREE":"true",
+        "MULTIPATHTYPE":"1",
+        "NAME":"",
+        "OPERATIONSYSTEM":"255",
+        "RUNNINGSTATUS":"28",
+        "TYPE":222,
+        "USECHAP":"true"
+    }]
+}
+"""
+
 FAKE_ERROR_INFO_RESPONSE = """
 {
     "error":{
@@ -975,6 +1010,9 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         self.assertRaises(exception.VolumeBackendAPIException,
                           self.driver.create_volume, test_volume)
 
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume, error_volume)
+
     def test_delete_volume_fail(self):
         self.driver.restclient.login()
         self.driver.restclient.test_fail = True
@@ -995,7 +1033,7 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         self.assertEqual(1, iscsi_properties['data']['target_lun'])
 
     def test_get_default_timeout(self):
-        result = huawei_utils._get_default_timeout(self.xml_file_path)
+        result = huawei_utils.get_default_timeout(self.xml_file_path)
         self.assertEqual('43200', result)
 
     def test_get_wait_interval(self):
@@ -1005,14 +1043,14 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
 
     def test_lun_is_associated_to_lungroup(self):
         self.driver.restclient.login()
-        self.driver.restclient._associate_lun_to_lungroup('11', '11')
+        self.driver.restclient.associate_lun_to_lungroup('11', '11')
         result = self.driver.restclient._is_lun_associated_to_lungroup('11',
                                                                        '11')
         self.assertTrue(result)
 
     def test_lun_is_not_associated_to_lun_group(self):
         self.driver.restclient.login()
-        self.driver.restclient._associate_lun_to_lungroup('12', '12')
+        self.driver.restclient.associate_lun_to_lungroup('12', '12')
         self.driver.restclient.remove_lun_from_lungroup('12', '12')
         result = self.driver.restclient._is_lun_associated_to_lungroup('12',
                                                                        '12')
@@ -1080,6 +1118,36 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
                                                       initiator_name)
         self.assertEqual('1', type)
 
+    def test_find_pool_info(self):
+        self.driver.restclient.login()
+        pools = {
+            "error": {"code": 0},
+            "data": [{
+                "NAME": "test001",
+                "ID": "0",
+                "USERFREECAPACITY": "36",
+                "USERTOTALCAPACITY": "48",
+                "USAGETYPE": constants.BLOCK_STORAGE_POOL_TYPE},
+                {"NAME": "test002",
+                 "ID": "1",
+                 "USERFREECAPACITY": "37",
+                 "USERTOTALCAPACITY": "49",
+                 "USAGETYPE": constants.FILE_SYSTEM_POOL_TYPE}]}
+        pool_name = 'test001'
+        test_info = {'CAPACITY': '36', 'ID': '0', 'TOTALCAPACITY': '48'}
+        pool_info = self.driver.restclient.find_pool_info(pool_name, pools)
+        self.assertEqual(test_info, pool_info)
+
+        pool_name = 'test002'
+        test_info = {}
+        pool_info = self.driver.restclient.find_pool_info(pool_name, pools)
+        self.assertEqual(test_info, pool_info)
+
+        pool_name = 'test000'
+        test_info = {}
+        pool_info = self.driver.restclient.find_pool_info(pool_name, pools)
+        self.assertEqual(test_info, pool_info)
+
     def create_fake_conf_file(self):
         """Create a fake Config file.
 
@@ -1252,6 +1320,9 @@ class Huawei18000FCDriverTestCase(test.TestCase):
         self.assertRaises(exception.VolumeBackendAPIException,
                           self.driver.create_volume, test_volume)
 
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume, error_volume)
+
     def test_delete_volume_fail(self):
         self.driver.restclient.login()
         self.driver.restclient.test_fail = True
@@ -1272,7 +1343,7 @@ class Huawei18000FCDriverTestCase(test.TestCase):
                           test_volume, FakeConnector)
 
     def test_get_default_timeout(self):
-        result = huawei_utils._get_default_timeout(self.xml_file_path)
+        result = huawei_utils.get_default_timeout(self.xml_file_path)
         self.assertEqual('43200', result)
 
     def test_get_wait_interval(self):
@@ -1282,14 +1353,14 @@ class Huawei18000FCDriverTestCase(test.TestCase):
 
     def test_lun_is_associated_to_lungroup(self):
         self.driver.restclient.login()
-        self.driver.restclient._associate_lun_to_lungroup('11', '11')
+        self.driver.restclient.associate_lun_to_lungroup('11', '11')
         result = self.driver.restclient._is_lun_associated_to_lungroup('11',
                                                                        '11')
         self.assertTrue(result)
 
     def test_lun_is_not_associated_to_lun_group(self):
         self.driver.restclient.login()
-        self.driver.restclient._associate_lun_to_lungroup('12', '12')
+        self.driver.restclient.associate_lun_to_lungroup('12', '12')
         self.driver.restclient.remove_lun_from_lungroup('12', '12')
         result = self.driver.restclient._is_lun_associated_to_lungroup('12',
                                                                        '12')
index 20f754ac4cb4c49c8e2a23835df4ea68129b768e..4afd4b172e9d3f75ffb4121cbfb406421e694978 100644 (file)
 
 STATUS_HEALTH = '1'
 STATUS_RUNNING = '10'
+BLOCK_STORAGE_POOL_TYPE = '1'
+FILE_SYSTEM_POOL_TYPE = '2'
+STATUS_VOLUME_READY = '27'
+STATUS_LUNCOPY_READY = '40'
 
 HOSTGROUP_PREFIX = 'OpenStack_HostGroup_'
 LUNGROUP_PREFIX = 'OpenStack_LunGroup_'
@@ -25,6 +29,7 @@ DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30
 DEFAULT_WAIT_INTERVAL = 5
 ERROR_CONNECT_TO_SERVER = -403
 ERROR_UNAUTHORIZED_TO_SERVER = -401
+SOCKET_TIME_OUT = 720
 
 MAX_HOSTNAME_LENTH = 31
 
index 8b4bbab493f65acbe7e5a31d3dc49a31d42030c0..4032307651f7bfa12e8848288fe0ba0c285578a6 100644 (file)
@@ -27,6 +27,7 @@ from cinder.volume import driver
 from cinder.volume.drivers.huawei import constants
 from cinder.volume.drivers.huawei import huawei_utils
 from cinder.volume.drivers.huawei import rest_client
+from cinder.volume import utils as volume_utils
 from cinder.zonemanager import utils as fczm_utils
 
 LOG = logging.getLogger(__name__)
@@ -69,7 +70,15 @@ class HuaweiBaseDriver(driver.VolumeDriver):
     @utils.synchronized('huawei', external=True)
     def create_volume(self, volume):
         """Create a volume."""
-        pool_info = self.restclient.find_pool_info()
+        pool_name = volume_utils.extract_host(volume['host'],
+                                              level='pool')
+        pools = self.restclient.find_all_pools()
+        pool_info = self.restclient.find_pool_info(pool_name, pools)
+        if not pool_info:
+            msg = (_('Error in getting pool information for the pool: %s.')
+                   % pool_name)
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
         volume_name = huawei_utils.encode_name(volume['id'])
         volume_description = volume['name']
         volume_size = huawei_utils.get_volume_size(volume)
@@ -156,8 +165,8 @@ class HuaweiBaseDriver(driver.VolumeDriver):
         def _volume_ready():
             result = self.restclient.get_lun_info(tgt_lun_id)
 
-            if result['HEALTHSTATUS'] == "1":
-                if result['RUNNINGSTATUS'] == "27":
+            if result['HEALTHSTATUS'] == constants.STATUS_HEALTH:
+                if result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY:
                     return True
             return False
 
@@ -545,12 +554,12 @@ class HuaweiBaseDriver(driver.VolumeDriver):
 
             def _luncopy_complete():
                 luncopy_info = self.restclient.get_luncopy_info(luncopy_id)
-                if luncopy_info['status'] == '40':
+                if luncopy_info['status'] == constants.STATUS_LUNCOPY_READY:
                     # luncopy_info['status'] means for the running status of
                     # the luncopy. If luncopy_info['status'] is equal to '40',
                     # this luncopy is completely ready.
                     return True
-                elif luncopy_info['state'] != '1':
+                elif luncopy_info['state'] != constants.STATUS_HEALTH:
                     # luncopy_info['state'] means for the healthy status of the
                     # luncopy. If luncopy_info['state'] is not equal to '1',
                     # this means that an error occurred during the LUNcopy
index ae13da95326d8905a68d2f2a192e42dc8040698e..8bb7267d7949bc372785043d8262a5f4503b5521 100644 (file)
@@ -89,7 +89,7 @@ def _get_opts_from_specs(opts_capability, opts_value, specs):
     opts.update(opts_capability)
     opts.update(opts_value)
 
-    for key, value in specs.iteritems():
+    for key, value in specs.items():
 
         # Get the scope, if is using scope format.
         scope = None
@@ -206,7 +206,7 @@ def get_qos_by_volume_type(volume_type):
         return qos
 
     LOG.info(_LI('The QoS sepcs is: %s.'), kvs)
-    for key, value in kvs.iteritems():
+    for key, value in kvs.items():
         if key in constants.HUAWEI_VALID_KEYS:
             if (key.upper() != 'IOTYPE') and (int(value) <= 0):
                 err_msg = (_('Qos config is wrong. %(key)s'
@@ -243,7 +243,7 @@ def _get_volume_type(type_id):
 def get_lun_conf_params(xml_file_path):
     """Get parameters from config file for creating lun."""
     lunsetinfo = {
-        'LUNType': 'Thick',
+        'LUNType': 0,
         'StripUnitSize': '64',
         'WriteType': '1',
         'MirrorSwitch': '1',
@@ -361,7 +361,7 @@ def get_wait_interval(xml_file_path, event_type):
     return int(wait_interval)
 
 
-def _get_default_timeout(xml_file_path):
+def get_default_timeout(xml_file_path):
     """Get timeout from huawei conf file."""
     root = parse_xml_file(xml_file_path)
     timeout = root.findtext('LUN/Timeout')
@@ -378,7 +378,7 @@ def _get_default_timeout(xml_file_path):
 def wait_for_condition(xml_file_path, func, interval, timeout=None):
     start_time = time.time()
     if timeout is None:
-        timeout = _get_default_timeout(xml_file_path)
+        timeout = get_default_timeout(xml_file_path)
 
     def _inner():
         try:
@@ -440,7 +440,7 @@ def get_iscsi_conf(xml_file_path):
 
 def check_qos_high_priority(qos):
     """Check QoS priority."""
-    for key, value in qos.iteritems():
+    for key, value in qos.items():
         if (key.find('MIN') == 0) or (key.find('LATENCY') == 0):
             return True
 
index 5e1af6214475c36b4200ecca1856ed7105afd4f1..adff4b1aeb6e88ff89de85d55fe52228f73e38ca 100644 (file)
@@ -14,6 +14,7 @@
 #    under the License.
 
 import json
+import socket
 import time
 
 from oslo_log import log as logging
@@ -47,16 +48,18 @@ class RestClient(object):
         Send HTTPS call, get response in JSON.
         Convert response into Python Object and return it.
         """
-        opener = urllib.build_opener(urllib.HTTPCookieProcessor(self.cookie))
-        urllib.install_opener(opener)
+
+        handler = urllib.request.HTTPCookieProcessor(self.cookie)
+        opener = urllib.request.build_opener(handler)
+        urllib.request.install_opener(opener)
         res_json = None
 
         try:
-            urllib.socket.setdefaulttimeout(720)
-            req = urllib.Request(url, data, self.headers)
+            socket.setdefaulttimeout(constants.SOCKET_TIME_OUT)
+            req = urllib.request.Request(url, data, self.headers)
             if method:
                 req.get_method = lambda: method
-            res = urllib.urlopen(req).read().decode("utf-8")
+            res = urllib.request.urlopen(req).read().decode("utf-8")
 
             if "xx/sessions" not in url:
                 LOG.info(_LI('\n\n\n\nRequest URL: %(url)s\n\n'
@@ -98,15 +101,15 @@ class RestClient(object):
             if result['error']['code'] == constants.ERROR_CONNECT_TO_SERVER:
                 continue
 
-            if (result['error']['code'] != 0) or ("data" not in result):
+            if (result['error']['code'] != 0) or ('data' not in result):
                 msg = (_("Login error, reason is: %s.") % result)
                 LOG.error(msg)
                 raise exception.VolumeBackendAPIException(data=msg)
 
-            deviceid = result['data']['deviceid']
-            self.url = item_url + deviceid
+            device_id = result['data']['deviceid']
+            self.url = item_url + device_id
             self.headers['iBaseToken'] = result['data']['iBaseToken']
-            return deviceid
+            return device_id
 
         msg = _("Login error: Can't connect to server.")
         LOG.error(msg)
@@ -121,7 +124,7 @@ class RestClient(object):
             raise exception.VolumeBackendAPIException(data=msg)
 
     def _assert_data_in_result(self, result, msg):
-        if "data" not in result:
+        if 'data' not in result:
             err_msg = (_('%s "data" was not in result.') % msg)
             LOG.error(err_msg)
             raise exception.VolumeBackendAPIException(data=err_msg)
@@ -159,34 +162,33 @@ class RestClient(object):
         result = self.call(url, data, "DELETE")
         self._assert_rest_result(result, 'Delete lun error.')
 
-    def find_pool_info(self):
-        root = huawei_utils.parse_xml_file(self.xml_file_path)
-        pool_name = root.findtext('Storage/StoragePool')
-        if not pool_name:
-            err_msg = (_("Invalid resource pool: %s.") % pool_name)
-            LOG.error(err_msg)
-            raise exception.InvalidInput(err_msg)
-
+    def find_all_pools(self):
         url = self.url + "/storagepool"
         result = self.call(url, None)
         self._assert_rest_result(result, 'Query resource pool error.')
+        self._assert_data_in_result(result, 'Query resource pool error.')
+        return result
 
+    def find_pool_info(self, pool_name, result):
         poolinfo = {}
-        if "data" in result:
+        if not pool_name:
+            return poolinfo
+
+        if 'data' in result:
             for item in result['data']:
                 if pool_name.strip() == item['NAME']:
+                    # USAGETYPE means pool type.
+                    if ('USAGETYPE' in item and
+                       item['USAGETYPE'] == constants.FILE_SYSTEM_POOL_TYPE):
+                        break
                     poolinfo['ID'] = item['ID']
                     poolinfo['CAPACITY'] = item['USERFREECAPACITY']
                     poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY']
                     break
-        if not poolinfo:
-            msg = (_('Get pool info error, pool name is: %s.') % pool_name)
-            LOG.error(msg)
-            raise exception.VolumeBackendAPIException(data=msg)
         return poolinfo
 
     def _get_id_from_result(self, result, name, key):
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if name == item[key]:
                     return item['ID']
@@ -358,7 +360,7 @@ class RestClient(object):
             is_associated = self._is_lun_associated_to_lungroup(lungroup_id,
                                                                 lun_id)
             if not is_associated:
-                self._associate_lun_to_lungroup(lungroup_id, lun_id)
+                self.associate_lun_to_lungroup(lungroup_id, lun_id)
 
             if view_id is None:
                 view_id = self._add_mapping_view(mapping_view_name)
@@ -519,7 +521,7 @@ class RestClient(object):
         self._assert_rest_result(result, 'Find host lun id error.')
 
         host_lun_id = 1
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if lun_id == item['ID']:
                     associate_data = item['ASSOCIATEMETADATA']
@@ -587,7 +589,7 @@ class RestClient(object):
         result = self.call(url, data)
         self._assert_rest_result(result, 'Add new host error.')
 
-        if "data" in result:
+        if 'data' in result:
             return result['data']['ID']
         else:
             return
@@ -629,7 +631,7 @@ class RestClient(object):
         result = self.call(url, data)
         self._assert_rest_result(result, 'Associate host to hostgroup error.')
 
-    def _associate_lun_to_lungroup(self, lungroup_id, lun_id):
+    def associate_lun_to_lungroup(self, lungroup_id, lun_id):
         """Associate lun to lungroup."""
         url = self.url + "/lungroup/associate"
         data = json.dumps({"ID": lungroup_id,
@@ -655,7 +657,7 @@ class RestClient(object):
         self._assert_rest_result(result,
                                  'Check initiator added to array error.')
 
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if item["ID"] == ininame:
                     return True
@@ -668,7 +670,7 @@ class RestClient(object):
         self._assert_rest_result(result,
                                  'Check initiator associated to host error.')
 
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if item['ID'] == ininame and item['ISFREE'] == "true":
                     return False
@@ -870,7 +872,7 @@ class RestClient(object):
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, 'Find lun number error.')
         lunnum = -1
-        if "data" in result:
+        if 'data' in result:
             lunnum = result['data']['COUNT']
         return lunnum
 
@@ -905,9 +907,9 @@ class RestClient(object):
         result = self.call(url, data, "PUT")
         self._assert_rest_result(result, 'Start LUNcopy error.')
 
-    def _get_capacity(self):
+    def _get_capacity(self, pool_name, result):
         """Get free capacity and total capacity of the pool."""
-        poolinfo = self.find_pool_info()
+        poolinfo = self.find_pool_info(pool_name, result)
         pool_capacity = {'total_capacity': 0.0,
                          'free_capacity': 0.0}
 
@@ -927,7 +929,7 @@ class RestClient(object):
         self._assert_rest_result(result, 'Get LUNcopy information error.')
 
         luncopyinfo = {}
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if luncopyid == item['ID']:
                     luncopyinfo['name'] = item['NAME']
@@ -1031,7 +1033,7 @@ class RestClient(object):
         self._assert_rest_result(result, msg)
 
         fc_wwpns = None
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if wwn == item['INITIATOR_PORT_WWN']:
                     fc_wwpns = item['TARGET_PORT_WWN']
@@ -1041,25 +1043,27 @@ class RestClient(object):
 
     def update_volume_stats(self):
         root = huawei_utils.parse_xml_file(self.xml_file_path)
-        pool_name = root.findtext('Storage/StoragePool')
-        if not pool_name:
+        pool_names = root.findtext('Storage/StoragePool')
+        if not pool_names:
             msg = (_(
                 'Invalid resource pool name. '
                 'Please check the config file.'))
             LOG.error(msg)
             raise exception.InvalidInput(msg)
         data = {}
-        capacity = self._get_capacity()
         data["pools"] = []
-        pool = {}
-        pool.update(dict(
-            pool_name=pool_name,
-            total_capacity_gb=capacity['total_capacity'],
-            free_capacity_gb=capacity['free_capacity'],
-            reserved_percentage=0,
-            QoS_support=True,
-        ))
-        data["pools"].append(pool)
+        result = self.find_all_pools()
+        for pool_name in pool_names.split(";"):
+            pool_name = pool_name.strip(' \t\n\r')
+            capacity = self._get_capacity(pool_name, result)
+            pool = {'pool_name': pool_name,
+                    'total_capacity_gb': capacity['total_capacity'],
+                    'free_capacity_gb': capacity['free_capacity'],
+                    'reserved_percentage': 0,
+                    'QoS_support': True,
+                    }
+
+            data["pools"].append(pool)
         return data
 
     def _find_qos_policy_info(self, policy_name):
@@ -1070,7 +1074,7 @@ class RestClient(object):
         self._assert_rest_result(result, msg)
 
         qos_info = {}
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if policy_name == item['NAME']:
                     qos_info['ID'] = item['ID']
@@ -1099,7 +1103,7 @@ class RestClient(object):
         self._assert_rest_result(result, msg)
         self._assert_data_in_result(result, msg)
 
-        if "data" in result:
+        if 'data' in result:
             for item in result['data']:
                 if (item['IPV4ADDR'] and item['HEALTHSTATUS'] ==
                     constants.STATUS_HEALTH