Merge "Adds client API and tests for volume attachments"
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index d2babcb..32dd124 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -132,7 +132,13 @@
 
             mgmt_url = None
             for ep in auth_data['serviceCatalog']:
-                if ep["type"] == service:
+                if ep["type"] == service and service != 'volume':
+                    mgmt_url = ep['endpoints'][self.region][self.endpoint_url]
+                    tenant_id = auth_data['token']['tenant']['id']
+                    break
+
+                elif ep["type"] == service and ep['name'] == 'cinder' \
+                    and service == 'volume':
                     mgmt_url = ep['endpoints'][self.region][self.endpoint_url]
                     tenant_id = auth_data['token']['tenant']['id']
                     break
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index c2e8e45..3a42d51 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -92,6 +92,25 @@
         """Deletes the Specified Volume"""
         return self.delete("volumes/%s" % str(volume_id))
 
+    def attach_volume(self, volume_id, instance_uuid, mountpoint):
+        """Attaches a volume to a given instance on a given mountpoint"""
+        post_body = {
+                    'instance_uuid': instance_uuid,
+                    'mountpoint': mountpoint
+                    }
+        post_body = json.dumps({'os-attach': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        return resp, body
+
+    def detach_volume(self, volume_id):
+        """Detaches a volume from an instance"""
+        post_body = {}
+        post_body = json.dumps({'os-detach': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        return resp, body
+
     def wait_for_volume_status(self, volume_id, status):
         """Waits for a Volume to reach a given status"""
         resp, body = self.get_volume(volume_id)
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index c150395..2e016d5 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -49,6 +49,9 @@
 
         cls.os = os
         cls.volumes_client = os.volumes_client
+        cls.servers_client = os.servers_client
+        cls.image_ref = cls.config.compute.image_ref
+        cls.flavor_ref = cls.config.compute.flavor_ref
         cls.build_interval = cls.config.volume.build_interval
         cls.build_timeout = cls.config.volume.build_timeout
         cls.volumes = {}
@@ -62,6 +65,7 @@
                                              cls.volumes_client.service,
                                              cls.os.tenant_name)
         except exceptions.EndpointNotFound:
+            cls.clear_isolated_creds()
             raise nose.SkipTest(skip_msg)
 
     @classmethod
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
new file mode 100644
index 0000000..2b6028e
--- /dev/null
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -0,0 +1,90 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# 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 nose.plugins.attrib import attr
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.volume.base import BaseVolumeTest
+
+
+class VolumesActionsTest(BaseVolumeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumesActionsTest, cls).setUpClass()
+        cls.client = cls.volumes_client
+        cls.servers_client = cls.servers_client
+
+        # Create a test shared instance and volume for attach/detach tests
+        srv_name = rand_name('Instance-')
+        vol_name = rand_name('Volume-')
+        resp, cls.server = cls.servers_client.create_server(srv_name,
+                                                            cls.image_ref,
+                                                            cls.flavor_ref)
+        cls.servers_client.wait_for_server_status(cls.server['id'], 'ACTIVE')
+
+        resp, cls.volume = cls.client.create_volume(size=1,
+                                                    display_name=vol_name)
+        cls.client.wait_for_volume_status(cls.volume['id'], 'available')
+
+    @classmethod
+    def tearDownClass(cls):
+        super(VolumesActionsTest, cls).tearDownClass()
+        # Delete the test instance and volume
+        cls.client.delete_volume(cls.volume['id'])
+        cls.servers_client.delete_server(cls.server['id'])
+
+    @attr(type='smoke')
+    def test_attach_detach_volume_to_instance(self):
+        """Volume is attached and detached successfully from an instance"""
+        try:
+            mountpoint = '/dev/vdc'
+            resp, body = self.client.attach_volume(self.volume['id'],
+                                                   self.server['id'],
+                                                   mountpoint)
+            self.assertEqual(202, resp.status)
+            self.client.wait_for_volume_status(self.volume['id'], 'in-use')
+        except:
+            self.fail("Could not attach volume to instance")
+        finally:
+            # Detach the volume from the instance
+            resp, body = self.client.detach_volume(self.volume['id'])
+            self.assertEqual(202, resp.status)
+            self.client.wait_for_volume_status(self.volume['id'], 'available')
+
+    def test_get_volume_attachment(self):
+        """Verify that a volume's attachment information is retrieved"""
+        mountpoint = '/dev/vdc'
+        resp, body = self.client.attach_volume(self.volume['id'],
+                                                   self.server['id'],
+                                                   mountpoint)
+        self.client.wait_for_volume_status(self.volume['id'], 'in-use')
+        self.assertEqual(202, resp.status)
+        try:
+            resp, volume = self.client.get_volume(self.volume['id'])
+            self.assertEqual(200, resp.status)
+            self.assertTrue('attachments' in volume)
+            attachment = volume['attachments'][0]
+            self.assertEqual(mountpoint, attachment['device'])
+            self.assertEqual(self.server['id'], attachment['server_id'])
+            self.assertEqual(self.volume['id'], attachment['id'])
+            self.assertEqual(self.volume['id'], attachment['volume_id'])
+        except:
+            self.fail("Could not get attachment details from volume")
+        finally:
+            self.client.detach_volume(self.volume['id'])
+            self.client.wait_for_volume_status(self.volume['id'], 'available')