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')