fix 'huawei dorado v6' create new volume from snapshot error

change the luncopy mode,in dorado v6 we use clone_pair
modified some interface to call dorado v6

Closes-Bug: #1942342
Change-Id: If26ebd6a94512b1d8ecc1b786f518f667fc19ffe
This commit is contained in:
bingyan 2021-11-30 18:01:08 +08:00
parent 97aeffa9f5
commit 77da18785a
6 changed files with 245 additions and 42 deletions

View File

@ -2087,6 +2087,61 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate/portgroup?TYPE=245&ASSOC'
'IATEOBJTYPE=257&ASSOCIATEOBJID=1114113/GET'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
FAKE_CLONEPAIR_PAIRID_GET_RESPONSE = """
{
"data":{
"name": "fake_clonepair_name",
"copyStatus": "0",
"syncStatus": "2"
},
"error": {
"code": 0,
"description": "0"
}
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/clonepair/fake_clonepair_id/GET'] = (
FAKE_CLONEPAIR_PAIRID_GET_RESPONSE)
FAKE_CLONEPAIR_PAIRID_DELETE_RESPONSE = """
{
"data":{
"name": "fake_clonepair_name"
},
"error": {
"code": 0,
"description": "0"
}
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/clonepair/fake_clonepair_id/DELETE'] = (
FAKE_CLONEPAIR_PAIRID_DELETE_RESPONSE)
FAKE_CLONEPAIR_RELATION_RESPONSE = """
{
"data":{
"ID": "fake_clonepair_id"
},
"error": {
"code": 0,
"description": ""
}
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/clonepair/relation/POST'] = (
FAKE_CLONEPAIR_RELATION_RESPONSE)
FAKE_CLONEPAIR_SYNCHRONIZE_RESPONSE = """
{
"data":{},
"error": {
"code": 0,
"description": ""
}
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/clonepair/synchronize/PUT'] = (
FAKE_CLONEPAIR_SYNCHRONIZE_RESPONSE)
REPLICA_BACKEND_ID = 'huawei-replica-1'
@ -2235,6 +2290,7 @@ class FakeISCSIStorage(huawei_driver.HuaweiISCSIDriver):
self.active_backend_id = None
self.replica = None
self.support_func = None
self.is_dorado_v6 = False
def do_setup(self):
self.metro_flag = True
@ -2262,6 +2318,7 @@ class FakeFCStorage(huawei_driver.HuaweiFCDriver):
self.active_backend_id = None
self.replica = None
self.support_func = None
self.is_dorado_v6 = False
def do_setup(self):
self.metro_flag = True
@ -2839,12 +2896,18 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase):
def test_delete_snapshot_success(self):
self.driver.delete_snapshot(self.snapshot)
def test_create_volume_from_snapsuccess(self):
@ddt.data(True, False)
def test_create_volume_from_snapsuccess(self, is_dorado_v6):
self.mock_object(
huawei_utils,
'get_volume_params',
return_value={'replication_enabled': True})
self.mock_object(replication.ReplicaCommonDriver, 'sync')
self.mock_object(self.driver.client, 'get_snapshot_info_by_name',
return_value=
{'ID': ID, 'RUNNINGSTATUS': constants.STATUS_ACTIVE})
self.configuration.lun_copy_speed = 2
self.driver.is_dorado_v6 = is_dorado_v6
model_update = self.driver.create_volume_from_snapshot(self.volume,
self.snapshot)
expect_value = {"huawei_lun_id": "1",
@ -4619,7 +4682,13 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
def test_delete_snapshot_success(self):
self.driver.delete_snapshot(self.snapshot)
def test_create_volume_from_snapsuccess(self):
@ddt.data(True, False)
def test_create_volume_from_snapsuccess(self, is_dorado_v6):
self.configuration.lun_copy_speed = 2
self.driver.is_dorado_v6 = is_dorado_v6
self.mock_object(self.driver.client, 'get_snapshot_info_by_name',
return_value=
{'ID': ID, 'RUNNINGSTATUS': constants.STATUS_ACTIVE})
lun_info = self.driver.create_volume_from_snapshot(self.volume,
self.snapshot)
expect_value = {"huawei_lun_id": "1",
@ -5353,10 +5422,10 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
@ddt.data(
([fake_snapshot.fake_snapshot_obj(
None, provider_location=SNAP_PROVIDER_LOCATION, id=ID)],
[], False),
None, provider_location=SNAP_PROVIDER_LOCATION, id=ID,
volume_size=1)], [], False),
([], [fake_volume.fake_volume_obj(
None, provider_location=PROVIDER_LOCATION, id=ID)], True),
None, provider_location=PROVIDER_LOCATION, id=ID, size=1)], True),
)
@ddt.unpack
def test_create_group_from_src(self, snapshots, source_vols, tmp_snap):
@ -5372,7 +5441,8 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
self.driver, '_delete_group_snapshot',
wraps=self.driver._delete_group_snapshot)
self.mock_object(self.driver.client, 'get_snapshot_info_by_name',
return_value={'ID': ID})
return_value=
{'ID': ID, 'RUNNINGSTATUS': constants.STATUS_ACTIVE})
model_update, volumes_model_update = self.driver.create_group_from_src(
None, self.group, [self.volume], snapshots=snapshots,

View File

@ -71,7 +71,7 @@ huawei_opts = [
CONF = cfg.CONF
CONF.register_opts(huawei_opts, group=configuration.SHARED_CONF_GROUP)
snap_attrs = ('id', 'volume_id', 'volume', 'provider_location')
snap_attrs = ('id', 'volume_id', 'volume', 'provider_location', 'volume_size')
Snapshot = collections.namedtuple('Snapshot', snap_attrs)
vol_attrs = ('id', 'lun_type', 'provider_location', 'metadata')
Volume = collections.namedtuple('Volume', vol_attrs)
@ -96,6 +96,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
self.support_func = None
self.metro_flag = False
self.replica = None
self.is_dorado_v6 = False
@staticmethod
def get_driver_options():
@ -144,6 +145,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
self.client = rest_client.RestClient(self.configuration,
**client_conf)
self.client.login()
self.is_dorado_v6 = huawei_utils.is_support_clone_pair(self.client)
# init remote client
metro_san_address = self.configuration.safe_get("metro_san_address")
@ -225,8 +227,9 @@ class HuaweiBaseDriver(driver.VolumeDriver):
return volume_type
def _get_lun_params(self, volume, opts):
def _get_lun_params(self, volume, opts, src_size=None):
pool_name = volume_utils.extract_host(volume.host, level='pool')
params = {
'TYPE': '11',
'NAME': huawei_utils.encode_name(volume.id),
@ -234,7 +237,8 @@ class HuaweiBaseDriver(driver.VolumeDriver):
'PARENTID': self.client.get_pool_id(pool_name),
'DESCRIPTION': volume.name,
'ALLOCTYPE': opts.get('LUNType', self.configuration.lun_type),
'CAPACITY': int(volume.size) * constants.CAPACITY_UNIT,
'CAPACITY': int(int(src_size) * constants.CAPACITY_UNIT if src_size
else int(volume.size) * constants.CAPACITY_UNIT),
'READCACHEPOLICY': self.configuration.lun_read_cache_policy,
'WRITECACHEPOLICY': self.configuration.lun_write_cache_policy,
}
@ -261,12 +265,15 @@ class HuaweiBaseDriver(driver.VolumeDriver):
return lun_info, model_update
def _create_base_type_volume(self, opts, volume):
def _create_base_type_volume(self, opts, volume, src_size=None):
"""Create volume and add some base type.
Base type is the service type which doesn't conflict with the other.
"""
lun_params = self._get_lun_params(volume, opts)
if self.is_dorado_v6:
lun_params = self._get_lun_params(volume, opts, src_size)
else:
lun_params = self._get_lun_params(volume, opts)
lun_info, model_update = self._create_volume(lun_params)
lun_id = lun_info['ID']
@ -623,30 +630,10 @@ class HuaweiBaseDriver(driver.VolumeDriver):
return moved, {}
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot.
We use LUNcopy to copy a new volume from snapshot.
The time needed increases as volume size does.
"""
opts = huawei_utils.get_volume_params(volume)
if opts.get('hypermetro') and opts.get('replication_enabled'):
msg = _("Hypermetro and Replication can not be "
"used in the same volume_type.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
snapshot_info = huawei_utils.get_snapshot_info(self.client, snapshot)
if not snapshot_info:
msg = _('create_volume_from_snapshot: Snapshot %(name)s '
'does not exist.') % {'name': snapshot.id}
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
snapshot_id = snapshot_info['ID']
lun_params, lun_info, model_update = self._create_base_type_volume(
opts, volume)
def _create_volume_wait_ready(self, opts, volume, snapshot_id,
src_size=None):
lun_params, lun_info, model_update = \
self._create_base_type_volume(opts, volume, src_size)
tgt_lun_id = lun_info['ID']
luncopy_name = huawei_utils.encode_name(volume.id)
@ -670,12 +657,86 @@ class HuaweiBaseDriver(driver.VolumeDriver):
huawei_utils.wait_for_condition(_volume_ready,
wait_interval,
wait_interval * 10)
return lun_params, lun_info, model_update
self._copy_volume(volume, luncopy_name,
snapshot_id, tgt_lun_id)
def _create_clone_pair(self, source_id, target_id, clone_speed):
clone_pair_id = self.client.create_clone_pair(
source_id, target_id, clone_speed)
def _pair_sync_completed():
clone_pair_info = self.client.get_clone_pair_info(clone_pair_id)
if clone_pair_info['copyStatus'] != constants.CLONE_STATUS_HEALTH:
msg = _("ClonePair %s is abnormal.") % clone_pair_id
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return (clone_pair_info['syncStatus'] in
constants.CLONE_STATUS_COMPLETE)
self.client.sync_clone_pair(clone_pair_id)
huawei_utils.wait_for_condition(
_pair_sync_completed, self.configuration.lun_copy_wait_interval,
self.configuration.lun_timeout)
self.client.delete_clone_pair(clone_pair_id)
def _create_volume_from_snapshot(self, volume, snapshot, opts,
clone_pair_flag=None):
snapshot_info = huawei_utils.get_snapshot_info(self.client, snapshot)
if not snapshot_info:
msg = _('create_volume_from_snapshot: Snapshot %(name)s '
'does not exist.') % {'name': snapshot.id}
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
snapshot_id = snapshot_info['ID']
if snapshot_info.get("RUNNINGSTATUS") != constants.STATUS_ACTIVE:
msg = _("Failed to create volume from snapshot duw to"
"snapshot %s is not activate.") % snapshot_id
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
expect_size = int(int(volume.size) * constants.CAPACITY_UNIT)
lun_params, lun_info, model_update = \
self._create_volume_wait_ready(opts, volume, snapshot_id,
src_size=snapshot.volume_size)
tgt_lun_id = lun_info['ID']
luncopy_name = huawei_utils.encode_name(volume.id)
if clone_pair_flag:
clone_speed = self.configuration.lun_copy_speed
self._create_clone_pair(snapshot_id, tgt_lun_id, clone_speed)
else:
self._copy_volume(volume, luncopy_name,
snapshot_id, tgt_lun_id)
try:
if int(lun_info['CAPACITY']) < expect_size:
self.client.extend_lun(lun_info["ID"], expect_size)
lun_info = self.client.get_lun_info(lun_info["ID"])
lun_params.update({"CAPACITY": expect_size})
except Exception as err:
LOG.exception('Extend lun %(lun_id)s error. Reason is %(err)s',
{"lun_id": lun_info['ID'], "err": err})
self._delete_lun_with_check(lun_info['ID'])
raise
return lun_params, lun_info, model_update
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot.
We use LUNcopy to copy a new volume from snapshot.
The time needed increases as volume size does.
For Dorado V6 we use clone_pair
"""
opts = huawei_utils.get_volume_params(volume)
if opts.get('hypermetro') and opts.get('replication_enabled'):
msg = _("Hypermetro and Replication can not be "
"used in the same volume_type.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
lun_params, lun_info, model_update = \
self._create_volume_from_snapshot(volume, snapshot, opts,
self.is_dorado_v6)
# NOTE(jlc): Actually, we just only support replication here right
# now, not hypermetro.
model_update = self._add_extend_type_to_volume(opts, lun_params,
lun_info, model_update)
model_update['provider_location'] = huawei_utils.to_string(
@ -692,6 +753,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
snapshot = Snapshot(id=uuid.uuid4().__str__(),
volume_id=src_vref.id,
volume=src_vref,
volume_size=src_vref.size,
provider_location=None)
# Create snapshot.
@ -1556,7 +1618,8 @@ class HuaweiBaseDriver(driver.VolumeDriver):
'provider_location': src_vol.provider_location,
}
snapshot_kwargs = {'id': six.text_type(uuid.uuid4()),
'volume': objects.Volume(**vol_kwargs)}
'volume': objects.Volume(**vol_kwargs),
'volume_size': src_vol.size}
snapshot = objects.Snapshot(**snapshot_kwargs)
snapshots.append(snapshot)

View File

@ -133,3 +133,8 @@ LUN_COPY_SPEED_TYPES = (
) = ('1', '2', '3', '4')
MAX_QUERY_COUNT = 100
CLONE_STATUS_HEALTH = '0'
CLONE_STATUS_COMPLETE = (CLONE_COMPLETE,) = ('2',)
CLONE_PAIR_NOT_EXIST = "1073798147"
SUPPORT_CLONE_PAIR_VERSION = "V600R003C00"

View File

@ -64,6 +64,7 @@ class HuaweiConf(object):
self._lun_type,
self._lun_ready_wait_interval,
self._lun_copy_wait_interval,
self._lun_copy_speed,
self._lun_timeout,
self._lun_write_type,
self._lun_prefetch,
@ -98,7 +99,7 @@ class HuaweiConf(object):
need_encode = True
if need_encode:
tree.write(self.conf.cinder_huawei_conf_file, 'UTF-8')
tree.write(self.conf.cinder_huawei_conf_file, encoding='UTF-8')
def _san_address(self, xml_root):
text = xml_root.findtext('Storage/RestURL')
@ -400,3 +401,19 @@ class HuaweiConf(object):
}
setattr(self.conf, 'replication', config)
def _lun_copy_speed(self, xml_root):
text = xml_root.findtext('LUN/LUNCopySpeed')
if text and text.strip() not in constants.LUN_COPY_SPEED_TYPES:
msg = (_("Invalid LUNCopySpeed '%(text)s', LUNCopySpeed must "
"be between %(low)s and %(high)s.")
% {"text": text, "low": constants.LUN_COPY_SPEED_LOW,
"high": constants.LUN_COPY_SPEED_HIGHEST})
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
if not text:
speed = constants.LUN_COPY_SPEED_MEDIUM
else:
speed = text.strip()
setattr(self.conf, 'lun_copy_speed', int(speed))

View File

@ -497,3 +497,10 @@ def get_group_type_params(group):
opt = get_volume_type_params(volume_type)
opts.append(opt)
return opts
def is_support_clone_pair(client):
array_info = client.get_array_info()
version_info = array_info['PRODUCTVERSION']
if version_info >= constants.SUPPORT_CLONE_PAIR_VERSION:
return True

View File

@ -48,6 +48,12 @@ class RestClient(object):
self.url = None
self.device_id = None
if hasattr(requests, 'packages'):
requests.packages.urllib3.disable_warnings(
requests.packages.urllib3.exceptions.InsecureRequestWarning)
requests.packages.urllib3.disable_warnings(
requests.packages.urllib3.exceptions.InsecurePlatformWarning)
def init_http_head(self):
self.url = None
self.session = requests.Session()
@ -1561,7 +1567,7 @@ class RestClient(object):
def extend_lun(self, lun_id, new_volume_size):
url = "/lun/expand"
data = {"TYPE": 11, "ID": lun_id,
"CAPACITY": new_volume_size}
"CAPACITY": int(new_volume_size)}
result = self.call(url, data, 'PUT')
msg = _('Extend volume error.')
@ -2416,3 +2422,38 @@ class RestClient(object):
msg = _('Update luns of qos %s error.') % qos_id
self._assert_rest_result(result, msg)
def create_clone_pair(self, source_id, target_id, clone_speed):
url = "/clonepair/relation"
data = {"copyRate": clone_speed,
"sourceID": source_id,
"targetID": target_id,
"isNeedSynchronize": "0"}
result = self.call(url, data, "POST")
self._assert_rest_result(result, 'Create ClonePair error, source_id '
'is %s.' % source_id)
return result['data']['ID']
def get_clone_pair_info(self, pair_id):
url = "/clonepair/%s" % pair_id
result = self.call(url, None, "GET")
self._assert_rest_result(result, 'Get ClonePair %s error.' % pair_id)
return result.get('data', {})
def sync_clone_pair(self, pair_id):
url = "/clonepair/synchronize"
data = {"ID": pair_id, "copyAction": 0}
result = self.call(url, data, "PUT")
self._assert_rest_result(result, 'Sync ClonePair error, pair is %s.'
% pair_id)
def delete_clone_pair(self, pair_id, delete_dst_lun=False):
data = {"ID": pair_id,
"isDeleteDstLun": delete_dst_lun}
url = "/clonepair/%s" % pair_id
result = self.call(url, data, "DELETE")
if result['error']['code'] == constants.CLONE_PAIR_NOT_EXIST:
LOG.warning('ClonePair %s to delete not exist.', pair_id)
return
self._assert_rest_result(result, 'Delete ClonePair %s error.'
% pair_id)