[ZFSonLinux] Add 'manage snapshot' feature support

Implement 'manage snapshot' feature in ZFSonLinux share driver.

Prerequisites:
- snapshot being managed should exist.

Details:
- snapshot being managed will be renamed by share driver based on
  taken ID.
- size for managed snapshot will be taken either from
  REST API (--driver_options size=5) or
  from its "used" attribute.

Implements BP zfsonlinux-manage-snapshot
DocImpact

Change-Id: If240ecde5676c334d39faaccd5703e93544aaa06
Depends-On: Ifc762a882a78159adacfe168a4edbe824178301a
This commit is contained in:
Valeriy Ponomaryov 2016-07-04 15:08:03 +03:00 committed by vponomaryov
parent 9928ba0190
commit d953baefcb
6 changed files with 143 additions and 3 deletions

View File

@ -171,6 +171,7 @@ elif [[ "$DRIVER" == "zfsonlinux" ]]; then
MANILA_TEMPEST_CONCURRENCY=8
RUN_MANILA_CG_TESTS=False
RUN_MANILA_MANAGE_TESTS=True
RUN_MANILA_MANAGE_SNAPSHOT_TESTS=True
iniset $TEMPEST_CONFIG share run_migration_tests False
iniset $TEMPEST_CONFIG share run_quota_tests True
iniset $TEMPEST_CONFIG share run_replication_tests True

View File

@ -33,7 +33,7 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| Driver name | create delete share | manage unmanage share | extend share | shrink share | create delete snapshot | create share from snapshot | manage unmanage snapshot |
+========================================+=======================+=======================+==============+==============+========================+============================+==========================+
| ZFSonLinux | M | N | M | M | M | M | \- |
| ZFSonLinux | M | N | M | M | M | M | N |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| Generic (Cinder as back-end) | J | K | L | L | J | J | M |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+

View File

@ -48,6 +48,8 @@ The following operations are supported:
* Deny NFS Share access
* Create snapshot
* Delete snapshot
* Manage snapshot
* Unmanage snapshot
* Create share from snapshot
* Extend share
* Shrink share

View File

@ -18,6 +18,7 @@ Module with ZFSonLinux share driver that utilizes ZFS filesystem resources
and exports them as shares.
"""
import math
import time
from oslo_config import cfg
@ -492,6 +493,7 @@ class ZFSonLinuxShareDriver(zfs_utils.ExecuteMixin, driver.ShareDriver):
}
)
self.zfs('snapshot', snapshot_name)
return {"provider_location": snapshot_name}
@ensure_share_server_not_provided
def delete_snapshot(self, context, snapshot, share_server=None):
@ -519,7 +521,7 @@ class ZFSonLinuxShareDriver(zfs_utils.ExecuteMixin, driver.ShareDriver):
_LW("Snapshot with '%(id)s' ID and '%(name)s' NAME is "
"absent on backend. Nothing has been deleted."),
{'id': snapshot['id'], 'name': snapshot_name})
self.private_storage.delete(snapshot['id'])
self.private_storage.delete(snapshot['snapshot_id'])
@ensure_share_server_not_provided
def create_share_from_snapshot(self, context, share, snapshot,
@ -723,6 +725,54 @@ class ZFSonLinuxShareDriver(zfs_utils.ExecuteMixin, driver.ShareDriver):
"""Removes the specified share from Manila management."""
self.private_storage.delete(share['id'])
def manage_existing_snapshot(self, snapshot_instance, driver_options):
"""Manage existing share snapshot with manila.
:param snapshot_instance: SnapshotInstance data
:param driver_options: expects only one optional key 'size'.
:return: dict with share snapshot instance fields for update, example:
{'size': 1,
'provider_location': 'path/to/some/dataset@some_snapshot_tag'}
"""
snapshot_size = int(driver_options.get("size", 0))
old_provider_location = snapshot_instance.get("provider_location")
old_snapshot_tag = old_provider_location.split("@")[-1]
new_snapshot_tag = self._get_snapshot_name(snapshot_instance["id"])
self.private_storage.update(
snapshot_instance["snapshot_id"], {
"entity_type": "snapshot",
"old_snapshot_tag": old_snapshot_tag,
"snapshot_tag": new_snapshot_tag,
}
)
try:
self.zfs("list", "-r", "-t", "snapshot", old_provider_location)
except exception.ProcessExecutionError as e:
raise exception.ManageInvalidShareSnapshot(reason=e.stderr)
if not snapshot_size:
consumed_space = self.get_zfs_option(old_provider_location, "used")
consumed_space = utils.translate_string_size_to_float(
consumed_space)
snapshot_size = int(math.ceil(consumed_space))
dataset_name = self.private_storage.get(
snapshot_instance["share_instance_id"], "dataset_name")
new_provider_location = dataset_name + "@" + new_snapshot_tag
self.zfs("rename", old_provider_location, new_provider_location)
return {
"size": snapshot_size,
"provider_location": new_provider_location,
}
def unmanage_snapshot(self, snapshot_instance):
"""Unmanage dataset snapshot."""
self.private_storage.delete(snapshot_instance["snapshot_id"])
def _get_replication_snapshot_prefix(self, replica):
"""Returns replica-based snapshot prefix."""
replication_snapshot_prefix = "%s_%s" % (

View File

@ -632,7 +632,7 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase):
snapshot['share_instance_id'],
{'dataset_name': 'foo_data_set_name'})
self.driver.create_snapshot('fake_context', snapshot)
result = self.driver.create_snapshot('fake_context', snapshot)
self.driver.zfs.assert_called_once_with(
'snapshot', snapshot_name)
@ -640,6 +640,7 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase):
snapshot_name.split('@')[-1],
self.driver.private_storage.get(
snapshot['snapshot_id'], 'snapshot_tag'))
self.assertEqual({"provider_location": snapshot_name}, result)
def test_delete_snapshot(self):
snapshot = {
@ -1194,6 +1195,89 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase):
self.driver.private_storage.delete.assert_called_once_with(share['id'])
@ddt.data(
{},
{"size": 5},
{"size": "5"},
)
def test_manage_existing_snapshot(self, driver_options):
dataset_name = "path/to/dataset"
old_provider_location = dataset_name + "@original_snapshot_tag"
snapshot_instance = {
"id": "fake_snapshot_instance_id",
"share_instance_id": "fake_share_instance_id",
"snapshot_id": "fake_snapshot_id",
"provider_location": old_provider_location,
}
new_snapshot_tag = "fake_new_snapshot_tag"
new_provider_location = (
old_provider_location.split("@")[0] + "@" + new_snapshot_tag)
self.mock_object(self.driver, "zfs")
self.mock_object(
self.driver, "get_zfs_option", mock.Mock(return_value="5G"))
self.mock_object(
self.driver,
'_get_snapshot_name',
mock.Mock(return_value=new_snapshot_tag))
self.driver.private_storage.update(
snapshot_instance["share_instance_id"],
{"dataset_name": dataset_name})
result = self.driver.manage_existing_snapshot(
snapshot_instance, driver_options)
expected_result = {
"size": 5,
"provider_location": new_provider_location,
}
self.assertEqual(expected_result, result)
self.driver._get_snapshot_name.assert_called_once_with(
snapshot_instance["id"])
self.driver.zfs.assert_has_calls([
mock.call("list", "-r", "-t", "snapshot", old_provider_location),
mock.call("rename", old_provider_location, new_provider_location),
])
def test_manage_existing_snapshot_not_found(self):
dataset_name = "path/to/dataset"
old_provider_location = dataset_name + "@original_snapshot_tag"
new_snapshot_tag = "fake_new_snapshot_tag"
snapshot_instance = {
"id": "fake_snapshot_instance_id",
"snapshot_id": "fake_snapshot_id",
"provider_location": old_provider_location,
}
self.mock_object(
self.driver, "_get_snapshot_name",
mock.Mock(return_value=new_snapshot_tag))
self.mock_object(
self.driver, "zfs",
mock.Mock(side_effect=exception.ProcessExecutionError("FAKE")))
self.assertRaises(
exception.ManageInvalidShareSnapshot,
self.driver.manage_existing_snapshot,
snapshot_instance, {},
)
self.driver.zfs.assert_called_once_with(
"list", "-r", "-t", "snapshot", old_provider_location)
self.driver._get_snapshot_name.assert_called_once_with(
snapshot_instance["id"])
def test_unmanage_snapshot(self):
snapshot_instance = {
"id": "fake_snapshot_instance_id",
"snapshot_id": "fake_snapshot_id",
}
self.mock_object(self.driver.private_storage, "delete")
self.driver.unmanage_snapshot(snapshot_instance)
self.driver.private_storage.delete.assert_called_once_with(
snapshot_instance["snapshot_id"])
def test__delete_dataset_or_snapshot_with_retry_snapshot(self):
self.mock_object(self.driver, 'get_zfs_option')
self.mock_object(self.driver, 'zfs')

View File

@ -0,0 +1,3 @@
---
features:
- Added support of 'manage snapshot' feature to ZFSonLinux driver.