}
}
+test_new_type = {
+ 'name': u'new_type',
+ 'qos_specs_id': None,
+ 'deleted': False,
+ 'created_at': None,
+ 'updated_at': None,
+ 'extra_specs': {
+ 'smarttier': '<is> true',
+ 'smartcache': '<is> true',
+ 'smartpartition': '<is> true',
+ 'thin_provisioning': '<is> true',
+ 'thick_provisioning': '<is> False',
+ 'policy': '2',
+ 'smartcache:cachename': 'cache-test',
+ 'smartpartition:partitionname': 'partition-test',
+ },
+ 'is_public': True,
+ 'deleted_at': None,
+ 'id': u'530a56e1-a1a4-49f3-ab6c-779a6e5d999f',
+ 'description': None,
+}
+
FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'wwpns': ['10000090fa0d6754'],
'wwnns': ['10000090fa0d6755'],
}
"""
+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_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 = """
{
MAP_COMMAND_TO_FAKE_RESPONSE['system/'] = (
FAKE_SYSTEM_VERSION_RESPONSE)
+MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate/cachepartition/POST'] = (
+ FAKE_SYSTEM_VERSION_RESPONSE)
+
def Fake_sleep(time):
pass
self.test_fail = False
self.checkFlag = False
self.remove_chap_flag = False
+ self.cache_not_exist = False
+ self.partition_not_exist = False
def _change_file_mode(self, filepath):
pass
return True
def get_partition_id_by_name(self, name):
+ if self.partition_not_exist:
+ return None
return "11"
- def add_lun_to_partition(self, lunid, partition_id):
- pass
-
def get_cache_id_by_name(self, name):
+ if self.cache_not_exist:
+ return None
return "11"
def add_lun_to_cache(self, lunid, cache_id):
self.assertEqual({'_name_id': '21ec7341-9256-497b-97d9-ef48edcf0637'},
model_update)
+ def test_retype_volume_success(self):
+ self.driver.restclient.login()
+ retype = self.driver.retype(None, test_volume,
+ test_new_type, None, test_host)
+ self.assertTrue(retype)
+
+ def test_retype_volume_cache_fail(self):
+ self.driver.restclient.cache_not_exist = True
+ self.driver.restclient.login()
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.retype, None,
+ test_volume, test_new_type, None, test_host)
+
+ def test_retype_volume_partition_fail(self):
+ self.driver.restclient.partition_not_exist = True
+ self.driver.restclient.login()
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.retype, None,
+ test_volume, test_new_type, None, test_host)
+
+ @mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
+ def test_retype_volume_fail(self, mock_add_lun_to_partition):
+ self.driver.restclient.login()
+ mock_add_lun_to_partition.side_effect = (
+ exception.VolumeBackendAPIException(data='Error occurred.'))
+ retype = self.driver.retype(None, test_volume,
+ test_new_type, None, test_host)
+ self.assertFalse(retype)
+
def create_fake_conf_file(self):
"""Create a fake Config file
CONF.register_opts(huawei_opt)
-class HuaweiBaseDriver(driver.MigrateVD, driver.BaseVD):
+class HuaweiBaseDriver(driver.VolumeDriver):
def __init__(self, *args, **kwargs):
super(HuaweiBaseDriver, self).__init__(*args, **kwargs)
self.restclient.remove_host(host_id)
self.restclient.delete_mapping_view(view_id)
+ def retype(self, ctxt, volume, new_type, diff, host):
+ """Convert the volume to be of the new type."""
+ LOG.debug("Enter retype: id=%(id)s, new_type=%(new_type)s, "
+ "diff=%(diff)s, host=%(host)s.", {'id': volume['id'],
+ 'new_type': new_type,
+ 'diff': diff,
+ 'host': host})
+
+ # Check what changes are needed
+ migration, change_opts, lun_id = self.determine_changes_when_retype(
+ volume, new_type, host)
+
+ try:
+ if migration:
+ LOG.debug("Begin to migrate LUN(id: %(lun_id)s) with "
+ "change %(change_opts)s.",
+ {"lun_id": lun_id, "change_opts": change_opts})
+ if self._migrate_volume(volume, host, new_type):
+ return True
+ else:
+ LOG.warning(_LW("Storage-assisted migration failed during "
+ "retype."))
+ return False
+ else:
+ # Modify lun to change policy
+ self.modify_lun(lun_id, change_opts)
+ return True
+ except exception.VolumeBackendAPIException:
+ LOG.exception(_LE('Retype volume error.'))
+ return False
+
+ def modify_lun(self, lun_id, change_opts):
+ if change_opts.get('partitionid', None):
+ old, new = change_opts['partitionid']
+ old_id = old[0]
+ old_name = old[1]
+ new_id = new[0]
+ new_name = new[1]
+ if old_id:
+ self.restclient.remove_lun_from_partition(lun_id, old_id)
+ if new_id:
+ self.restclient.add_lun_to_partition(lun_id, new_id)
+ LOG.info(_LI("Retype LUN(id: %(lun_id)s) smartpartition from "
+ "(name: %(old_name)s, id: %(old_id)s) to "
+ "(name: %(new_name)s, id: %(new_id)s) success."),
+ {"lun_id": lun_id,
+ "old_id": old_id, "old_name": old_name,
+ "new_id": new_id, "new_name": new_name})
+
+ if change_opts.get('cacheid', None):
+ old, new = change_opts['cacheid']
+ old_id = old[0]
+ old_name = old[1]
+ new_id = new[0]
+ new_name = new[1]
+ if old_id:
+ self.restclient.remove_lun_from_cache(lun_id, old_id)
+ if new_id:
+ self.restclient.add_lun_to_cache(lun_id, new_id)
+ LOG.info(_LI("Retype LUN(id: %(lun_id)s) smartcache from "
+ "(name: %(old_name)s, id: %(old_id)s) to "
+ "(name: %(new_name)s, id: %(new_id)s) successfully."),
+ {'lun_id': lun_id,
+ 'old_id': old_id, "old_name": old_name,
+ 'new_id': new_id, "new_name": new_name})
+
+ if change_opts.get('policy', None):
+ old_policy, new_policy = change_opts['policy']
+ self.restclient.change_lun_smarttier(lun_id, new_policy)
+ LOG.info(_LI("Retype LUN(id: %(lun_id)s) smarttier policy from "
+ "%(old_policy)s to %(new_policy)s success."),
+ {'lun_id': lun_id,
+ 'old_policy': old_policy,
+ 'new_policy': new_policy})
+
+ if change_opts.get('qos', None):
+ old_qos, new_qos = change_opts['qos']
+ old_qos_id = old_qos[0]
+ old_qos_value = old_qos[1]
+ if old_qos_id:
+ self.remove_qos_lun(lun_id, old_qos_id)
+ if new_qos:
+ smart_qos = smartx.SmartQos(self.restclient)
+ smart_qos.create_qos(new_qos, lun_id)
+ LOG.info(_LI("Retype LUN(id: %(lun_id)s) smartqos from "
+ "%(old_qos_value)s to %(new_qos)s success."),
+ {'lun_id': lun_id,
+ 'old_qos_value': old_qos_value,
+ 'new_qos': new_qos})
+
+ def get_lun_specs(self, lun_id):
+ lun_opts = {
+ 'policy': None,
+ 'partitionid': None,
+ 'cacheid': None,
+ 'LUNType': None,
+ }
+
+ lun_info = self.restclient.get_lun_info(lun_id)
+ lun_opts['LUNType'] = int(lun_info['ALLOCTYPE'])
+ if lun_info['DATATRANSFERPOLICY']:
+ lun_opts['policy'] = lun_info['DATATRANSFERPOLICY']
+ if lun_info['SMARTCACHEPARTITIONID']:
+ lun_opts['cacheid'] = lun_info['SMARTCACHEPARTITIONID']
+ if lun_info['CACHEPARTITIONID']:
+ lun_opts['partitionid'] = lun_info['CACHEPARTITIONID']
+
+ return lun_opts
+
+ def determine_changes_when_retype(self, volume, new_type, host):
+ migration = False
+ change_opts = {
+ 'policy': None,
+ 'partitionid': None,
+ 'cacheid': None,
+ 'qos': None,
+ 'host': None,
+ 'LUNType': None,
+ }
+
+ lun_id = volume.get('provider_location', None)
+ old_opts = self.get_lun_specs(lun_id)
+
+ new_specs = new_type['extra_specs']
+ new_opts = huawei_utils._get_extra_spec_value(new_specs)
+ new_opts = smartx.SmartX().get_smartx_specs_opts(new_opts)
+
+ if 'LUNType' not in new_opts:
+ new_opts['LUNType'] = huawei_utils.find_luntype_in_xml(
+ self.xml_file_path)
+
+ if volume['host'] != host['host']:
+ migration = True
+ change_opts['host'] = (volume['host'], host['host'])
+ if old_opts['LUNType'] != new_opts['LUNType']:
+ migration = True
+ change_opts['LUNType'] = (old_opts['LUNType'], new_opts['LUNType'])
+
+ new_cache_id = None
+ new_cache_name = new_opts['cachename']
+ if new_cache_name:
+ new_cache_id = self.restclient.get_cache_id_by_name(new_cache_name)
+ if new_cache_id is None:
+ msg = (_(
+ "Can't find cache name on the array, cache name is: "
+ "%(name)s.") % {'name': new_cache_name})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ new_partition_id = None
+ new_partition_name = new_opts['partitionname']
+ if new_partition_name:
+ new_partition_id = self.restclient.get_partition_id_by_name(
+ new_partition_name)
+ if new_partition_id is None:
+ msg = (_(
+ "Can't find partition name on the array, partition name "
+ "is: %(name)s.") % {'name': new_partition_name})
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
+
+ # smarttier
+ if old_opts['policy'] != new_opts['policy']:
+ change_opts['policy'] = (old_opts['policy'], new_opts['policy'])
+
+ # smartcache
+ old_cache_id = old_opts['cacheid']
+ if old_cache_id != new_cache_id:
+ old_cache_name = None
+ if old_cache_id:
+ cache_info = self.restclient.get_cache_info_by_id(old_cache_id)
+ old_cache_name = cache_info['NAME']
+ change_opts['cacheid'] = ([old_cache_id, old_cache_name],
+ [new_cache_id, new_cache_name])
+
+ # smartpartition
+ old_partition_id = old_opts['partitionid']
+ if old_partition_id != new_partition_id:
+ old_partition_name = None
+ if old_partition_id:
+ partition_info = self.restclient.get_partition_info_by_id(
+ old_partition_id)
+ old_partition_name = partition_info['NAME']
+ change_opts['partitionid'] = ([old_partition_id,
+ old_partition_name],
+ [new_partition_id,
+ new_partition_name])
+
+ # smartqos
+ new_qos = huawei_utils.get_qos_by_volume_type(new_type)
+ old_qos_id = self.restclient.get_qosid_by_lunid(lun_id)
+ old_qos = self._get_qos_specs_from_array(old_qos_id)
+ if old_qos != new_qos:
+ change_opts['qos'] = ([old_qos_id, old_qos], new_qos)
+
+ LOG.debug("Determine changes when retype. Migration: "
+ "%(migration)s, change_opts: %(change_opts)s.",
+ {'migration': migration, 'change_opts': change_opts})
+ return migration, change_opts, lun_id
+
+ def _get_qos_specs_from_array(self, qos_id):
+ qos = {}
+ qos_info = {}
+ if qos_id:
+ qos_info = self.restclient.get_qos_info(qos_id)
+
+ for key, value in qos_info.items():
+ if key.upper() in constants.QOS_KEYS:
+ if key.upper() == 'LATENCY' and value == '0':
+ continue
+ else:
+ qos[key.upper()] = value
+ return qos
+
def terminate_connection_fc(self, volume, connector):
"""Delete map between a volume and a host."""
wwns = connector['wwpns']
ISCSI multipath support
SmartX support
Volume migration support
+ Volume retype support
"""
VERSION = "1.1.1"
Multiple pools support
SmartX support
Volume migration support
+ Volume retype support
"""
VERSION = "1.1.1"
result = self.call(url, data, "PUT")
self._assert_rest_result(result, _('Change lun priority error.'))
+ def change_lun_smarttier(self, lunid, smarttier_policy):
+ """Change lun smarttier policy."""
+ url = self.url + "/lun/" + lunid
+ data = json.dumps({"TYPE": "11",
+ "ID": lunid,
+ "DATATRANSFERPOLICY": smarttier_policy})
+
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(
+ result, _('Change lun smarttier policy error.'))
+
def get_qosid_by_lunid(self, lun_id):
"""Get QoS id by lun id."""
url = self.url + "/lun/" + lun_id
result = self.call(url, data, "POST")
self._assert_rest_result(result, _('Add lun to partition error.'))
+ def remove_lun_from_partition(self, lun_id, partition_id):
+ url = (self.url + '/lun/associate/cachepartition?ID=' + partition_id
+ + '&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=' + lun_id)
+
+ result = self.call(url, None, "DELETE")
+ self._assert_rest_result(result, _('Remove lun from partition error.'))
+
def get_cache_id_by_name(self, name):
url = self.url + "/SMARTCACHEPARTITION"
result = self.call(url, None, "GET")
if name == item['NAME']:
return item['ID']
+ def get_cache_info_by_id(self, cacheid):
+ url = self.url + "/SMARTCACHEPARTITION/" + cacheid
+ data = json.dumps({"TYPE": "273",
+ "ID": cacheid})
+
+ result = self.call(url, data, "GET")
+ self._assert_rest_result(
+ result, _('Get smartcache by cache id error.'))
+
+ return result['data']
+
+ def remove_lun_from_cache(self, lun_id, cache_id):
+ url = self.url + "/SMARTCACHEPARTITION/REMOVE_ASSOCIATE"
+ data = json.dumps({"ID": cache_id,
+ "ASSOCIATEOBJTYPE": 11,
+ "ASSOCIATEOBJID": lun_id,
+ "TYPE": 273})
+
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, _('Remove lun from cache error.'))
+
def find_available_qos(self, qos):
""""Find available QoS on the array."""
qos_id = None
return (qos_id, lun_list)
def add_lun_to_qos(self, qos_id, lun_id, lun_list):
+ """Add lun to QoS."""
url = self.url + "/ioclass/" + qos_id
lun_list = []
lun_string = lun_list[1:-1]