Implement share revert to snapshot
This commit adds the ability for Manila to revert a
share to the latest available snapshot.
The feature is implemented in the LVM driver, for
testing purposes.
APIImpact
DocImpact
Co-Authored-By: Ben Swartzlander <ben@swartzlander.org>
Co-Authored-By: Andrew Kerr <andrew.kerr@netapp.com>
Implements: blueprint manila-share-revert-to-snapshot
Change-Id: Id497e13070e0003db2db951526a52de6c2182cca
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py
index 85cbc5d..63fdf23 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py
@@ -14,11 +14,16 @@
# under the License.
import ddt
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc
from testtools import testcase as tc
+from manila_tempest_tests.common import constants
from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+CONF = config.CONF
@ddt.ddt
@@ -69,8 +74,12 @@
share_type = self.shares_v2_client.get_share_type(
st['share_type']['id'])
# Verify a non-admin can only read the required extra-specs
- expected_keys = ['driver_handles_share_servers', 'snapshot_support',
- 'create_share_from_snapshot_support']
+ expected_keys = ['driver_handles_share_servers', 'snapshot_support']
+ if utils.is_microversion_ge(CONF.share.max_api_microversion, '2.24'):
+ expected_keys.append('create_share_from_snapshot_support')
+ if utils.is_microversion_ge(CONF.share.max_api_microversion,
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION):
+ expected_keys.append('revert_to_snapshot_support')
actual_keys = share_type['share_type']['extra_specs'].keys()
self.assertEqual(sorted(expected_keys), sorted(actual_keys),
'Incorrect extra specs visible to non-admin user; '
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index 4459f59..398108d 100644
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -740,6 +740,8 @@
CONF.share.capability_snapshot_support)
create_from_snapshot_support = six.text_type(
CONF.share.capability_create_share_from_snapshot_support)
+ revert_to_snapshot_support = six.text_type(
+ CONF.share.capability_revert_to_snapshot_support)
extra_specs_dict = {
"driver_handles_share_servers": dhss,
@@ -748,6 +750,7 @@
optional = {
"snapshot_support": snapshot_support,
"create_share_from_snapshot_support": create_from_snapshot_support,
+ "revert_to_snapshot_support": revert_to_snapshot_support,
}
# NOTE(gouthamr): In micro-versions < 2.24, snapshot_support is a
# required extra-spec
diff --git a/manila_tempest_tests/tests/api/test_revert_to_snapshot.py b/manila_tempest_tests/tests/api/test_revert_to_snapshot.py
new file mode 100644
index 0000000..686b185
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_revert_to_snapshot.py
@@ -0,0 +1,109 @@
+# Copyright 2016 Andrew Kerr
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import ddt
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import constants
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+@base.skip_if_microversion_not_supported(
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION)
+@ddt.ddt
+class RevertToSnapshotTest(base.BaseSharesMixedTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(RevertToSnapshotTest, cls).skip_checks()
+ if not CONF.share.run_revert_to_snapshot_tests:
+ msg = "Revert to snapshot tests are disabled."
+ raise cls.skipException(msg)
+ if not CONF.share.capability_revert_to_snapshot_support:
+ msg = "Revert to snapshot support is disabled."
+ raise cls.skipException(msg)
+ if not CONF.share.capability_snapshot_support:
+ msg = "Snapshot support is disabled."
+ raise cls.skipException(msg)
+ if not CONF.share.run_snapshot_tests:
+ msg = "Snapshot tests are disabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(RevertToSnapshotTest, cls).resource_setup()
+ cls.admin_client = cls.admin_shares_v2_client
+ pools = cls.admin_client.list_pools(detail=True)['pools']
+ revert_support = [
+ pool['capabilities'][constants.REVERT_TO_SNAPSHOT_SUPPORT]
+ for pool in pools]
+ if not any(revert_support):
+ msg = "Revert to snapshot not supported."
+ raise cls.skipException(msg)
+
+ cls.share_type_name = data_utils.rand_name("share-type")
+ extra_specs = {constants.REVERT_TO_SNAPSHOT_SUPPORT: True}
+ cls.revert_enabled_extra_specs = cls.add_extra_specs_to_dict(
+ extra_specs=extra_specs)
+
+ cls.share_type = cls.create_share_type(
+ cls.share_type_name,
+ extra_specs=cls.revert_enabled_extra_specs,
+ client=cls.admin_client)
+
+ cls.st_id = cls.share_type['share_type']['id']
+
+ cls.share = cls.create_share(share_type_id=cls.st_id)
+
+ @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_to_latest_snapshot(self, version):
+ snapshot = self.create_snapshot_wait_for_active(self.share['id'],
+ cleanup_in_class=False)
+ self.shares_v2_client.revert_to_snapshot(
+ self.share['id'],
+ snapshot['id'],
+ version=version)
+ self.shares_v2_client.wait_for_share_status(self.share['id'],
+ constants.STATUS_AVAILABLE)
+
+ @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_to_previous_snapshot(self, version):
+ snapshot1 = self.create_snapshot_wait_for_active(
+ self.share['id'], cleanup_in_class=False)
+ snapshot2 = self.create_snapshot_wait_for_active(
+ self.share['id'], cleanup_in_class=False)
+
+ self.shares_v2_client.delete_snapshot(snapshot2['id'])
+ self.shares_v2_client.wait_for_resource_deletion(
+ snapshot_id=snapshot2['id'])
+
+ self.shares_v2_client.revert_to_snapshot(self.share['id'],
+ snapshot1['id'],
+ version=version)
+ self.shares_v2_client.wait_for_share_status(self.share['id'],
+ constants.STATUS_AVAILABLE)
diff --git a/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py b/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py
new file mode 100644
index 0000000..ee403af
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py
@@ -0,0 +1,162 @@
+# Copyright 2016 Andrew Kerr
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import ddt
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import constants
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+@base.skip_if_microversion_not_supported(
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION)
+@ddt.ddt
+class RevertToSnapshotNegativeTest(base.BaseSharesMixedTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(RevertToSnapshotNegativeTest, cls).skip_checks()
+ if not CONF.share.run_revert_to_snapshot_tests:
+ msg = "Revert to snapshot tests are disabled."
+ raise cls.skipException(msg)
+ if not CONF.share.capability_revert_to_snapshot_support:
+ msg = "Revert to snapshot support is disabled."
+ raise cls.skipException(msg)
+ if not CONF.share.capability_snapshot_support:
+ msg = "Snapshot support is disabled."
+ raise cls.skipException(msg)
+ if not CONF.share.run_snapshot_tests:
+ msg = "Snapshot tests are disabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(RevertToSnapshotNegativeTest, cls).resource_setup()
+ cls.admin_client = cls.admin_shares_v2_client
+ pools = cls.admin_client.list_pools(detail=True)['pools']
+ revert_support = [
+ pool['capabilities'][constants.REVERT_TO_SNAPSHOT_SUPPORT]
+ for pool in pools]
+ if not any(revert_support):
+ msg = "Revert to snapshot not supported."
+ raise cls.skipException(msg)
+
+ cls.share_type_name = data_utils.rand_name("share-type")
+ extra_specs = {constants.REVERT_TO_SNAPSHOT_SUPPORT: True}
+ cls.revert_enabled_extra_specs = cls.add_extra_specs_to_dict(
+ extra_specs=extra_specs)
+
+ cls.share_type = cls.create_share_type(
+ cls.share_type_name,
+ extra_specs=cls.revert_enabled_extra_specs,
+ client=cls.admin_client)
+
+ cls.st_id = cls.share_type['share_type']['id']
+
+ cls.share = cls.create_share(share_type_id=cls.st_id)
+ cls.share2 = cls.create_share(share_type_id=cls.st_id)
+
+ @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_to_second_latest_snapshot(self, version):
+ snapshot1 = self.create_snapshot_wait_for_active(
+ self.share['id'], cleanup_in_class=False)
+ self.create_snapshot_wait_for_active(self.share['id'],
+ cleanup_in_class=False)
+
+ self.assertRaises(exceptions.Conflict,
+ self.shares_v2_client.revert_to_snapshot,
+ self.share['id'],
+ snapshot1['id'],
+ version=version)
+
+ @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_to_error_snapshot(self, version):
+ snapshot = self.create_snapshot_wait_for_active(self.share['id'],
+ cleanup_in_class=False)
+
+ self.admin_client.reset_state(snapshot['id'],
+ status=constants.STATUS_ERROR,
+ s_type='snapshots')
+
+ self.assertRaises(exceptions.Conflict,
+ self.shares_v2_client.revert_to_snapshot,
+ self.share['id'],
+ snapshot['id'],
+ version=version)
+
+ @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_error_share_to_snapshot(self, version):
+ snapshot = self.create_snapshot_wait_for_active(self.share['id'],
+ cleanup_in_class=False)
+
+ self.admin_client.reset_state(self.share['id'],
+ status=constants.STATUS_ERROR,
+ s_type='shares')
+
+ self.addCleanup(self.admin_client.reset_state,
+ self.share['id'],
+ status=constants.STATUS_AVAILABLE,
+ s_type='shares')
+
+ self.assertRaises(exceptions.Conflict,
+ self.shares_v2_client.revert_to_snapshot,
+ self.share['id'],
+ snapshot['id'],
+ version=version)
+
+ @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_to_missing_snapshot(self, version):
+ self.assertRaises(exceptions.BadRequest,
+ self.shares_v2_client.revert_to_snapshot,
+ self.share['id'],
+ self.share['id'],
+ version=version)
+
+ @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+ @ddt.data(
+ *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+ CONF.share.max_api_microversion}
+ )
+ def test_revert_to_invalid_snapshot(self, version):
+ snapshot = self.create_snapshot_wait_for_active(
+ self.share['id'], cleanup_in_class=False)
+
+ self.assertRaises(exceptions.BadRequest,
+ self.shares_v2_client.revert_to_snapshot,
+ self.share2['id'],
+ snapshot['id'],
+ version=version)
diff --git a/manila_tempest_tests/tests/api/test_shares_actions.py b/manila_tempest_tests/tests/api/test_shares_actions.py
index d03df33..0f904fc 100644
--- a/manila_tempest_tests/tests/api/test_shares_actions.py
+++ b/manila_tempest_tests/tests/api/test_shares_actions.py
@@ -20,6 +20,7 @@
import testtools
from testtools import testcase as tc
+from manila_tempest_tests.common import constants
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils
@@ -106,6 +107,9 @@
expected_keys.append("user_id")
if utils.is_microversion_ge(version, '2.24'):
expected_keys.append("create_share_from_snapshot_support")
+ if utils.is_microversion_ge(version,
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION):
+ expected_keys.append("revert_to_snapshot_support")
actual_keys = list(share.keys())
[self.assertIn(key, actual_keys) for key in expected_keys]
@@ -168,6 +172,12 @@
self._get_share('2.24')
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+ @utils.skip_if_microversion_not_supported(
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION)
+ def test_get_share_with_revert_to_snapshot_support(self):
+ self._get_share(constants.REVERT_TO_SNAPSHOT_MICROVERSION)
+
+ @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
def test_list_shares(self):
# list shares
@@ -213,6 +223,9 @@
keys.append("user_id")
if utils.is_microversion_ge(version, '2.24'):
keys.append("create_share_from_snapshot_support")
+ if utils.is_microversion_ge(version,
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION):
+ keys.append("revert_to_snapshot_support")
[self.assertIn(key, sh.keys()) for sh in shares for key in keys]
# our shares in list and have no duplicates
@@ -265,6 +278,13 @@
self._list_shares_with_detail('2.24')
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+ @utils.skip_if_microversion_not_supported(
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION)
+ def test_list_shares_with_detail_with_revert_to_snapshot_support(self):
+ self._list_shares_with_detail(
+ constants.REVERT_TO_SNAPSHOT_MICROVERSION)
+
+ @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
def test_list_shares_with_detail_filter_by_metadata(self):
filters = {'metadata': self.metadata}