'provider_location': '11',
}
+test_host = {'host': 'ubuntu001@backend001#OpenStack_Pool',
+ 'capabilities': {'smartcache': True,
+ 'location_info': '210235G7J20000000000',
+ 'QoS_support': True,
+ 'pool_name': 'OpenStack_Pool',
+ 'timestamp': '2015-07-13T11:41:00.513549',
+ 'smartpartition': True,
+ 'allocated_capacity_gb': 0,
+ 'volume_backend_name': 'Huawei18000FCDriver',
+ 'free_capacity_gb': 20.0,
+ 'driver_version': '1.1.0',
+ 'total_capacity_gb': 20.0,
+ 'smarttier': True,
+ 'hypermetro': True,
+ 'reserved_percentage': 0,
+ 'vendor_name': None,
+ 'thick_provisioning': True,
+ 'thin_provisioning': True,
+ 'storage_protocol': 'FC',
+ }
+ }
+
FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'wwpns': ['10000090fa0d6754'],
'wwnns': ['10000090fa0d6755'],
},
"data": {
"ID": "11",
- "IOCLASSID": "11",
+ "IOCLASSID": "",
"NAME": "5mFHcBv4RkCcD+JyrWc0SA",
"RUNNINGSTATUS": "2",
"HEALTHSTATUS": "1",
"RUNNINGSTATUS": "27",
- "LUNLIST": ""
+ "ALLOCTYPE": "0",
+ "CAPACITY": "2097152",
+ "WRITEPOLICY": "1",
+ "MIRRORPOLICY": "0",
+ "PREFETCHPOLICY": "1",
+ "PREFETCHVALUE": "20",
+ "DATATRANSFERPOLICY": "1",
+ "READCACHEPOLICY": "2",
+ "WRITECACHEPOLICY": "5",
+ "OWNINGCONTROLLER": "0B",
+ "SMARTCACHEPARTITIONID": "",
+ "CACHEPARTITIONID": ""
}
}
"""
}
"""
-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 = """
{
}
"""
+FAKE_GET_LUN_MIGRATION_RESPONSE = """
+{
+ "data":[{"ENDTIME":"1436816174",
+ "ID":"9",
+ "PARENTID":"11",
+ "PARENTNAME":"xmRBHMlVRruql5vwthpPXQ",
+ "PROCESS":"-1",
+ "RUNNINGSTATUS":"76",
+ "SPEED":"2",
+ "STARTTIME":"1436816111",
+ "TARGETLUNID":"1",
+ "TARGETLUNNAME":"4924891454902893639",
+ "TYPE":253,
+ "WORKMODE":"0"
+ }],
+ "error":{"code":0,
+ "description":"0"}
+}
+"""
+
FAKE_QOS_INFO_RESPONSE = """
{
"error":{
MAP_COMMAND_TO_FAKE_RESPONSE['sessions'] = (
FAKE_LOGIN_OUT_STORAGE_RESPONSE)
+MAP_COMMAND_TO_FAKE_RESPONSE['LUN_MIGRATION/POST'] = (
+ FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['LUN_MIGRATION?range=[0-100]/GET'] = (
+ FAKE_GET_LUN_MIGRATION_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['LUN_MIGRATION/11/DELETE'] = (
+ FAKE_COMMON_SUCCESS_RESPONSE)
+
# mock storage info map
MAP_COMMAND_TO_FAKE_RESPONSE['storagepool'] = (
FAKE_STORAGE_POOL_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?TYPE=256&ASSOCIATEOBJTYPE=11'
'&ASSOCIATEOBJID=1/GET'] = (
- FAKE_LUN_ASSOCIATE_RESPONSE)
+ FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11'
'&ASSOCIATEOBJID=11/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/'] = (
- FAKE_COMMON_SUCCESS_RESPONSE)
+ FAKE_ISCSI_INITIATOR_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/POST'] = (
FAKE_ISCSI_INITIATOR_RESPONSE)
self.driver.restclient.login()
lun_info = self.driver.create_volume_from_snapshot(test_volume,
test_volume)
- self.assertEqual('1', lun_info['provider_location'])
+ self.assertEqual('1', lun_info['ID'])
def test_initialize_connection_success(self):
self.driver.restclient.login()
self.driver.restclient.login()
lun_info = self.driver.create_volume_from_snapshot(test_volume,
test_volume)
- self.assertEqual('1', lun_info['provider_location'])
+ self.assertEqual('1', lun_info['ID'])
def test_initialize_connection_success(self):
self.driver.restclient.login()
self.configuration)
self.assertEqual('0', host_os)
+ def test_migrate_volume_success(self):
+ self.driver.restclient.login()
+
+ # Migrate volume without new type.
+ model_update = None
+ moved = False
+ empty_dict = {}
+ # Migrate volume without new type.
+ moved, model_update = self.driver.migrate_volume(None,
+ test_volume,
+ test_host,
+ None)
+ self.assertTrue(moved)
+ self.assertEqual(empty_dict, model_update)
+
+ # Migrate volume with new type.
+ moved = False
+ empty_dict = {}
+ new_type = {'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'}}
+ moved, model_update = self.driver.migrate_volume(None,
+ test_volume,
+ test_host,
+ new_type)
+ self.assertTrue(moved)
+ self.assertEqual(empty_dict, model_update)
+
+ def test_migrate_volume_fail(self):
+ self.driver.restclient.login()
+ self.driver.restclient.test_fail = True
+
+ # Migrate volume without new type.
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.migrate_volume, None,
+ test_volume, test_host, None)
+
+ # Migrate volume with new type.
+ new_type = {'extra_specs':
+ {'smarttier': '<is> true',
+ 'smartcache': '<is> true',
+ 'thin_provisioning': '<is> true',
+ 'thick_provisioning': '<is> False',
+ 'policy': '2',
+ 'smartcache:cachename': 'cache-test',
+ 'partitionname': 'partition-test'}}
+ self.driver.restclient.test_fail = True
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.migrate_volume, None,
+ test_volume, test_host, new_type)
+
+ def test_check_migration_valid(self):
+ self.driver.restclient.login()
+ is_valid = self.driver._check_migration_valid(test_host,
+ test_volume)
+ self.assertTrue(is_valid)
+ # No pool_name in capabilities.
+ invalid_host1 = {'host': 'ubuntu001@backend002#OpenStack_Pool',
+ 'capabilities':
+ {'location_info': '210235G7J20000000000',
+ 'allocated_capacity_gb': 0,
+ 'volume_backend_name': 'Huawei18000FCDriver',
+ 'storage_protocol': 'FC'}}
+ is_valid = self.driver._check_migration_valid(invalid_host1,
+ test_volume)
+ self.assertFalse(is_valid)
+ # location_info in capabilities is not matched.
+ invalid_host2 = {'host': 'ubuntu001@backend002#OpenStack_Pool',
+ 'capabilities':
+ {'location_info': '210235G7J20000000001',
+ 'allocated_capacity_gb': 0,
+ 'pool_name': 'OpenStack_Pool',
+ 'volume_backend_name': 'Huawei18000FCDriver',
+ 'storage_protocol': 'FC'}}
+ is_valid = self.driver._check_migration_valid(invalid_host2,
+ test_volume)
+ self.assertFalse(is_valid)
+ # storage_protocol is not match current protocol and volume status is
+ # 'in-use'.
+ volume_in_use = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'size': 2,
+ 'volume_name': 'vol1',
+ 'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+ 'volume_attachment': 'in-use',
+ 'provider_location': '11'}
+ invalid_host2 = {'host': 'ubuntu001@backend002#OpenStack_Pool',
+ 'capabilities':
+ {'location_info': '210235G7J20000000001',
+ 'allocated_capacity_gb': 0,
+ 'pool_name': 'OpenStack_Pool',
+ 'volume_backend_name': 'Huawei18000FCDriver',
+ 'storage_protocol': 'iSCSI'}}
+ is_valid = self.driver._check_migration_valid(invalid_host2,
+ volume_in_use)
+ self.assertFalse(is_valid)
+ # pool_name is empty.
+ invalid_host3 = {'host': 'ubuntu001@backend002#OpenStack_Pool',
+ 'capabilities':
+ {'location_info': '210235G7J20000000001',
+ 'allocated_capacity_gb': 0,
+ 'pool_name': '',
+ 'volume_backend_name': 'Huawei18000FCDriver',
+ 'storage_protocol': 'iSCSI'}}
+ is_valid = self.driver._check_migration_valid(invalid_host3,
+ test_volume)
+ self.assertFalse(is_valid)
+
+ @mock.patch.object(rest_client.RestClient, 'rename_lun')
+ def test_update_migrated_volume_success(self, mock_rename_lun):
+ self.driver.restclient.login()
+ original_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0635'}
+ current_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0636'}
+ model_update = self.driver.update_migrated_volume(None,
+ original_volume,
+ current_volume,
+ 'available')
+ self.assertEqual({'_name_id': None}, model_update)
+
+ @mock.patch.object(rest_client.RestClient, 'rename_lun')
+ def test_update_migrated_volume_fail(self, mock_rename_lun):
+ self.driver.restclient.login()
+ mock_rename_lun.side_effect = exception.VolumeBackendAPIException(
+ data='Error occurred.')
+ original_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0635'}
+ current_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0636',
+ '_name_id': '21ec7341-9256-497b-97d9-ef48edcf0637'}
+ model_update = self.driver.update_migrated_volume(None,
+ original_volume,
+ current_volume,
+ 'available')
+ self.assertEqual({'_name_id': '21ec7341-9256-497b-97d9-ef48edcf0637'},
+ model_update)
+
def create_fake_conf_file(self):
"""Create a fake Config file
userpassword_text = doc.createTextNode('Admin@storage')
userpassword.appendChild(userpassword_text)
storage.appendChild(userpassword)
+
+ protocol = doc.createElement('Protocol')
+ protocol_text = doc.createTextNode('FC')
+ protocol.appendChild(protocol_text)
+ storage.appendChild(protocol)
+
url = doc.createElement('RestURL')
url_text = doc.createTextNode('http://100.115.10.69:8082/'
'deviceManager/rest/')
storagepool.appendChild(pool_text)
lun.appendChild(storagepool)
+ lun_type = doc.createElement('LUNType')
+ lun_type_text = doc.createTextNode('Thick')
+ lun_type.appendChild(lun_type_text)
+ lun.appendChild(lun_type)
+
timeout = doc.createElement('Timeout')
timeout_text = doc.createTextNode('43200')
timeout.appendChild(timeout_text)
CAPACITY_UNIT = 1024.0 / 1024.0 / 2
DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30
DEFAULT_WAIT_INTERVAL = 5
+
+MIGRATION_WAIT_INTERVAL = 5
+MIGRATION_FAULT = '74'
+MIGRATION_COMPLETE = '76'
+
ERROR_CONNECT_TO_SERVER = -403
ERROR_UNAUTHORIZED_TO_SERVER = -401
SOCKET_TIME_OUT = 720
from oslo_utils import units
from cinder import exception
-from cinder.i18n import _, _LI, _LW
+from cinder.i18n import _, _LE, _LI, _LW
from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.huawei import constants
from cinder.volume.drivers.huawei import rest_client
from cinder.volume.drivers.huawei import smartx
from cinder.volume import utils as volume_utils
+from cinder.volume import volume_types
from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
CONF.register_opts(huawei_opt)
-class HuaweiBaseDriver(driver.VolumeDriver):
+class HuaweiBaseDriver(driver.MigrateVD, driver.BaseVD):
def __init__(self, *args, **kwargs):
super(HuaweiBaseDriver, self).__init__(*args, **kwargs)
# Create LUN on the array.
lun_info = self.restclient.create_volume(lun_param)
lun_id = lun_info['ID']
- qos = huawei_utils.get_volume_qos(volume)
- if qos:
- smart_qos = smartx.SmartQos(self.restclient)
- smart_qos.create_qos(qos, lun_id)
- smartpartition = smartx.SmartPartition(self.restclient)
- smartpartition.add(opts, lun_id)
- smartcache = smartx.SmartCache(self.restclient)
- smartcache.add(opts, lun_id)
+
+ try:
+ qos = huawei_utils.get_volume_qos(volume)
+ if qos:
+ smart_qos = smartx.SmartQos(self.restclient)
+ smart_qos.create_qos(qos, lun_id)
+ smartpartition = smartx.SmartPartition(self.restclient)
+ smartpartition.add(opts, lun_id)
+
+ smartcache = smartx.SmartCache(self.restclient)
+ smartcache.add(opts, lun_id)
+ except Exception as err:
+ if lun_id:
+ self._delete_lun_with_check(lun_id)
+ raise exception.InvalidInput(
+ reason=_('Create volume error. Because %s.') % err)
return {'provider_location': lun_info['ID'],
'ID': lun_id,
lun_list,
qos_id)
+ def _delete_lun_with_check(self, dst_id):
+ LOG.info(_LI("Try to delete lun %s if it exists."), dst_id)
+ if self.restclient.check_lun_exist(dst_id):
+ qos_id = self.restclient.get_qosid_by_lunid(dst_id)
+ if qos_id:
+ qos = smartx.SmartQos(self.restclient)
+ qos.delete_qos(qos_id)
+ self.restclient.delete_lun(dst_id)
+
+ def _is_lun_migration_complete(self, src_id, dst_id):
+ result = self.restclient.get_lun_migration_task()
+ found_migration_task = False
+ if 'data' in result:
+ for item in result['data']:
+ if (src_id == item['PARENTID']
+ and dst_id == item['TARGETLUNID']):
+ found_migration_task = True
+ if constants.MIGRATION_COMPLETE == item['RUNNINGSTATUS']:
+ return True
+ if constants.MIGRATION_FAULT == item['RUNNINGSTATUS']:
+ err_msg = (_('Lun migration error.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+ if not found_migration_task:
+ err_msg = (_("Cannot find migration task."))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ return False
+
+ def _is_lun_migration_exist(self, src_id, dst_id):
+ try:
+ result = self.restclient.get_lun_migration_task()
+ except Exception:
+ LOG.error(_LE("Get LUN migration error."))
+ return False
+ if 'data' in result:
+ for item in result['data']:
+ if (src_id == item['PARENTID']
+ and dst_id == item['TARGETLUNID']):
+ return True
+ return False
+
+ def _migrate_lun(self, src_id, dst_id):
+ try:
+ self.restclient.create_lun_migration(src_id, dst_id)
+
+ def _is_lun_migration_complete():
+ return self._is_lun_migration_complete(src_id, dst_id)
+
+ wait_interval = constants.MIGRATION_WAIT_INTERVAL
+ huawei_utils.wait_for_condition(self.xml_file_path,
+ _is_lun_migration_complete,
+ wait_interval)
+ # Clean up if migration failed.
+ except Exception as ex:
+ raise exception.VolumeBackendAPIException(data=ex)
+ finally:
+ if self._is_lun_migration_exist(src_id, dst_id):
+ self.restclient.delete_lun_migration(src_id, dst_id)
+ self._delete_lun_with_check(dst_id)
+
+ LOG.debug("Migrate lun %s successfully.", src_id)
+ return True
+
+ def _wait_volume_ready(self, lun_id):
+ event_type = 'LUNReadyWaitInterval'
+ wait_interval = huawei_utils.get_wait_interval(self.xml_file_path,
+ event_type)
+
+ def _volume_ready():
+ result = self.restclient.get_lun_info(lun_id)
+ if (result['HEALTHSTATUS'] == constants.STATUS_HEALTH
+ and result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY):
+ return True
+ return False
+
+ huawei_utils.wait_for_condition(self.xml_file_path,
+ _volume_ready,
+ wait_interval,
+ wait_interval * 10)
+
+ def _get_original_status(self, volume):
+ if not volume['volume_attachment']:
+ return 'available'
+ else:
+ return 'in-use'
+
+ def update_migrated_volume(self, ctxt, volume, new_volume,
+ original_volume_status):
+ original_name = huawei_utils.encode_name(volume['id'])
+ current_name = huawei_utils.encode_name(new_volume['id'])
+
+ lun_id = self.restclient.get_volume_by_name(current_name)
+ try:
+ self.restclient.rename_lun(lun_id, original_name)
+ except exception.VolumeBackendAPIException:
+ LOG.error(_LE('Unable to rename lun %s on array.'), current_name)
+ return {'_name_id': new_volume['_name_id'] or new_volume['id']}
+
+ LOG.debug("Rename lun from %(current_name)s to %(original_name)s "
+ "successfully.",
+ {'current_name': current_name,
+ 'original_name': original_name})
+
+ model_update = {'_name_id': None}
+
+ return model_update
+
+ def migrate_volume(self, ctxt, volume, host, new_type=None):
+ """Migrate a volume within the same array."""
+ return self._migrate_volume(volume, host, new_type)
+
+ def _check_migration_valid(self, host, volume):
+ if 'pool_name' not in host['capabilities']:
+ return False
+
+ target_device = host['capabilities']['location_info']
+
+ # Source and destination should be on same array.
+ if target_device != self.restclient.device_id:
+ return False
+
+ # Same protocol should be used if volume is in-use.
+ protocol = huawei_utils.get_protocol(self.xml_file_path)
+ if (host['capabilities']['storage_protocol'] != protocol
+ and self._get_original_status(volume) == 'in-use'):
+ return False
+
+ pool_name = host['capabilities']['pool_name']
+ if len(pool_name) == 0:
+ return False
+
+ return True
+
+ def _migrate_volume(self, volume, host, new_type=None):
+ if not self._check_migration_valid(host, volume):
+ return (False, None)
+
+ type_id = volume['volume_type_id']
+
+ volume_type = None
+ if type_id:
+ volume_type = volume_types.get_volume_type(None, type_id)
+
+ pool_name = host['capabilities']['pool_name']
+ pools = self.restclient.find_all_pools()
+ pool_info = self.restclient.find_pool_info(pool_name, pools)
+ src_volume_name = huawei_utils.encode_name(volume['id'])
+ dst_volume_name = six.text_type(hash(src_volume_name))
+ src_id = volume.get('provider_location', None)
+
+ src_lun_params = self.restclient.get_lun_info(src_id)
+
+ opts = None
+ qos = None
+ if new_type:
+ # If new type exists, use new type.
+ opts = huawei_utils._get_extra_spec_value(
+ new_type['extra_specs'])
+ opts = smartx.SmartX().get_smartx_specs_opts(opts)
+ if 'LUNType' not in opts:
+ opts['LUNType'] = huawei_utils.find_luntype_in_xml(
+ self.xml_file_path)
+
+ qos = huawei_utils.get_qos_by_volume_type(new_type)
+ elif volume_type:
+ qos = huawei_utils.get_qos_by_volume_type(volume_type)
+
+ if not opts:
+ opts = huawei_utils.get_volume_params(volume)
+ opts = smartx.SmartX().get_smartx_specs_opts(opts)
+
+ lun_info = self._create_lun_with_extra_feature(pool_info,
+ dst_volume_name,
+ src_lun_params,
+ opts)
+ lun_id = lun_info['ID']
+
+ if qos:
+ LOG.info(_LI('QoS: %s.'), qos)
+ SmartQos = smartx.SmartQos(self.restclient)
+ SmartQos.create_qos(qos, lun_id)
+ if opts:
+ smartpartition = smartx.SmartPartition(self.restclient)
+ smartpartition.add(opts, lun_id)
+ smartcache = smartx.SmartCache(self.restclient)
+ smartcache.add(opts, lun_id)
+
+ dst_id = lun_info['ID']
+ self._wait_volume_ready(dst_id)
+ moved = self._migrate_lun(src_id, dst_id)
+
+ return moved, {}
+
+ def _create_lun_with_extra_feature(self, pool_info,
+ lun_name,
+ lun_params,
+ spec_opts):
+ LOG.info(_LI('Create a new lun %s for migration.'), lun_name)
+
+ # Prepare lun parameters.
+ lunparam = {"TYPE": '11',
+ "NAME": lun_name,
+ "PARENTTYPE": '216',
+ "PARENTID": pool_info['ID'],
+ "ALLOCTYPE": lun_params['ALLOCTYPE'],
+ "CAPACITY": lun_params['CAPACITY'],
+ "WRITEPOLICY": lun_params['WRITEPOLICY'],
+ "MIRRORPOLICY": lun_params['MIRRORPOLICY'],
+ "PREFETCHPOLICY": lun_params['PREFETCHPOLICY'],
+ "PREFETCHVALUE": lun_params['PREFETCHVALUE'],
+ "DATATRANSFERPOLICY": '0',
+ "READCACHEPOLICY": lun_params['READCACHEPOLICY'],
+ "WRITECACHEPOLICY": lun_params['WRITECACHEPOLICY'],
+ "OWNINGCONTROLLER": lun_params['OWNINGCONTROLLER'],
+ }
+ if 'LUNType' in spec_opts:
+ lunparam['ALLOCTYPE'] = spec_opts['LUNType']
+ if spec_opts['policy']:
+ lunparam['DATATRANSFERPOLICY'] = spec_opts['policy']
+
+ lun_info = self.restclient.create_volume(lunparam)
+ return lun_info
+
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot.
def _volume_ready():
result = self.restclient.get_lun_info(tgt_lun_id)
- if result['HEALTHSTATUS'] == constants.STATUS_HEALTH:
- if result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY:
- return True
+ if (result['HEALTHSTATUS'] == constants.STATUS_HEALTH
+ and result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY):
+ return True
return False
huawei_utils.wait_for_condition(self.xml_file_path,
self._copy_volume(volume, luncopy_name,
snapshot_id, tgt_lun_id)
- return {'provider_location': lun_info['ID'],
+ return {'ID': lun_info['ID'],
'lun_info': lun_info}
def create_cloned_volume(self, volume, src_vref):
"""Clone a new volume from an existing volume."""
# Form the snapshot structure.
- snapshot = {'id': uuid.uuid4().__str__(), 'volume_id': src_vref['id']}
+ snapshot = {'id': uuid.uuid4().__str__(),
+ 'volume_id': src_vref['id'],
+ 'volume': src_vref}
# Create snapshot.
self.create_snapshot(snapshot)
return info
- def migrate_volume(self, context, volume, host):
- return (False, None)
-
def create_export(self, context, volume, connector):
"""Export a volume."""
pass
Multiple pools support
ISCSI multipath support
SmartX support
+ Volume migration support
"""
VERSION = "1.1.1"
1.1.1 - Code refactor
Multiple pools support
SmartX support
+ Volume migration support
"""
VERSION = "1.1.1"
data['volume_backend_name'] = backend_name or self.__class__.__name__
data['storage_protocol'] = 'FC'
data['driver_version'] = self.VERSION
- data['verdor_name'] = 'Huawei'
+ data['vendor_name'] = 'Huawei'
return data
@fczm_utils.AddFCZone
if key:
key = key.lower()
+ if ((not scope or scope == 'capabilities') and
+ key in opts_capability):
+ words = value.split()
+
+ if not (words and len(words) == 2 and words[0] == '<is>'):
+ LOG.error(_LE("Capabilities value must be specified as "
+ "'<is> True' or '<is> true'."))
+ else:
+ del words[0]
+ value = words[0]
+ opts[key] = value.lower()
+
if (scope in opts_capability) and (key in opts_value):
if (scope in opts_associate) and (opts_associate[scope] == key):
opts[key] = value
def get_lun_params(xml_file_path, smartx_opts):
- lunsetinfo = {}
lunsetinfo = get_lun_conf_params(xml_file_path)
lunsetinfo = _get_smartx_specs_params(lunsetinfo, smartx_opts)
return lunsetinfo
lunsetinfo['LUNType'] = luntype.strip()
if luntype.strip() == 'Thick':
lunsetinfo['LUNType'] = 0
- if luntype.strip() == 'Thin':
+ elif luntype.strip() == 'Thin':
lunsetinfo['LUNType'] = 1
- elif luntype is not '' and luntype is not None:
+ else:
err_msg = (_(
- 'Config file is wrong. LUNType must be "Thin"'
- ' or "Thick". LUNType: %(fetchtype)s.')
+ "LUNType config is wrong. LUNType must be 'Thin'"
+ " or 'Thick'. LUNType: %(fetchtype)s.")
% {'fetchtype': luntype})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
+ else:
+ lunsetinfo['LUNType'] = 0
stripunitsize = root.findtext('LUN/StripUnitSize')
if stripunitsize is not None:
return lunsetinfo
+def find_luntype_in_xml(xml_file_path):
+ root = parse_xml_file(xml_file_path)
+ luntype = root.findtext('LUN/LUNType')
+ if luntype:
+ if luntype.strip() in ['Thick', 'Thin']:
+ if luntype.strip() == 'Thick':
+ luntype = 0
+ elif luntype.strip() == 'Thin':
+ luntype = 1
+ else:
+ err_msg = (_(
+ "LUNType config is wrong. LUNType must be 'Thin'"
+ " or 'Thick'. LUNType: %(fetchtype)s.")
+ % {'fetchtype': luntype})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+ else:
+ luntype = 0
+ return luntype
+
+
def encode_name(name):
uuid_str = name.replace("-", "")
vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)
"""Get iSCSI info from config file."""
iscsiinfo = {}
root = parse_xml_file(xml_file_path)
- TargetIP = root.findtext('iSCSI/DefaultTargetIP').strip()
- iscsiinfo['DefaultTargetIP'] = TargetIP
+ target_ip = root.findtext('iSCSI/DefaultTargetIP').strip()
+ iscsiinfo['DefaultTargetIP'] = target_ip
initiator_list = []
for dic in root.findall('iSCSI/Initiator'):
volume_size = int(volume['size']) * units.Gi / 512
return volume_size
+
+
+def get_protocol(xml_file_path):
+ """Get protocol from huawei conf file."""
+ root = parse_xml_file(xml_file_path)
+ protocol = root.findtext('Storage/Protocol')
+ if not protocol:
+ err_msg = (_('Get protocol from huawei conf file error.'))
+ LOG.error(err_msg)
+ raise exception.InvalidInput(reason=err_msg)
+
+ return protocol
LOG.error(_LE('Bad response from server: %(url)s.'
' Error: %(err)s'), {'url': url, 'err': err})
json_msg = ('{"error":{"code": %s,"description": "Connect to '
- 'server error."}}'), constants.ERROR_CONNECT_TO_SERVER
+ 'server error."}}') % constants.ERROR_CONNECT_TO_SERVER
res_json = json.loads(json_msg)
return res_json
raise exception.VolumeBackendAPIException(data=msg)
device_id = result['data']['deviceid']
+ self.device_id = device_id
self.url = item_url + device_id
self.headers['iBaseToken'] = result['data']['iBaseToken']
return device_id
return result['data']
def check_snapshot_exist(self, snapshot_id):
- url = self.url + "/snapshot/" + snapshot_id
+ url = self.url + "/snapshot/%s" % snapshot_id
data = json.dumps({"TYPE": "27",
"ID": snapshot_id})
result = self.call(url, data, "GET")
if hostgroup_id is None:
err_msg = (_(
'Failed to create hostgroup: %(name)s. '
- 'Check if it exists on the array.'),
- {'name': hostgroup_name})
+ 'Check if it exists on the array.')
+ % {'name': hostgroup_name})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
if 'data' in result:
return result['data']['ID']
- else:
- return
def _is_host_associate_to_hostgroup(self, hostgroup_id, host_id):
"""Check whether the host is associated to the hostgroup."""
result = self.call(url, None, "GET")
self._assert_rest_result(result, _('Find lun group from mapping view '
'error.'))
+ lungroup_id = None
+ if 'data' in result:
+ # One map can have only one lungroup.
+ for item in result['data']:
+ lungroup_id = item['ID']
- return self._get_id_from_result(result, view_id, 'ID')
+ return lungroup_id
def start_luncopy(self, luncopy_id):
"""Start a LUNcopy."""
iqn = iqn_prefix + ':' + iqn_suffix + ':' + iscsi_ip
LOG.info(_LI('_get_tgt_iqn: iSCSI target iqn is: %s.'), iqn)
return iqn
- else:
- return
def get_fc_target_wwpns(self, wwn):
url = (self.url +
capacity = self._get_capacity(pool_name, result)
pool = {}
pool.update(dict(
+ location_info=self.device_id,
pool_name=pool_name,
total_capacity_gb=capacity['total_capacity'],
free_capacity_gb=capacity['free_capacity'],
target_iqn = self._get_tgt_iqn_from_rest(ip)
if not target_iqn:
target_iqn = self._get_tgt_iqn(ip)
+ if target_iqn:
target_iqns.append(target_iqn)
return (target_iqns, target_ips, portgroup_id)
LOG.warning(_LW("Can't find target iqn from rest."))
return target_iqn
- target_iqn = self._get_id_from_result(result, target_ip, 'ID')
+ if 'data' in result:
+ for item in result['data']:
+ if target_ip in item['ID']:
+ target_iqn = item['ID']
if not target_iqn:
LOG.warning(_LW("Can't find target iqn from rest."))
result = self.call(url, None, "GET")
self._assert_rest_result(result, _('Get lungroup id by lun id error.'))
- return self._get_id_from_result(result, lun_id, 'ID')
+ lun_group_id = None
+ # Lun only in one lungroup.
+ if 'data' in result:
+ for item in result['data']:
+ lun_group_id = item['ID']
+
+ return lun_group_id
def get_lun_info(self, lun_id):
url = self.url + "/lun/" + lun_id
return result['data']
+ def create_lun_migration(self, src_id, dst_id, speed=2):
+ url = self.url + "/LUN_MIGRATION"
+ data = json.dumps({"TYPE": '253',
+ "PARENTID": src_id,
+ "TARGETLUNID": dst_id,
+ "SPEED": speed,
+ "WORKMODE": 0})
+
+ result = self.call(url, data, "POST")
+ msg = _('Create lun migration error.')
+ self._assert_rest_result(result, msg)
+ self._assert_data_in_result(result, msg)
+
+ def get_lun_migration_task(self):
+ url = self.url + '/LUN_MIGRATION?range=[0-100]'
+ result = self.call(url, None, "GET")
+ self._assert_rest_result(result, _('Get lun migration task error.'))
+ return result
+
+ def delete_lun_migration(self, src_id, dst_id):
+ url = self.url + '/LUN_MIGRATION/' + src_id
+ result = self.call(url, None, "DELETE")
+ msg = _('Delete lun migration error.')
+ self._assert_rest_result(result, msg)
+ self._assert_data_in_result(result, msg)
+
def get_partition_id_by_name(self, name):
url = self.url + "/cachepartition"
result = self.call(url, None, "GET")
if name == item['NAME']:
return item['ID']
- return
-
def get_partition_info_by_id(self, partition_id):
url = self.url + '/cachepartition/' + partition_id
for item in result['data']:
if name == item['NAME']:
return item['ID']
- return
def find_available_qos(self, qos):
""""Find available QoS on the array."""
"ID": initiator})
result = self.call(url, data, "PUT")
self._assert_rest_result(result, _('Remove iscsi from host error.'))
+
+ def rename_lun(self, lun_id, new_name):
+ url = self.url + "/lun/" + lun_id
+ data = json.dumps({"NAME": new_name})
+ result = self.call(url, data, "PUT")
+ msg = _('Rename lun on array error.')
+ self._assert_rest_result(result, msg)
+ self._assert_data_in_result(result, msg)