Merge "Add test for volume snapshot in compute api"
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 1f53f9a..8d68986 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -75,6 +75,7 @@
         cls.networks_client = cls.os.networks_client
         cls.limits_client = cls.os.limits_client
         cls.volumes_extensions_client = cls.os.volumes_extensions_client
+        cls.snapshots_extensions_client = cls.os.snapshots_extensions_client
         cls.interfaces_client = cls.os.interfaces_client
         cls.fixed_ips_client = cls.os.fixed_ips_client
         cls.availability_zone_client = cls.os.availability_zone_client
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
new file mode 100644
index 0000000..a00c0ba
--- /dev/null
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -0,0 +1,73 @@
+# Copyright 2015 Fujitsu(fnst) Corporation
+# 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.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.common import waiters
+from tempest import config
+from tempest import test
+
+
+CONF = config.CONF
+
+
+class VolumesSnapshotsTestJSON(base.BaseV2ComputeTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(VolumesSnapshotsTestJSON, cls).skip_checks()
+        if not CONF.service_available.cinder:
+            skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(VolumesSnapshotsTestJSON, cls).setup_clients()
+        cls.volumes_client = cls.volumes_extensions_client
+        cls.snapshots_client = cls.snapshots_extensions_client
+
+    @test.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f')
+    def test_volume_snapshot_create_get_list_delete(self):
+        v_name = data_utils.rand_name('Volume')
+        volume = self.volumes_client.create_volume(
+            size=CONF.volume.volume_size,
+            display_name=v_name)['volume']
+        self.addCleanup(self.delete_volume, volume['id'])
+        waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+                                       'available')
+        s_name = data_utils.rand_name('Snapshot')
+        # Create snapshot
+        snapshot = self.snapshots_client.create_snapshot(
+            volume['id'],
+            display_name=s_name)['snapshot']
+
+        def delete_snapshot(snapshot_id):
+            waiters.wait_for_snapshot_status(self.snapshots_client,
+                                             snapshot_id,
+                                             'available')
+            # Delete snapshot
+            self.snapshots_client.delete_snapshot(snapshot_id)
+            self.snapshots_client.wait_for_resource_deletion(snapshot_id)
+
+        self.addCleanup(delete_snapshot, snapshot['id'])
+        self.assertEqual(volume['id'], snapshot['volumeId'])
+        # Get snapshot
+        fetched_snapshot = self.snapshots_client.show_snapshot(
+            snapshot['id'])['snapshot']
+        self.assertEqual(s_name, fetched_snapshot['displayName'])
+        self.assertEqual(volume['id'], fetched_snapshot['volumeId'])
+        # Fetch all snapshots
+        snapshots = self.snapshots_client.list_snapshots()['snapshots']
+        self.assertIn(snapshot['id'], map(lambda x: x['id'], snapshots))
diff --git a/tempest/api_schema/response/compute/v2_1/snapshots.py b/tempest/api_schema/response/compute/v2_1/snapshots.py
new file mode 100644
index 0000000..01a524b
--- /dev/null
+++ b/tempest/api_schema/response/compute/v2_1/snapshots.py
@@ -0,0 +1,61 @@
+# Copyright 2015 Fujitsu(fnst) Corporation
+# 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.
+
+common_snapshot_info = {
+    'type': 'object',
+    'properties': {
+        'id': {'type': 'string'},
+        'volumeId': {'type': 'string'},
+        'status': {'type': 'string'},
+        'size': {'type': 'integer'},
+        'createdAt': {'type': 'string'},
+        'displayName': {'type': ['string', 'null']},
+        'displayDescription': {'type': ['string', 'null']}
+    },
+    'additionalProperties': False,
+    'required': ['id', 'volumeId', 'status', 'size',
+                 'createdAt', 'displayName', 'displayDescription']
+}
+
+create_get_snapshot = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'snapshot': common_snapshot_info
+        },
+        'additionalProperties': False,
+        'required': ['snapshot']
+    }
+}
+
+list_snapshots = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'snapshots': {
+                'type': 'array',
+                'items': common_snapshot_info
+            }
+        },
+        'additionalProperties': False,
+        'required': ['snapshots']
+    }
+}
+
+delete_snapshot = {
+    'status_code': [202]
+}
diff --git a/tempest/clients.py b/tempest/clients.py
index c0d4585..55b32fc 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -73,6 +73,8 @@
     ServerGroupsClient
 from tempest.services.compute.json.servers_client import ServersClient
 from tempest.services.compute.json.services_client import ServicesClient
+from tempest.services.compute.json.snapshots_extensions_client import \
+    SnapshotsExtensionsClient
 from tempest.services.compute.json.tenant_networks_client import \
     TenantNetworksClient
 from tempest.services.compute.json.tenant_usages_client import \
@@ -325,6 +327,8 @@
             self.auth_provider, **params_volume)
         self.compute_versions_client = VersionsClient(self.auth_provider,
                                                       **params_volume)
+        self.snapshots_extensions_client = SnapshotsExtensionsClient(
+            self.auth_provider, **params_volume)
 
     def _set_database_clients(self):
         self.database_flavors_client = DatabaseFlavorsClient(
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 248513e..ecd0385 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -183,6 +183,27 @@
             raise exceptions.TimeoutException(message)
 
 
+def wait_for_snapshot_status(client, snapshot_id, status):
+    """Waits for a Snapshot to reach a given status."""
+    body = client.show_snapshot(snapshot_id)['snapshot']
+    snapshot_status = body['status']
+    start = int(time.time())
+
+    while snapshot_status != status:
+        time.sleep(client.build_interval)
+        body = client.show_snapshot(snapshot_id)['snapshot']
+        snapshot_status = body['status']
+        if snapshot_status == 'error':
+            raise exceptions.SnapshotBuildErrorException(
+                snapshot_id=snapshot_id)
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Snapshot %s failed to reach %s status (current %s) '
+                       'within the required time (%s s).' %
+                       (snapshot_id, status, snapshot_status,
+                        client.build_timeout))
+            raise exceptions.TimeoutException(message)
+
+
 def wait_for_bm_node_status(client, node_id, attr, status):
     """Waits for a baremetal node attribute to reach given status.
 
diff --git a/tempest/services/compute/json/snapshots_extensions_client.py b/tempest/services/compute/json/snapshots_extensions_client.py
new file mode 100644
index 0000000..6902a39
--- /dev/null
+++ b/tempest/services/compute/json/snapshots_extensions_client.py
@@ -0,0 +1,71 @@
+# Copyright 2015 Fujitsu(fnst) Corporation
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+from tempest_lib import exceptions as lib_exc
+
+from tempest.api_schema.response.compute.v2_1 import snapshots as schema
+from tempest.common import service_client
+
+
+class SnapshotsExtensionsClient(service_client.ServiceClient):
+
+    def create_snapshot(self, volume_id, **kwargs):
+        post_body = {
+            'volume_id': volume_id
+        }
+        post_body.update(kwargs)
+        post_body = json.dumps({'snapshot': post_body})
+        resp, body = self.post('os-snapshots', post_body)
+        body = json.loads(body)
+        self.validate_response(schema.create_get_snapshot, resp, body)
+        return service_client.ResponseBody(resp, body)
+
+    def show_snapshot(self, snapshot_id):
+        url = "os-snapshots/%s" % snapshot_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.validate_response(schema.create_get_snapshot, resp, body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_snapshots(self, detail=False, params=None):
+        url = 'os-snapshots'
+
+        if detail:
+            url += '/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        self.validate_response(schema.list_snapshots, resp, body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_snapshot(self, snapshot_id):
+        resp, body = self.delete("os-snapshots/%s" % snapshot_id)
+        self.validate_response(schema.delete_snapshot, resp, body)
+        return service_client.ResponseBody(resp, body)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.show_snapshot(id)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    @property
+    def resource_type(self):
+        """Returns the primary type of resource this client works with."""
+        return 'snapshot'