diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py index d2babcb3f0..32dd124784 100644 --- a/tempest/common/rest_client.py +++ b/tempest/common/rest_client.py @@ -132,7 +132,13 @@ def keystone_auth(self, user, password, auth_url, service, tenant_name): 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 68c7a86f92..863d9a82ed 100644 --- a/tempest/services/volume/json/volumes_client.py +++ b/tempest/services/volume/json/volumes_client.py @@ -92,6 +92,25 @@ def delete_volume(self, volume_id): """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 41f08fe966..1f36ceb36f 100644 --- a/tempest/tests/volume/base.py +++ b/tempest/tests/volume/base.py @@ -50,6 +50,9 @@ def setUpClass(cls): 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 = {} @@ -63,6 +66,7 @@ def setUpClass(cls): 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 0000000000..2b6028e6aa --- /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')