Huawei: Support huawei consistency group

Adding support for consistency groups in huawei driver.
supports the following interfaces:
1.create_consistencygroup
2.delete_consistencygroup
3.update_consistencygroup
4.create_cgsnapshot
5.delete_cgsnapshot

DocImpact
Implements: blueprint support-huawei-consistency-group
Change-Id: Ib35c382d1cc008f483a60da557781435dde098e1
This commit is contained in:
chenzongliang 2015-12-23 18:04:32 +08:00 committed by huananhuawei
parent af0cddbaea
commit d32d9966b6
6 changed files with 769 additions and 39 deletions

View File

@ -22,6 +22,7 @@ import tempfile
import time
from xml.dom import minidom
from cinder import context
from cinder import exception
from cinder import test
from cinder.tests.unit import utils
@ -35,6 +36,7 @@ from cinder.volume.drivers.huawei import hypermetro
from cinder.volume.drivers.huawei import replication
from cinder.volume.drivers.huawei import rest_client
from cinder.volume.drivers.huawei import smartx
from cinder.volume import volume_types
hypermetro_devices = """{
@ -109,6 +111,9 @@ async_replica_specs = {'replication_enabled': '<is> True',
'replication_type': '<in> async'}
TEST_PAIR_ID = "3400a30d844d0004"
replica_hypermetro_specs = {'hypermetro': '<is> True',
'replication_enabled': '<is> True'}
replication_volume = {
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 2,
@ -141,10 +146,16 @@ test_snap = {
'volume_type_id': None,
'provider_location': '11',
'volume': {'provider_location': '12',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'admin_metadata': {
'huawei_lun_wwn': '6643e8c1004c5f6723e9f454003'}},
}
test_cgsnapshot = {'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'consistencygroup_id': '21ec7341-9256-497b-'
'97d9-ef48edcf0635',
'status': 'available'}
test_host = {'host': 'ubuntu001@backend001#OpenStack_Pool',
'capabilities': {'smartcache': True,
'location_info': '210235G7J20000000000',
@ -205,6 +216,21 @@ test_new_replication_type = {
'description': None,
}
test_hypermetro_type = {
'name': u'new_type',
'qos_specs_id': None,
'deleted': False,
'created_at': None,
'updated_at': None,
'extra_specs': {
'hypermetro': '<is> True'
},
'is_public': True,
'deleted_at': None,
'id': u'550c089b-bfdd-4f7f-86e1-3ba88125555c',
'description': None,
}
hypermetro_devices = """
{
"remote_device": {
@ -216,6 +242,9 @@ hypermetro_devices = """
}
"""
CONSISTGROUP = {'id': "21ec7341-9256-497b-97d9-ef48edcf0635",
'status': "available", }
FAKE_FIND_POOL_RESPONSE = {'CAPACITY': '985661440',
'ID': '0',
'TOTALCAPACITY': '985661440'}
@ -1003,12 +1032,13 @@ FAKE_HYPERMETRODOMAIN_RESPONSE = """
"error":{
"code": 0
},
"data":{
"data":[{
"PRODUCTVERSION": "V100R001C10",
"ID": "11",
"NAME": "hypermetro_test",
"RUNNINGSTATUS": "42"
}
"RUNNINGSTATUS": "1",
"HEALTHSTATUS": "0"
}]
}
"""
@ -1072,7 +1102,7 @@ FAKE_SMARTCACHEPARTITION_RESPONSE = """
}
"""
FAKE_CONNECT_FC_RESPONCE = {
FAKE_CONNECT_FC_RESPONSE = {
"driver_volume_type": 'fibre_channel',
"data": {
"target_wwn": ["10000090fa0d6754"],
@ -1081,7 +1111,15 @@ FAKE_CONNECT_FC_RESPONCE = {
}
}
FAKE_METRO_INFO_RESPONCE = {
FAKE_METRO_INFO_RESPONSE = {
"PRODUCTVERSION": "V100R001C10",
"ID": "11",
"NAME": "hypermetro_test",
"RUNNINGSTATUS": "42",
"HEALTHSTATUS": "0"
}
FAKE_METRO_INFO_NEW_RESPONSE = """{
"error": {
"code": 0
},
@ -1089,9 +1127,85 @@ FAKE_METRO_INFO_RESPONCE = {
"PRODUCTVERSION": "V100R001C10",
"ID": "11",
"NAME": "hypermetro_test",
"RUNNINGSTATUS": "42"
"RUNNINGSTATUS": "1",
"HEALTHSTATUS": "1"
}
}
"""
FAKE_CREATE_METROROUP_RESPONSE = """
{
"data": {
"DESCRIPTION": "",
"DOMAINID": "643e8c4c5f670100",
"DOMAINNAME": "hypermetro-domain",
"HEALTHSTATUS": "1",
"ID": "3400a30d844d8002",
"ISEMPTY": "true",
"NAME": "6F7kdHZcQJ2zbzxHmBl4FQ",
"PRIORITYSTATIONTYPE": "0",
"RECOVERYPOLICY": "1",
"RESOURCETYPE": "11",
"RUNNINGSTATUS": "41",
"SPEED": "2",
"SYNCDIRECTION": "1",
"TYPE": 15364
},
"error": {
"code": 0,
"description": "0"
}
}
"""
FAKE_GET_METROROUP_RESPONSE = {
"data": [{
"DESCRIPTION": "",
"DOMAINID": "643e8c4c5f670100",
"DOMAINNAME": "hypermetro-domain",
"HEALTHSTATUS": "1",
"ID": "11",
"ISEMPTY": "true",
"NAME": huawei_utils.encode_name(test_volume['id']),
"PRIORITYSTATIONTYPE": "0",
"RECOVERYPOLICY": "1",
"RESOURCETYPE": "11",
"RUNNINGSTATUS": "41",
"SPEED": "2",
"SYNCDIRECTION": "1",
"TYPE": 15364
}],
"error": {
"code": 0,
"description": "0"
},
}
FAKE_GET_METROROUP_ID_RESPONSE = """
{
"data": {
"DESCRIPTION": "",
"DOMAINID": "643e8c4c5f670100",
"DOMAINNAME": "hypermetro-domain",
"HEALTHSTATUS": "1",
"ID": "11",
"ISEMPTY": "false",
"NAME": "IexzQZJWSXuX2e9I7c8GNQ",
"PRIORITYSTATIONTYPE": "0",
"RECOVERYPOLICY": "1",
"RESOURCETYPE": "11",
"RUNNINGSTATUS": "1",
"SPEED": "2",
"SYNCDIRECTION": "1",
"TYPE": 15364
},
"error": {
"code": 0,
"description": "0"
}
}
"""
# mock login info map
MAP_COMMAND_TO_FAKE_RESPONSE = {}
@ -1453,24 +1567,33 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroDomain?range=[0-32]/GET'] = (
FAKE_HYPERMETRODOMAIN_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/POST'] = (
FAKE_HYPERMETRODOMAIN_RESPONSE)
FAKE_HYPERMETRO_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/GET'] = (
FAKE_HYPERMETRODOMAIN_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/3400a30d844d0007/GET'] = (
FAKE_METRO_INFO_NEW_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/disable_hcpair/PUT'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/hyperMetro/associate/pair/POST'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/hyperMetro/associate/pair/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/1/GET'] = (
FAKE_HYPERMETRO_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair?range=[0-65535]/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair?range=[0-4095]/GET'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/splitmirror?range=[0-512]/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/synchronize_hcpair/PUT'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/splitmirror?range=[0-8191]/GET'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
FAKE_GET_PORTG_BY_VIEW = """
@ -1659,8 +1782,25 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/fc_port/associate?TYPE=213&ASSOCIATEOBJTYPE='
'257&ASSOCIATEOBJID=0/GET'] = (
FAKE_PORTS_IN_PG_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetro_ConsistentGroup/POST'] = (
FAKE_CREATE_METROROUP_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE["/HyperMetro_ConsistentGroup?type"
"='15364'/GET"] = (
json.dumps(FAKE_GET_METROROUP_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["/HyperMetro_ConsistentGroup/11/GET"] = (
FAKE_GET_METROROUP_ID_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE["/HyperMetro_ConsistentGroup/11/DELETE"] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE["/HyperMetro_ConsistentGroup/stop/PUT"] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE["/HyperMetro_ConsistentGroup/sync/PUT"] = (
FAKE_COMMON_SUCCESS_RESPONSE)
# Replication response
FAKE_GET_REMOTEDEV_RESPONSE = """
{
"data":[{
@ -1677,6 +1817,7 @@ FAKE_GET_REMOTEDEV_RESPONSE = """
}
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/remote_device/GET'] = (
FAKE_GET_REMOTEDEV_RESPONSE)
@ -2291,7 +2432,7 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
def test_get_volume_status(self):
data = self.driver.get_volume_stats()
self.assertEqual('2.0.6', data['driver_version'])
self.assertEqual('2.0.7', data['driver_version'])
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={"CAPACITY": 6291456})
@ -2657,7 +2798,7 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
@mock.patch.object(rest_client.RestClient, 'check_hypermetro_exist',
return_value=True)
@mock.patch.object(rest_client.RestClient, 'get_hypermetro_by_id',
return_value=FAKE_METRO_INFO_RESPONCE)
return_value=FAKE_METRO_INFO_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'delete_hypermetro')
@mock.patch.object(rest_client.RestClient, 'delete_lun',
return_value=None)
@ -3567,6 +3708,50 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
iqn = self.driver.client._get_tgt_iqn_from_rest(ip)
self.assertIsNone(iqn)
def test_create_cgsnapshot(self):
test_snapshots = [test_snap]
ctxt = context.get_admin_context()
model, snapshots = self.driver.create_cgsnapshot(ctxt,
test_cgsnapshot,
test_snapshots)
snapshots_model_update = [{'id': '21ec7341-9256-497b-97d9'
'-ef48edcf0635',
'status': 'available',
'provider_location': 11}]
self.assertEqual(snapshots_model_update, snapshots)
self.assertEqual('available', model['status'])
def test_create_cgsnapshot_create_snapshot_fail(self):
test_snapshots = [test_snap]
ctxt = context.get_admin_context()
self.mock_object(rest_client.RestClient, 'create_snapshot',
mock.Mock(side_effect=(
exception.VolumeBackendAPIException(data='err'))))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cgsnapshot,
ctxt,
test_cgsnapshot,
test_snapshots)
def test_create_cgsnapshot_active_snapshot_fail(self):
test_snapshots = [test_snap]
ctxt = context.get_admin_context()
self.mock_object(rest_client.RestClient, 'activate_snapshot',
mock.Mock(side_effect=(
exception.VolumeBackendAPIException(data='err'))))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cgsnapshot,
ctxt,
test_cgsnapshot,
test_snapshots)
def test_delete_cgsnapshot(self):
test_snapshots = [test_snap]
ctxt = context.get_admin_context()
self.driver.delete_cgsnapshot(ctxt,
test_cgsnapshot,
test_snapshots)
class FCSanLookupService(object):
@ -3685,7 +3870,7 @@ class HuaweiFCDriverTestCase(test.TestCase):
'get_remote_device_by_wwn',
mock.Mock(return_value=remote_device_info))
data = self.driver.get_volume_stats()
self.assertEqual('2.0.5', data['driver_version'])
self.assertEqual('2.0.7', data['driver_version'])
self.assertTrue(data['pools'][0]['replication_enabled'])
self.assertListEqual(['sync', 'async'],
data['pools'][0]['replication_type'])
@ -3702,7 +3887,7 @@ class HuaweiFCDriverTestCase(test.TestCase):
'try_get_remote_wwn',
mock.Mock(return_value={}))
data = self.driver.get_volume_stats()
self.assertEqual('2.0.5', data['driver_version'])
self.assertEqual('2.0.7', data['driver_version'])
self.assertNotIn('replication_enabled', data['pools'][0])
def test_extend_volume(self):
@ -4091,6 +4276,96 @@ class HuaweiFCDriverTestCase(test.TestCase):
lun_info = self.driver.create_volume(hyper_volume)
self.assertEqual(metadata, lun_info['metadata'])
@mock.patch.object(huawei_driver.HuaweiBaseDriver, '_get_volume_params',
return_value=fake_hypermetro_opts)
@mock.patch.object(rest_client.RestClient, 'get_all_pools',
return_value=FAKE_STORAGE_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'get_pool_info',
return_value=FAKE_FIND_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
return_value='11')
@mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
return_value=True)
@mock.patch.object(rest_client.RestClient, 'create_hypermetro')
def test_create_hypermetro_fail(self,
mock_pair_info,
mock_hypermetro_opts,
mock_all_pool_info,
mock_pool_info,
mock_hyper_domain,
mock_volume_ready,
):
mock_pair_info.side_effect = (
exception.VolumeBackendAPIException(data='Error occurred.'))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.metro.create_hypermetro, "11", {})
@mock.patch.object(huawei_driver.huawei_utils, 'get_volume_metadata',
return_value={'hypermetro_id': '3400a30d844d0007',
'remote_lun_id': '1'})
@mock.patch.object(rest_client.RestClient, 'do_mapping',
return_value={'lun_id': '1',
'view_id': '1',
'aval_luns': '[1]'})
def test_hypermetro_connection_success_2(self, mock_map, mock_metadata):
fc_properties = self.driver.metro.connect_volume_fc(test_volume,
FakeConnector)
self.assertEqual(1, fc_properties['data']['target_lun'])
@mock.patch.object(huawei_driver.huawei_utils, 'get_volume_metadata',
return_value={'hypermetro_id': '3400a30d844d0007',
'remote_lun_id': '1'})
def test_terminate_hypermetro_connection_success(self, mock_metradata):
self.driver.metro.disconnect_volume_fc(test_volume, FakeConnector)
@mock.patch.object(huawei_driver.huawei_utils, 'get_volume_metadata',
return_value={'hypermetro_id': '3400a30d844d0007',
'remote_lun_id': None})
@mock.patch.object(rest_client.RestClient, 'get_lun_id_by_name',
return_value=None)
def test_hypermetroid_none_fail(self, mock_metadata, moke_metro_name):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.metro.connect_volume_fc,
test_volume,
FakeConnector)
def test_wait_volume_ready_success(self):
flag = self.driver.metro._wait_volume_ready("11")
self.assertIsNone(flag)
@mock.patch.object(huawei_driver.huawei_utils, 'get_volume_metadata',
return_value={'hypermetro_id': '3400a30d844d0007',
'remote_lun_id': '1'})
@mock.patch.object(rest_client.RestClient, 'get_online_free_wwns',
return_value=[])
@mock.patch.object(rest_client.RestClient, 'get_host_iscsi_initiators',
return_value=[])
def test_hypermetro_connection_fail(self, mock_metadata,
mock_fc_initiator,
mock_host_initiators):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.metro.connect_volume_fc,
test_volume,
FakeConnector)
def test_create_snapshot_fail_hypermetro(self):
self.mock_object(
huawei_driver.HuaweiBaseDriver,
'_get_volume_type',
mock.Mock(return_value={'extra_specs': replica_hypermetro_specs}))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
test_volume, test_snap)
def test_create_snapshot_fail_no_snapshot_id(self):
temp_snap = copy.deepcopy(test_snap)
temp_snap.pop('provider_location')
self.mock_object(rest_client.RestClient, 'get_snapshot_id_by_name',
mock.Mock(return_value=None))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
test_volume, temp_snap)
@mock.patch.object(rest_client.RestClient, 'call',
return_value={"data": [{"RUNNINGSTATUS": "27",
"ID": '1'},
@ -4130,6 +4405,97 @@ class HuaweiFCDriverTestCase(test.TestCase):
res = self.driver.client.is_host_associated_to_hostgroup('1')
self.assertFalse(res)
@mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_consistencygroup_type',
return_value={"hypermetro": "true"})
def test_create_hypermetro_consistencygroup_success(self, mock_grouptype):
"""Test that create_consistencygroup return successfully."""
ctxt = context.get_admin_context()
# Create consistency group
model_update = self.driver.create_consistencygroup(ctxt, CONSISTGROUP)
self.assertEqual('available',
model_update['status'],
"Consistency Group created failed")
@mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_consistencygroup_type',
return_value={"hypermetro": "false"})
def test_create_normal_consistencygroup_success(self,
mock_grouptype):
"""Test that create_consistencygroup return successfully."""
ctxt = context.get_admin_context()
# Create consistency group
model_update = self.driver.create_consistencygroup(ctxt, CONSISTGROUP)
self.assertEqual('available',
model_update['status'],
"Consistency Group created failed")
@mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_consistencygroup_type',
return_value={"hypermetro": "true"})
def test_delete_hypermetro_consistencygroup_success(self, mock_grouptype):
"""Test that create_consistencygroup return successfully."""
test_volumes = [test_volume]
ctxt = context.get_admin_context()
# Create consistency group
model, volumes = self.driver.delete_consistencygroup(ctxt,
CONSISTGROUP,
test_volumes)
self.assertEqual('available',
model['status'],
"Consistency Group created failed")
def test_delete_normal_consistencygroup_success(self):
ctxt = context.get_admin_context()
test_volumes = [test_volume]
self.mock_object(huawei_driver.HuaweiBaseDriver,
'_get_consistencygroup_type',
mock.Mock(return_value={"hypermetro": "false"}))
model, volumes = self.driver.delete_consistencygroup(ctxt,
CONSISTGROUP,
test_volumes)
self.assertEqual('available',
model['status'],
"Consistency Group created failed")
@mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_consistencygroup_type',
return_value={"hypermetro": "true"})
@mock.patch.object(huawei_driver.huawei_utils, 'get_volume_metadata',
return_value={'hypermetro_id': '3400a30d844d0007',
'remote_lun_id': '59'})
def test_update_consistencygroup_success(self,
mock_grouptype,
mock_metadata):
"""Test that create_consistencygroup return successfully."""
ctxt = context.get_admin_context()
add_volumes = [test_volume]
remove_volumes = [test_volume]
# Create consistency group
model_update = self.driver.update_consistencygroup(ctxt,
CONSISTGROUP,
add_volumes,
remove_volumes)
self.assertEqual('available',
model_update[0]['status'],
"Consistency Group update failed")
def test_create_hypermetro_consistencygroup_success_2(self):
ctxt = context.get_admin_context()
# Create consistency group
temp_cg = copy.deepcopy(CONSISTGROUP)
temp_cg['volume_type_id'] = '550c089b-bfdd-4f7f-86e1-3ba88125555c,'
self.mock_object(volume_types, 'get_volume_type',
mock.Mock(return_value=test_hypermetro_type))
model_update = self.driver.create_consistencygroup(ctxt, temp_cg)
self.assertEqual('available',
model_update['status'],
"Consistency Group created failed")
class HuaweiConfTestCase(test.TestCase):
def setUp(self):

View File

@ -48,8 +48,11 @@ ERROR_VOLUME_ALREADY_EXIST = 1077948993
LOGIN_SOCKET_TIMEOUT = 4
ERROR_VOLUME_NOT_EXIST = 1077939726
RELOGIN_ERROR_PASS = [ERROR_VOLUME_NOT_EXIST]
HYPERMETRO_RUNNSTATUS_STOP = 41
HYPERMETRO_RUNNSTATUS_NORMAL = 1
RUNNING_NORMAL = '1'
RUNNING_SYNC = '23'
RUNNING_STOP = '41'
HEALTH_NORMAL = '1'
NO_SPLITMIRROR_LICENSE = 1077950233
NO_MIGRATION_LICENSE = 1073806606

View File

@ -51,6 +51,21 @@ huawei_opts = [
cfg.StrOpt('hypermetro_devices',
default=None,
help='The remote device hypermetro will use.'),
cfg.StrOpt('metro_san_user',
default=None,
help='The remote metro device san user.'),
cfg.StrOpt('metro_san_password',
default=None,
help='The remote metro device san password.'),
cfg.StrOpt('metro_domain_name',
default=None,
help='The remote metro device domain name.'),
cfg.StrOpt('metro_san_address',
default=None,
help='The remote metro device request url.'),
cfg.StrOpt('metro_storage_pools',
default=None,
help='The remote metro device pool names.'),
]
CONF = cfg.CONF
@ -110,14 +125,17 @@ class HuaweiBaseDriver(driver.VolumeDriver):
metro_san_user = self.configuration.safe_get("metro_san_user")
metro_san_password = self.configuration.safe_get("metro_san_password")
if metro_san_address and metro_san_user and metro_san_password:
self.metro_flag = True
metro_san_address = metro_san_address.split(";")
self.rmt_client = rest_client.RestClient(self.configuration,
metro_san_address,
metro_san_user,
metro_san_password)
self.rmt_client.login()
self.rmt_client.login()
self.metro_flag = True
else:
self.metro_flag = False
LOG.warning(_LW("Remote device not configured in cinder.conf"))
# init replication manager
if replica_client_conf:
self.replica_client = rest_client.RestClient(self.configuration,
@ -133,10 +151,8 @@ class HuaweiBaseDriver(driver.VolumeDriver):
def get_volume_stats(self, refresh=False):
"""Get volume status and reload huawei config file."""
self.huawei_conf.update_config_value()
if self.metro_flag:
self.rmt_client.get_all_pools()
stats = self.client.update_volume_stats()
stats = self.update_hypermetro_capability(stats)
if self.replica:
stats = self.replica.update_replica_capability(stats)
@ -146,6 +162,18 @@ class HuaweiBaseDriver(driver.VolumeDriver):
return stats
def update_hypermetro_capability(self, stats):
if self.metro_flag:
version = self.client.find_array_version()
rmt_version = self.rmt_client.find_array_version()
if (version >= constants.ARRAY_VERSION
and rmt_version >= constants.ARRAY_VERSION):
for pool in stats['pools']:
pool['hypermetro'] = True
pool['consistencygroup_support'] = True
return stats
def _get_volume_type(self, volume):
volume_type = None
type_id = volume['volume_type_id']
@ -164,6 +192,17 @@ class HuaweiBaseDriver(driver.VolumeDriver):
opts = self._get_volume_params_from_specs(specs)
return opts
def _get_consistencygroup_type(self, group):
specs = {}
opts = {}
type_id = group['volume_type_id'].split(",")
if type_id[0] and len(type_id) == 2:
ctxt = context.get_admin_context()
volume_type = volume_types.get_volume_type(ctxt, type_id[0])
specs = dict(volume_type).get('extra_specs')
opts = self._get_volume_params_from_specs(specs)
return opts
def _get_volume_params_from_specs(self, specs):
"""Return the volume parameters from extra specs."""
opts_capability = {
@ -1480,6 +1519,134 @@ class HuaweiBaseDriver(driver.VolumeDriver):
self.client.is_host_associated_to_hostgroup(host_id)):
self.client.remove_host(host_id)
def create_consistencygroup(self, context, group):
"""Creates a consistencygroup."""
model_update = {'status': 'available'}
opts = self._get_consistencygroup_type(group)
if (opts.get('hypermetro') == 'true'):
metro = hypermetro.HuaweiHyperMetro(self.client,
self.rmt_client,
self.configuration)
metro.create_consistencygroup(group)
return model_update
# Array will create CG at create_cgsnapshot time. Cinder will
# maintain the CG and volumes relationship in the db.
return model_update
def delete_consistencygroup(self, context, group, volumes):
opts = self._get_consistencygroup_type(group)
if opts.get('hypermetro') == 'true':
metro = hypermetro.HuaweiHyperMetro(self.client,
self.rmt_client,
self.configuration)
return metro.delete_consistencygroup(context, group, volumes)
model_update = {}
volumes_model_update = []
model_update.update({'status': group['status']})
for volume_ref in volumes:
try:
self.delete_volume(volume_ref)
volumes_model_update.append(
{'id': volume_ref['id'], 'status': 'deleted'})
except Exception:
volumes_model_update.append(
{'id': volume_ref['id'], 'status': 'error_deleting'})
return model_update, volumes_model_update
def update_consistencygroup(self, context, group,
add_volumes,
remove_volumes):
model_update = {'status': 'available'}
opts = self._get_consistencygroup_type(group)
if opts.get('hypermetro') == 'true':
metro = hypermetro.HuaweiHyperMetro(self.client,
self.rmt_client,
self.configuration)
metro.update_consistencygroup(context, group,
add_volumes,
remove_volumes)
return model_update, None, None
# Array will create CG at create_cgsnapshot time. Cinder will
# maintain the CG and volumes relationship in the db.
return model_update, None, None
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Create cgsnapshot."""
LOG.info(_LI('Create cgsnapshot for consistency group'
': %(group_id)s'),
{'group_id': cgsnapshot['consistencygroup_id']})
model_update = {}
snapshots_model_update = []
added_snapshots_info = []
try:
for snapshot in snapshots:
volume = snapshot.get('volume')
if not volume:
msg = (_("Can't get volume id from snapshot, "
"snapshot: %(id)s") % {"id": snapshot['id']})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
volume_name = huawei_utils.encode_name(volume['id'])
lun_id = self.client.get_lun_id(volume, volume_name)
snapshot_name = huawei_utils.encode_name(snapshot['id'])
snapshot_description = snapshot['id']
info = self.client.create_snapshot(lun_id,
snapshot_name,
snapshot_description)
snapshot_model_update = {'id': snapshot['id'],
'status': 'available',
'provider_location': info['ID']}
snapshots_model_update.append(snapshot_model_update)
added_snapshots_info.append(info)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Create cgsnapshots failed. "
"Cgsnapshot id: %s."), cgsnapshot['id'])
snapshot_ids = [added_snapshot['ID']
for added_snapshot in added_snapshots_info]
try:
self.client.activate_snapshot(snapshot_ids)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Active cgsnapshots failed. "
"Cgsnapshot id: %s."), cgsnapshot['id'])
model_update['status'] = 'available'
return model_update, snapshots_model_update
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Delete consistency group snapshot."""
LOG.info(_LI('Delete cgsnapshot %(snap_id)s for consistency group: '
'%(group_id)s'),
{'snap_id': cgsnapshot['id'],
'group_id': cgsnapshot['consistencygroup_id']})
model_update = {}
snapshots_model_update = []
model_update['status'] = cgsnapshot['status']
for snapshot in snapshots:
try:
self.delete_snapshot(snapshot)
snapshots_model_update.append({'id': snapshot['id'],
'status': 'deleted'})
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Delete cg snapshots failed. "
"Cgsnapshot id: %s"), cgsnapshot['id'])
return model_update, snapshots_model_update
def _classify_volume(self, volumes):
normal_volumes = []
replica_volumes = []
@ -1608,9 +1775,13 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
2.0.3 - Manage/unmanage snapshot support
2.0.5 - Replication V2 support
2.0.6 - Support iSCSI configuration in Replication
2.0.7 - Hypermetro support
Hypermetro consistency group support
Consistency group support
Cgsnapshot support
"""
VERSION = "2.0.6"
VERSION = "2.0.7"
def __init__(self, *args, **kwargs):
super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
@ -1801,9 +1972,13 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
2.0.3 - Manage/unmanage snapshot support
2.0.4 - Balanced FC port selection
2.0.5 - Replication V2 support
2.0.7 - Hypermetro support
Hypermetro consistency group support
Consistency group support
Cgsnapshot support
"""
VERSION = "2.0.5"
VERSION = "2.0.7"
def __init__(self, *args, **kwargs):
super(HuaweiFCDriver, self).__init__(*args, **kwargs)

View File

@ -79,17 +79,10 @@ class HuaweiHyperMetro(object):
remote_lun_id = metadata['remote_lun_id']
if metro_id:
exst_flag = self.client.check_hypermetro_exist(metro_id)
if exst_flag:
metro_info = self.client.get_hypermetro_by_id(metro_id)
metro_status = int(metro_info['data']['RUNNINGSTATUS'])
self.check_metro_need_to_stop(volume)
LOG.debug("Hypermetro status is: %s.", metro_status)
if constants.HYPERMETRO_RUNNSTATUS_STOP != metro_status:
self.client.stop_hypermetro(metro_id)
# Delete hypermetro
self.client.delete_hypermetro(metro_id)
# Delete hypermetro
self.client.delete_hypermetro(metro_id)
# Delete remote lun.
if remote_lun_id and self.rmt_client.check_lun_exist(remote_lun_id):
@ -274,3 +267,100 @@ class HuaweiHyperMetro(object):
def get_hypermetro_stats(self, hypermetro_id):
pass
def create_consistencygroup(self, group):
LOG.info(_LI("Create Consistency Group: %(group)s."),
{'group': group['id']})
group_name = huawei_utils.encode_name(group['id'])
domain_name = self.configuration.metro_domain_name
domain_id = self.client.get_hyper_domain_id(domain_name)
if not domain_name or not domain_id:
msg = _("The domain_name config in cinder.conf is wrong.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self.client.create_metrogroup(group_name, group['id'], domain_id)
def delete_consistencygroup(self, context, group, volumes):
LOG.info(_LI("Delete Consistency Group: %(group)s."),
{'group': group['id']})
model_update = {}
volumes_model_update = []
model_update['status'] = group['status']
metrogroup_id = self.check_consistencygroup_need_to_stop(group)
if metrogroup_id:
self.client.delete_metrogroup(metrogroup_id)
# Deal with the return volumes info
for volume_ref in volumes:
volume_update = {'id': volume_ref['id']}
volume_update['status'] = 'deleted'
volumes_model_update.append(volume_update)
return model_update, volumes_model_update
def update_consistencygroup(self, context, group,
add_volumes, remove_volumes):
LOG.info(_LI("Update Consistency Group: %(group)s. "
"This adds or removes volumes from a CG."),
{'group': group['id']})
model_update = {}
model_update['status'] = group['status']
metrogroup_id = self.check_consistencygroup_need_to_stop(group)
if metrogroup_id:
# Deal with add volumes to CG
for volume in add_volumes:
metro_id = self.check_metro_need_to_stop(volume)
self.client.add_metro_to_metrogroup(metrogroup_id,
metro_id)
# Deal with remove volumes from CG
for volume in remove_volumes:
metro_id = self.check_metro_need_to_stop(volume)
self.client.remove_metro_from_metrogroup(metrogroup_id,
metro_id)
self.client.sync_hypermetro(metro_id)
new_group_info = self.client.get_metrogroup_by_id(metrogroup_id)
is_empty = new_group_info["ISEMPTY"]
if is_empty == 'false':
self.client.sync_metrogroup(metrogroup_id)
# if CG not exist on array
else:
msg = _("The CG does not exist on array.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def check_metro_need_to_stop(self, volume):
metadata = huawei_utils.get_volume_metadata(volume)
metro_id = metadata['hypermetro_id']
metro_existed = self.client.check_hypermetro_exist(metro_id)
if metro_existed:
metro_info = self.client.get_hypermetro_by_id(metro_id)
metro_health_status = metro_info['HEALTHSTATUS']
metro_running_status = metro_info['RUNNINGSTATUS']
if (metro_health_status == constants.HEALTH_NORMAL and
(metro_running_status == constants.RUNNING_NORMAL or
metro_running_status == constants.RUNNING_SYNC)):
self.client.stop_hypermetro(metro_id)
return metro_id
def check_consistencygroup_need_to_stop(self, group):
group_name = huawei_utils.encode_name(group['id'])
metrogroup_id = self.client.get_metrogroup_by_name(group_name)
if metrogroup_id:
metrogroup_info = self.client.get_metrogroup_by_id(metrogroup_id)
health_status = metrogroup_info['HEALTHSTATUS']
running_status = metrogroup_info['RUNNINGSTATUS']
if (health_status == constants.HEALTH_NORMAL
and (running_status == constants.RUNNING_NORMAL
or running_status == constants.RUNNING_SYNC)):
self.client.stop_metrogroup(metrogroup_id)
return metrogroup_id

View File

@ -286,7 +286,9 @@ class RestClient(object):
def activate_snapshot(self, snapshot_id):
url = "/snapshot/activate"
data = {"SNAPSHOTLIST": [snapshot_id]}
data = ({"SNAPSHOTLIST": snapshot_id}
if type(snapshot_id) in (list, tuple)
else {"SNAPSHOTLIST": [snapshot_id]})
result = self.call(url, data)
self._assert_rest_result(result, _('Activate snapshot error.'))
@ -1156,6 +1158,7 @@ class RestClient(object):
smartcache=True,
smartpartition=True,
hypermetro=True,
consistencygroup_support=True,
))
data['pools'].append(pool)
return data
@ -1885,7 +1888,8 @@ class RestClient(object):
msg = _('get_hypermetro_by_id error.')
self._assert_rest_result(result, msg)
return result
self._assert_data_in_result(result, msg)
return result['data']
def check_hypermetro_exist(self, metro_id):
url = "/HyperMetroPair/" + metro_id
@ -1936,8 +1940,97 @@ class RestClient(object):
if 'data' in result:
return result["data"]["AVAILABLEHOSTLUNIDLIST"]
def get_metrogroup_by_name(self, name):
url = "/HyperMetro_ConsistentGroup?type='15364'"
result = self.call(url, None, "GET")
msg = _('Get hypermetro group by name error.')
self._assert_rest_result(result, msg)
return self._get_id_from_result(result, name, 'NAME')
def get_metrogroup_by_id(self, id):
url = "/HyperMetro_ConsistentGroup/" + id
result = self.call(url, None, "GET")
msg = _('Get hypermetro group by id error.')
self._assert_rest_result(result, msg)
self._assert_data_in_result(result, msg)
return result['data']
def create_metrogroup(self, name, description, domain_id):
url = "/HyperMetro_ConsistentGroup"
data = {"NAME": name,
"TYPE": "15364",
"DESCRIPTION": description,
"RECOVERYPOLICY": "1",
"SPEED": "2",
"PRIORITYSTATIONTYPE": "0",
"DOMAINID": domain_id}
result = self.call(url, data, "POST")
msg = _('create hypermetro group error.')
self._assert_rest_result(result, msg)
if 'data' in result:
return result["data"]["ID"]
def delete_metrogroup(self, metrogroup_id):
url = "/HyperMetro_ConsistentGroup/" + metrogroup_id
result = self.call(url, None, "DELETE")
msg = _('Delete hypermetro group error.')
self._assert_rest_result(result, msg)
def get_metrogroup(self, metrogroup_id):
url = "/HyperMetro_ConsistentGroup/" + metrogroup_id
result = self.call(url, None, "GET")
msg = _('Get hypermetro group error.')
self._assert_rest_result(result, msg)
def stop_metrogroup(self, metrogroup_id):
url = "/HyperMetro_ConsistentGroup/stop"
data = {"TYPE": "15364",
"ID": metrogroup_id
}
result = self.call(url, data, "PUT")
msg = _('stop hypermetro group error.')
self._assert_rest_result(result, msg)
def sync_metrogroup(self, metrogroup_id):
url = "/HyperMetro_ConsistentGroup/sync"
data = {"TYPE": "15364",
"ID": metrogroup_id
}
result = self.call(url, data, "PUT")
msg = _('sync hypermetro group error.')
self._assert_rest_result(result, msg)
def add_metro_to_metrogroup(self, metrogroup_id, metro_id):
url = "/hyperMetro/associate/pair"
data = {"TYPE": "15364",
"ID": metrogroup_id,
"ASSOCIATEOBJTYPE": "15361",
"ASSOCIATEOBJID": metro_id}
result = self.call(url, data, "POST")
msg = _('Add hypermetro to metrogroup error.')
self._assert_rest_result(result, msg)
def remove_metro_from_metrogroup(self, metrogroup_id, metro_id):
url = "/hyperMetro/associate/pair"
data = {"TYPE": "15364",
"ID": metrogroup_id,
"ASSOCIATEOBJTYPE": "15361",
"ASSOCIATEOBJID": metro_id}
result = self.call(url, data, "DELETE")
msg = _('Delete hypermetro from metrogroup error.')
self._assert_rest_result(result, msg)
def get_hypermetro_pairs(self):
url = "/HyperMetroPair?range=[0-65535]"
url = "/HyperMetroPair?range=[0-4095]"
result = self.call(url, None, "GET")
msg = _('Get HyperMetroPair error.')
self._assert_rest_result(result, msg)
@ -1945,7 +2038,7 @@ class RestClient(object):
return result.get('data', [])
def get_split_mirrors(self):
url = "/splitmirror?range=[0-512]"
url = "/splitmirror?range=[0-8191]"
result = self.call(url, None, "GET")
if result['error']['code'] == constants.NO_SPLITMIRROR_LICENSE:
msg = _('License is unavailable.')

View File

@ -0,0 +1,3 @@
---
features:
- Added consistency group support to the Huawei driver.