Merge "Add snapshot instances admin APIs"
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index 4b394c6..55f2e15 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -34,7 +34,7 @@
help="The minimum api microversion is configured to be the "
"value of the minimum microversion supported by Manila."),
cfg.StrOpt("max_api_microversion",
- default="2.18",
+ default="2.19",
help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."),
cfg.StrOpt("region",
diff --git a/manila_tempest_tests/services/share/v2/json/shares_client.py b/manila_tempest_tests/services/share/v2/json/shares_client.py
index 3995161..46c0ce7 100755
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -552,6 +552,68 @@
###############
+ def get_snapshot_instance(self, instance_id, version=LATEST_MICROVERSION):
+ resp, body = self.get("snapshot-instances/%s" % instance_id,
+ version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def list_snapshot_instances(self, detail=False, snapshot_id=None,
+ version=LATEST_MICROVERSION):
+ """Get list of share snapshot instances."""
+ uri = "snapshot-instances%s" % ('/detail' if detail else '')
+ if snapshot_id is not None:
+ uri += '?snapshot_id=%s' % snapshot_id
+ resp, body = self.get(uri, version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def reset_snapshot_instance_status(self, instance_id,
+ status=constants.STATUS_AVAILABLE,
+ version=LATEST_MICROVERSION):
+ """Reset the status."""
+ uri = 'snapshot-instances/%s/action' % instance_id
+ post_body = {
+ 'reset_status': {
+ 'status': status
+ }
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body, extra_headers=True, version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
+
+ def wait_for_snapshot_instance_status(self, instance_id, expected_status):
+ """Waits for a snapshot instance status to reach a given status."""
+ body = self.get_snapshot_instance(instance_id)
+ instance_status = body['status']
+ start = int(time.time())
+
+ while instance_status != expected_status:
+ time.sleep(self.build_interval)
+ body = self.get_snapshot_instance(instance_id)
+ instance_status = body['status']
+ if instance_status == expected_status:
+ return
+ if 'error' in instance_status:
+ raise share_exceptions.SnapshotInstanceBuildErrorException(
+ id=instance_id)
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = ('The status of snapshot instance %(id)s failed to '
+ 'reach %(expected_status)s status within the '
+ 'required time (%(time)ss). Current '
+ 'status: %(current_status)s.' %
+ {
+ 'expected_status': expected_status,
+ 'time': self.build_timeout,
+ 'id': instance_id,
+ 'current_status': instance_status,
+ })
+ raise exceptions.TimeoutException(message)
+
+###############
+
def _get_access_action_name(self, version, action):
if utils.is_microversion_gt(version, "2.6"):
return action.split('os-')[-1]
diff --git a/manila_tempest_tests/share_exceptions.py b/manila_tempest_tests/share_exceptions.py
index 3a11531..9b84d02 100644
--- a/manila_tempest_tests/share_exceptions.py
+++ b/manila_tempest_tests/share_exceptions.py
@@ -37,6 +37,11 @@
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
+class SnapshotInstanceBuildErrorException(exceptions.TempestException):
+ message = ("Snapshot instance %(id)s failed to build and is in "
+ "ERROR status.")
+
+
class CGSnapshotBuildErrorException(exceptions.TempestException):
message = ("CGSnapshot %(cgsnapshot_id)s failed to build and is in ERROR "
"status")
diff --git a/manila_tempest_tests/tests/api/admin/test_share_snapshot_instances.py b/manila_tempest_tests/tests/api/admin/test_share_snapshot_instances.py
new file mode 100644
index 0000000..68f5661
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_share_snapshot_instances.py
@@ -0,0 +1,121 @@
+# Copyright 2016 Huawei
+# 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 import test
+import testtools
+
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+@testtools.skipUnless(CONF.share.run_snapshot_tests,
+ 'Snapshot tests are disabled.')
+@base.skip_if_microversion_lt("2.19")
+@ddt.ddt
+class ShareSnapshotInstancesTest(base.BaseSharesAdminTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(ShareSnapshotInstancesTest, cls).resource_setup()
+ cls.share = cls.create_share()
+ snap = cls.create_snapshot_wait_for_active(cls.share["id"])
+ cls.snapshot = cls.shares_v2_client.get_snapshot(snap['id'])
+
+ @ddt.data(True, False)
+ @test.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+ def test_list_snapshot_instances_by_snapshot(self, detail):
+ """Test that we get only the 1 snapshot instance from snapshot."""
+ snapshot_instances = self.shares_v2_client.list_snapshot_instances(
+ detail=detail, snapshot_id=self.snapshot['id'])
+
+ expected_keys = ['id', 'snapshot_id', 'status']
+
+ if detail:
+ extra_detail_keys = ['provider_location', 'share_id',
+ 'share_instance_id', 'created_at',
+ 'updated_at', 'progress']
+ expected_keys.extend(extra_detail_keys)
+
+ si_num = len(snapshot_instances)
+ self.assertEqual(1, si_num,
+ 'Incorrect amount of snapshot instances found; '
+ 'expected 1, found %s.' % si_num)
+
+ si = snapshot_instances[0]
+ self.assertEqual(self.snapshot['id'], si['snapshot_id'],
+ 'Snapshot instance %s has incorrect snapshot id;'
+ ' expected %s, got %s.' % (si['id'],
+ self.snapshot['id'],
+ si['snapshot_id']))
+ if detail:
+ self.assertEqual(self.snapshot['share_id'], si['share_id'])
+
+ for key in si:
+ self.assertIn(key, expected_keys)
+ self.assertEqual(len(expected_keys), len(si))
+
+ @test.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+ def test_list_snapshot_instances(self):
+ """Test that we get at least the snapshot instance."""
+ snapshot_instances = self.shares_v2_client.list_snapshot_instances()
+
+ snapshot_ids = [si['snapshot_id'] for si in snapshot_instances]
+
+ msg = ('Snapshot instance for snapshot %s was not found.' %
+ self.snapshot['id'])
+ self.assertIn(self.snapshot['id'], snapshot_ids, msg)
+
+ @test.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+ def test_get_snapshot_instance(self):
+ instances = self.shares_v2_client.list_snapshot_instances(
+ snapshot_id=self.snapshot['id'])
+ instance_detail = self.shares_v2_client.get_snapshot_instance(
+ instance_id=instances[0]['id'])
+
+ expected_keys = (
+ 'id', 'created_at', 'updated_at', 'progress', 'provider_location',
+ 'share_id', 'share_instance_id', 'snapshot_id', 'status',
+ )
+
+ for key in instance_detail:
+ self.assertIn(key, expected_keys)
+ self.assertEqual(len(expected_keys), len(instance_detail))
+ self.assertEqual(self.snapshot['id'], instance_detail['snapshot_id'])
+ self.assertEqual(self.snapshot['share_id'],
+ instance_detail['share_id'])
+ self.assertEqual(self.snapshot['provider_location'],
+ instance_detail['provider_location'])
+
+ @test.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+ def test_reset_snapshot_instance_status_and_delete(self):
+ """Test resetting a snapshot instance's status attribute."""
+ snapshot = self.create_snapshot_wait_for_active(self.share["id"])
+
+ snapshot_instances = self.shares_v2_client.list_snapshot_instances(
+ snapshot_id=snapshot['id'])
+
+ sii = snapshot_instances[0]['id']
+
+ for status in ("error", "available"):
+ self.shares_v2_client.reset_snapshot_instance_status(
+ sii, status=status)
+ self.shares_v2_client.wait_for_snapshot_instance_status(
+ sii, expected_status=status)
+ self.shares_v2_client.delete_snapshot(snapshot['id'])
+ self.shares_v2_client.wait_for_resource_deletion(
+ snapshot_id=snapshot['id'])
diff --git a/manila_tempest_tests/tests/api/admin/test_share_snapshot_instances_negative.py b/manila_tempest_tests/tests/api/admin/test_share_snapshot_instances_negative.py
new file mode 100644
index 0000000..b76481c
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_share_snapshot_instances_negative.py
@@ -0,0 +1,88 @@
+# Copyright 2016 Huawei
+# 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.
+
+from tempest import config
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+import testtools
+
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+@testtools.skipUnless(CONF.share.run_snapshot_tests,
+ 'Snapshot tests are disabled.')
+@base.skip_if_microversion_lt("2.19")
+class SnapshotInstancesNegativeTest(base.BaseSharesMixedTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(SnapshotInstancesNegativeTest, cls).resource_setup()
+ cls.admin_client = cls.admin_shares_v2_client
+ cls.member_client = cls.shares_v2_client
+ cls.share = cls.create_share(client=cls.admin_client)
+ cls.snapshot = cls.create_snapshot_wait_for_active(
+ cls.share["id"], client=cls.admin_client)
+
+ @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+ def test_list_snapshot_instances_with_snapshot_by_non_admin(self):
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.member_client.list_snapshot_instances,
+ snapshot_id=self.snapshot['id'])
+
+ @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+ def test_get_snapshot_instance_by_non_admin(self):
+ instances = self.admin_client.list_snapshot_instances(
+ snapshot_id=self.snapshot['id'])
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.member_client.get_snapshot_instance,
+ instance_id=instances[0]['id'])
+
+ @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+ def test_reset_snapshot_instance_status_by_non_admin(self):
+ instances = self.admin_client.list_snapshot_instances(
+ snapshot_id=self.snapshot['id'])
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.member_client.reset_snapshot_instance_status,
+ instances[0]['id'],
+ 'error')
+
+
+@testtools.skipUnless(CONF.share.run_snapshot_tests,
+ 'Snapshot tests are disabled.')
+@base.skip_if_microversion_lt("2.19")
+class SnapshotInstancesNegativeNoResourceTest(base.BaseSharesMixedTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(SnapshotInstancesNegativeNoResourceTest, cls).resource_setup()
+ cls.admin_client = cls.admin_shares_v2_client
+ cls.member_client = cls.shares_v2_client
+
+ @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API])
+ def test_get_snapshot_instance_with_non_existent_instance(self):
+ self.assertRaises(lib_exc.NotFound,
+ self.admin_client.get_snapshot_instance,
+ instance_id="nonexistent_instance")
+
+ @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API])
+ def test_list_snapshot_instances_by_non_admin(self):
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.member_client.list_snapshot_instances)