From d32d9966b6cf9a3cdd7889161b566d52d435f40a Mon Sep 17 00:00:00 2001 From: chenzongliang Date: Wed, 23 Dec 2015 18:04:32 +0800 Subject: [PATCH] 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 --- cinder/tests/unit/test_huawei_drivers.py | 398 +++++++++++++++++- cinder/volume/drivers/huawei/constants.py | 7 +- cinder/volume/drivers/huawei/huawei_driver.py | 189 ++++++++- cinder/volume/drivers/huawei/hypermetro.py | 110 ++++- cinder/volume/drivers/huawei/rest_client.py | 101 ++++- ...ei-consistency-group-b666f8f6c6cddd8f.yaml | 3 + 6 files changed, 769 insertions(+), 39 deletions(-) create mode 100644 releasenotes/notes/support-huawei-consistency-group-b666f8f6c6cddd8f.yaml diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index d2e8bf50edd..7e6547c9a5b 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -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': ' True', 'replication_type': ' async'} TEST_PAIR_ID = "3400a30d844d0004" +replica_hypermetro_specs = {'hypermetro': ' True', + 'replication_enabled': ' 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': ' 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): diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py index b4c12798c0c..140e2562cb6 100644 --- a/cinder/volume/drivers/huawei/constants.py +++ b/cinder/volume/drivers/huawei/constants.py @@ -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 diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index 53970505c64..505525791e8 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -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) diff --git a/cinder/volume/drivers/huawei/hypermetro.py b/cinder/volume/drivers/huawei/hypermetro.py index 83f9e9eab03..db3ded6f1de 100644 --- a/cinder/volume/drivers/huawei/hypermetro.py +++ b/cinder/volume/drivers/huawei/hypermetro.py @@ -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 diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index 2c514d91eca..fcf9c87c459 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -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.') diff --git a/releasenotes/notes/support-huawei-consistency-group-b666f8f6c6cddd8f.yaml b/releasenotes/notes/support-huawei-consistency-group-b666f8f6c6cddd8f.yaml new file mode 100644 index 00000000000..dced56fee8f --- /dev/null +++ b/releasenotes/notes/support-huawei-consistency-group-b666f8f6c6cddd8f.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added consistency group support to the Huawei driver. \ No newline at end of file