diff --git a/manila/share/drivers/huawei/base.py b/manila/share/drivers/huawei/base.py index 1fbbbfa9..739756b7 100644 --- a/manila/share/drivers/huawei/base.py +++ b/manila/share/drivers/huawei/base.py @@ -77,6 +77,10 @@ class HuaweiBase(object): def manage_existing(self, share, driver_options): """Manage existing share.""" + @abc.abstractmethod + def manage_existing_snapshot(self, snapshot, driver_options): + """Manage existing snapshot.""" + @abc.abstractmethod def get_network_allocations_number(self): """Get number of network interfaces to be created.""" diff --git a/manila/share/drivers/huawei/constants.py b/manila/share/drivers/huawei/constants.py index c1abd2d1..478a8571 100644 --- a/manila/share/drivers/huawei/constants.py +++ b/manila/share/drivers/huawei/constants.py @@ -16,6 +16,7 @@ STATUS_ETH_RUNNING = "10" STATUS_FS_HEALTH = "1" STATUS_FS_RUNNING = "27" +STATUS_FSSNAPSHOT_HEALTH = '1' STATUS_JOIN_DOMAIN = '1' STATUS_EXIT_DOMAIN = '0' STATUS_SERVICE_RUNNING = "2" diff --git a/manila/share/drivers/huawei/huawei_nas.py b/manila/share/drivers/huawei/huawei_nas.py index 51334813..383659c4 100644 --- a/manila/share/drivers/huawei/huawei_nas.py +++ b/manila/share/drivers/huawei/huawei_nas.py @@ -43,7 +43,7 @@ class HuaweiNasDriver(driver.ShareDriver): """Huawei Share Driver. Executes commands relating to Shares. - API version history:: + Driver version history: 1.0 - Initial version. 1.1 - Add shrink share. @@ -56,6 +56,7 @@ class HuaweiNasDriver(driver.ShareDriver): Add ensure share. Add QoS support. Add create share from snapshot. + 1.3 - Add manage snapshot. """ def __init__(self, *args, **kwargs): @@ -134,7 +135,8 @@ class HuaweiNasDriver(driver.ShareDriver): def create_snapshot(self, context, snapshot, share_server=None): """Create a snapshot.""" LOG.debug("Create a snapshot.") - self.plugin.create_snapshot(snapshot, share_server) + snapshot_name = self.plugin.create_snapshot(snapshot, share_server) + return {'provider_location': snapshot_name} def delete_snapshot(self, context, snapshot, share_server=None): """Delete a snapshot.""" @@ -181,6 +183,13 @@ class HuaweiNasDriver(driver.ShareDriver): driver_options) return {'size': share_size, 'export_locations': location} + def manage_existing_snapshot(self, snapshot, driver_options): + """Manage existing snapshot.""" + LOG.debug("Manage existing snapshot to manila.") + snapshot_name = self.plugin.manage_existing_snapshot(snapshot, + driver_options) + return {'provider_location': snapshot_name} + def _update_share_stats(self): """Retrieve status info from share group.""" @@ -188,7 +197,7 @@ class HuaweiNasDriver(driver.ShareDriver): data = dict( share_backend_name=backend_name or 'HUAWEI_NAS_Driver', vendor_name='Huawei', - driver_version='1.2', + driver_version='1.3', storage_protocol='NFS_CIFS', qos=True, total_capacity_gb=0.0, diff --git a/manila/share/drivers/huawei/v3/connection.py b/manila/share/drivers/huawei/v3/connection.py index 4195bbb9..7dc2123e 100644 --- a/manila/share/drivers/huawei/v3/connection.py +++ b/manila/share/drivers/huawei/v3/connection.py @@ -248,6 +248,7 @@ class V3StorageConnection(driver.HuaweiBase): snap_id = self.helper._create_snapshot(sharefsid, snapshot_name) LOG.info(_LI('Creating snapshot id %s.'), snap_id) + return snapshot_name.replace("-", "_") def delete_snapshot(self, snapshot, share_server=None): """Delete a snapshot.""" @@ -262,7 +263,8 @@ class V3StorageConnection(driver.HuaweiBase): return snapshot_id = self.helper._get_snapshot_id(sharefsid, snap_name) - snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_id) + snapshot_info = self.helper._get_snapshot_by_id(snapshot_id) + snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_info) if snapshot_flag: self.helper._delete_snapshot(snapshot_id) @@ -358,7 +360,8 @@ class V3StorageConnection(driver.HuaweiBase): name=snapshot['share_name']) snapshot_id = self.helper._get_snapshot_id(share_fs_id, snapshot['id']) - snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_id) + snapshot_info = self.helper._get_snapshot_by_id(snapshot_id) + snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_info) if not snapshot_flag: err_msg = (_("Cannot find snapshot %s on array.") % snapshot['snapshot_id']) @@ -876,6 +879,51 @@ class V3StorageConnection(driver.HuaweiBase): location = self._get_location_path(share_name, share_proto) return (share_size, [location]) + def _check_snapshot_valid_for_manage(self, snapshot_info): + snapshot_name = snapshot_info['data']['NAME'] + + # Check whether the snapshot is normal. + if (snapshot_info['data']['HEALTHSTATUS'] + != constants.STATUS_FSSNAPSHOT_HEALTH): + msg = (_("Can't import snapshot %(snapshot)s to Manila. " + "Snapshot status is not normal, snapshot status: " + "%(status)s.") + % {'snapshot': snapshot_name, + 'status': snapshot_info['data']['HEALTHSTATUS']}) + raise exception.ManageInvalidShareSnapshot( + reason=msg) + + def manage_existing_snapshot(self, snapshot, driver_options): + """Manage existing snapshot.""" + + share_proto = snapshot['share']['share_proto'] + share_url_type = self.helper._get_share_url_type(share_proto) + share_storage = self.helper._get_share_by_name(snapshot['share_name'], + share_url_type) + if not share_storage: + err_msg = (_("Failed to import snapshot %(snapshot)s to Manila. " + "Snapshot source share %(share)s doesn't exist " + "on array.") + % {'snapshot': snapshot['provider_location'], + 'share': snapshot['share_name']}) + raise exception.InvalidShare(reason=err_msg) + sharefsid = share_storage['FSID'] + + provider_location = snapshot.get('provider_location') + snapshot_id = sharefsid + "@" + provider_location + snapshot_info = self.helper._get_snapshot_by_id(snapshot_id) + snapshot_flag = self.helper._check_snapshot_id_exist(snapshot_info) + if not snapshot_flag: + err_msg = (_("Cannot find snapshot %s on array.") + % snapshot['provider_location']) + raise exception.ManageInvalidShareSnapshot(reason=err_msg) + else: + self._check_snapshot_valid_for_manage(snapshot_info) + snapshot_name = ("share_snapshot_" + + snapshot['id'].replace("-", "_")) + self.helper._rename_share_snapshot(snapshot_id, snapshot_name) + return snapshot_name + def check_retype_change_opts(self, opts, poolinfo, fs): change_opts = { "partitionid": None, diff --git a/manila/share/drivers/huawei/v3/helper.py b/manila/share/drivers/huawei/v3/helper.py index b2e2b586..7ffd4bbe 100644 --- a/manila/share/drivers/huawei/v3/helper.py +++ b/manila/share/drivers/huawei/v3/helper.py @@ -588,23 +588,25 @@ class RestHelper(object): return share_client_type - def _check_snapshot_id_exist(self, snap_id): + def _check_snapshot_id_exist(self, snapshot_info): """Check the snapshot id exists.""" - url_subfix = "/FSSNAPSHOT/" + snap_id - url = url_subfix - result = self.call(url, None, "GET") - - if result['error']['code'] == constants.MSG_SNAPSHOT_NOT_FOUND: + if snapshot_info['error']['code'] == constants.MSG_SNAPSHOT_NOT_FOUND: return False - elif result['error']['code'] == 0: + elif snapshot_info['error']['code'] == 0: return True else: err_str = "Check the snapshot id exists error!" err_msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str, - 'res': result}) - LOG.error(err_msg) - raise exception.InvalidShare(reason=err_msg) + 'res': snapshot_info}) + raise exception.InvalidShareSnapshot(reason=err_msg) + + def _get_snapshot_by_id(self, snap_id): + """Get snapshot by id""" + url = "/FSSNAPSHOT/" + snap_id + + result = self.call(url, None, "GET") + return result def _delete_snapshot(self, snap_id): """Deletes snapshot.""" @@ -851,6 +853,14 @@ class RestHelper(object): self._assert_rest_result(result, _('Remove filesystem from partition error.')) + def _rename_share_snapshot(self, snapshot_id, new_name): + url = "/FSSNAPSHOT/" + snapshot_id + data = jsonutils.dumps({"NAME": new_name}) + result = self.call(url, data, "PUT") + msg = _('Rename share snapshot on array error.') + self._assert_rest_result(result, msg) + self._assert_data_in_result(result, msg) + def _get_cache_id_by_name(self, name): url = "/SMARTCACHEPARTITION" result = self.call(url, None, "GET") diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py index 43d99152..7191ff6f 100644 --- a/manila/tests/share/drivers/huawei/test_huawei_nas.py +++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py @@ -450,11 +450,21 @@ class FakeHuaweiNasHelper(helper.RestHelper): if url == "/FSSNAPSHOT/4@share_snapshot_fake_snapshot_uuid": if self.snapshot_flag: - data = """{"error":{"code":0},"data":{"ID":"3"}}""" + data = """{"error":{"code":0}, + "data":{"ID":"4@share_snapshot_fake_snapshot_uuid"}}""" else: data = '{"error":{"code":1073754118}}' self.delete_flag = True + if url == "/FSSNAPSHOT/4@fake_storage_snapshot_name": + if self.snapshot_flag: + data = """{"error":{"code":0}, + "data":{"ID":"4@share_snapshot_fake_snapshot_uuid", + "NAME":"share_snapshot_fake_snapshot_uuid", + "HEALTHSTATUS":"1"}}""" + else: + data = '{"error":{"code":1073754118}}' + if url == "/FSSNAPSHOT/3": data = """{"error":{"code":0}}""" self.delete_flag = True @@ -943,6 +953,40 @@ class HuaweiShareDriverTestCase(test.TestCase): }, } + self.storage_nfs_snapshot = { + 'id': 'fake_snapshot_uuid', + 'snapshot_id': 'fake_snapshot_uuid', + 'display_name': 'snapshot', + 'name': 'fake_snapshot_name', + 'provider_location': 'fake_storage_snapshot_name', + 'size': 1, + 'share_name': 'share_fake_uuid', + 'share_id': 'fake_uuid', + 'share': { + 'share_name': 'share_fake_uuid', + 'share_id': 'fake_uuid', + 'share_size': 1, + 'share_proto': 'NFS', + }, + } + + self.storage_cifs_snapshot = { + 'id': 'fake_snapshot_uuid', + 'snapshot_id': 'fake_snapshot_uuid', + 'display_name': 'snapshot', + 'name': 'fake_snapshot_name', + 'provider_location': 'fake_storage_snapshot_name', + 'size': 1, + 'share_name': 'share_fake_uuid', + 'share_id': 'fake_uuid', + 'share': { + 'share_name': 'share_fake_uuid', + 'share_id': 'fake_uuid', + 'share_size': 1, + 'share_proto': 'CIFS', + }, + } + self.security_service = { 'id': 'fake_id', 'domain': 'FAKE', @@ -1762,12 +1806,14 @@ class HuaweiShareDriverTestCase(test.TestCase): self.assertTrue(self.driver.plugin.helper.delete_flag) def test_check_snapshot_id_exist_fail(self): - snapshot_id = "4" + snapshot_id = "4@share_snapshot_not_exist" self.driver.plugin.helper.login() self.driver.plugin.helper.test_normal = False - self.assertRaises(exception.InvalidShare, + snapshot_info = self.driver.plugin.helper._get_snapshot_by_id( + snapshot_id) + self.assertRaises(exception.InvalidShareSnapshot, self.driver.plugin.helper._check_snapshot_id_exist, - snapshot_id) + snapshot_info) def test_delete_share_nfs_fail_not_exist(self): self.driver.plugin.helper.login() @@ -2135,7 +2181,7 @@ class HuaweiShareDriverTestCase(test.TestCase): expected["share_backend_name"] = "fake_share_backend_name" expected["driver_handles_share_servers"] = False expected["vendor_name"] = 'Huawei' - expected["driver_version"] = '1.2' + expected["driver_version"] = '1.3' expected["storage_protocol"] = 'NFS_CIFS' expected['reserved_percentage'] = 0 expected['total_capacity_gb'] = 0.0 @@ -2776,6 +2822,56 @@ class HuaweiShareDriverTestCase(test.TestCase): self.share_nfs, self.driver_options) + @ddt.data({"share_proto": "NFS", + "provider_location": "share_snapshot_fake_snapshot_uuid"}, + {"share_proto": "CIFS", + "provider_location": "share_snapshot_fake_snapshot_uuid"}) + @ddt.unpack + def test_manage_existing_snapshot_success(self, share_proto, + provider_location): + if share_proto == "NFS": + snapshot = self.storage_nfs_snapshot + elif share_proto == "CIFS": + snapshot = self.storage_cifs_snapshot + self.driver.plugin.helper.login() + snapshot_info = self.driver.manage_existing_snapshot( + snapshot, self.driver_options) + self.assertEqual(provider_location, snapshot_info['provider_location']) + + def test_manage_existing_snapshot_share_not_exist(self): + self.driver.plugin.helper.login() + self.mock_object(self.driver.plugin.helper, + '_get_share_by_name', + mock.Mock(return_value={})) + self.assertRaises(exception.InvalidShare, + self.driver.manage_existing_snapshot, + self.storage_nfs_snapshot, + self.driver_options) + + def test_manage_existing_snapshot_sharesnapshot_not_exist(self): + self.driver.plugin.helper.login() + self.mock_object(self.driver.plugin.helper, + '_check_snapshot_id_exist', + mock.Mock(return_value={})) + self.assertRaises(exception.ManageInvalidShareSnapshot, + self.driver.manage_existing_snapshot, + self.storage_nfs_snapshot, + self.driver_options) + + def test_manage_existing_snapshot_sharesnapshot_not_normal(self): + snapshot_info = {"error": {"code": 0}, + "data": {"ID": "4@share_snapshot_fake_snapshot_uuid", + "NAME": "share_snapshot_fake_snapshot_uuid", + "HEALTHSTATUS": "2"}} + self.driver.plugin.helper.login() + self.mock_object(self.driver.plugin.helper, + '_get_snapshot_by_id', + mock.Mock(return_value=snapshot_info)) + self.assertRaises(exception.ManageInvalidShareSnapshot, + self.driver.manage_existing_snapshot, + self.storage_nfs_snapshot, + self.driver_options) + def test_get_pool_success(self): self.driver.plugin.helper.login() pool_name = self.driver.get_pool(self.share_nfs_host_not_exist) diff --git a/releasenotes/notes/manage-share-snapshot-in-huawei-driver-007b2c763fbdf480.yaml b/releasenotes/notes/manage-share-snapshot-in-huawei-driver-007b2c763fbdf480.yaml new file mode 100644 index 00000000..3123dcd2 --- /dev/null +++ b/releasenotes/notes/manage-share-snapshot-in-huawei-driver-007b2c763fbdf480.yaml @@ -0,0 +1,3 @@ +--- +features: + - Manage share snapshot on array in huawei driver.