Merge "Minor changes to scenario manager"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 025d0f3..0e11b90 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -770,6 +770,11 @@
# value)
#leave_dirty_stack=false
+# Allows a full cleaning process after a stress test. Caution
+# : this cleanup will remove every objects of every tenant.
+# (boolean value)
+#full_clean_stack=false
+
[telemetry]
@@ -835,6 +840,9 @@
# (boolean value)
#multi_backend=false
+# Runs Cinder volumes backup test (boolean value)
+#backup=true
+
# Is the v1 volume API enabled (boolean value)
#api_v1=true
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index 898cccc..a4e6c17 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -13,11 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from testtools.matchers import Contains
+from testtools import matchers
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class UsersTestJSON(base.BaseIdentityV2AdminTest):
@@ -30,7 +30,7 @@
cls.alt_password = data_utils.rand_name('pass_')
cls.alt_email = cls.alt_user + '@testmail.tm'
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_create_user(self):
# Create a user
self.data.setup_test_tenant()
@@ -41,7 +41,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual(self.alt_user, user['name'])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_create_user_with_enabled(self):
# Create a user with enabled : False
self.data.setup_test_tenant()
@@ -55,7 +55,7 @@
self.assertEqual('false', str(user['enabled']).lower())
self.assertEqual(self.alt_email, user['email'])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_update_user(self):
# Test case to check if updating of user attributes is successful.
test_user = data_utils.rand_name('test_user_')
@@ -83,7 +83,7 @@
self.assertEqual(u_email2, updated_user['email'])
self.assertEqual('false', str(updated_user['enabled']).lower())
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_delete_user(self):
# Delete a user
test_user = data_utils.rand_name('test_user_')
@@ -95,7 +95,7 @@
resp, body = self.client.delete_user(user['id'])
self.assertEqual('204', resp['status'])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_user_authentication(self):
# Valid user's token is authenticated
self.data.setup_test_user()
@@ -108,7 +108,7 @@
self.data.test_tenant)
self.assertEqual('200', resp['status'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_authentication_request_without_token(self):
# Request for token authentication with a valid token in header
self.data.setup_test_user()
@@ -125,16 +125,16 @@
self.assertEqual('200', resp['status'])
self.client.auth_provider.clear_auth()
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_get_users(self):
# Get a list of users and find the test user
self.data.setup_test_user()
resp, users = self.client.get_users()
self.assertThat([u['name'] for u in users],
- Contains(self.data.test_user),
+ matchers.Contains(self.data.test_user),
"Could not find %s" % self.data.test_user)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_users_for_tenant(self):
# Return a list of all users for a tenant
self.data.setup_test_tenant()
@@ -167,7 +167,7 @@
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_users_with_roles_for_tenant(self):
# Return list of users on tenant when roles are assigned to users
self.data.setup_test_user()
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 802113a..9629213 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -19,7 +19,7 @@
from tempest.test import attr
-class UsersTestJSON(base.BaseIdentityV3AdminTest):
+class TokensV3TestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
@attr(type='smoke')
@@ -51,5 +51,5 @@
subject_token)
-class UsersTestXML(UsersTestJSON):
+class TokensV3TestXML(TokensV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index c2eef36..cae20ad 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -14,11 +14,11 @@
import re
from tempest.api.identity import base
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.openstack.common import timeutils
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -49,10 +49,10 @@
self.assertIsNotNone(self.trustor_project_id)
# Create a trustor User
- self.trustor_username = rand_name('user-')
+ self.trustor_username = data_utils.rand_name('user-')
u_desc = self.trustor_username + 'description'
u_email = self.trustor_username + '@testmail.xx'
- self.trustor_password = rand_name('pass-')
+ self.trustor_password = data_utils.rand_name('pass-')
resp, user = self.client.create_user(
self.trustor_username,
description=u_desc,
@@ -63,8 +63,8 @@
self.trustor_user_id = user['id']
# And two roles, one we'll delegate and one we won't
- self.delegated_role = rand_name('DelegatedRole-')
- self.not_delegated_role = rand_name('NotDelegatedRole-')
+ self.delegated_role = data_utils.rand_name('DelegatedRole-')
+ self.not_delegated_role = data_utils.rand_name('NotDelegatedRole-')
resp, role = self.client.create_role(self.delegated_role)
self.assertEqual(resp['status'], '201')
@@ -196,7 +196,7 @@
self.create_trustor_and_roles()
self.addCleanup(self.cleanup_user_and_roles)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_trust_impersonate(self):
# Test case to check we can create, get and delete a trust
# updates are not supported for trusts
@@ -208,7 +208,7 @@
self.check_trust_roles()
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_trust_noimpersonate(self):
# Test case to check we can create, get and delete a trust
# with impersonation=False
@@ -220,7 +220,7 @@
self.check_trust_roles()
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_trust_expire(self):
# Test case to check we can create, get and delete a trust
# with an expiry specified
@@ -236,7 +236,7 @@
self.check_trust_roles()
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_trust_expire_invalid(self):
# Test case to check we can check an invlaid expiry time
# is rejected with the correct error
@@ -246,7 +246,7 @@
self.create_trust,
expires=expires_str)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_get_trusts_query(self):
self.create_trust()
resp, trusts_get = self.trustor_client.get_trusts(
@@ -255,7 +255,7 @@
self.assertEqual(1, len(trusts_get))
self.validate_trust(trusts_get[0], summary=True)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_get_trusts_all(self):
self.create_trust()
resp, trusts_get = self.client.get_trusts()
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index 4267c1d..5130f87 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -15,10 +15,10 @@
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
-from tempest.common.utils.linux.remote_client import RemoteClient
+from tempest.common.utils.linux import remote_client
from tempest import config
from tempest.openstack.common import log as logging
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -147,7 +147,7 @@
'network': cls._get_default_network()['id']
})
- @attr(type='slow')
+ @test.attr(type='slow')
@testtools.skipIf(existing_keypair, 'Server ssh tests are disabled.')
def test_can_log_into_created_server(self):
@@ -166,11 +166,12 @@
body['physical_resource_id'])
# Check that the user can authenticate with the generated password
- linux_client = RemoteClient(server, 'ec2-user',
- pkey=self.keypair['private_key'])
+ linux_client = remote_client.RemoteClient(server, 'ec2-user',
+ pkey=self.keypair[
+ 'private_key'])
linux_client.validate_authentication()
- @attr(type='slow')
+ @test.attr(type='slow')
def test_stack_wait_condition_data(self):
sid = self.stack_identifier
diff --git a/tempest/api/utils.py b/tempest/api/utils.py
index c62dc8d..00c93b7 100644
--- a/tempest/api/utils.py
+++ b/tempest/api/utils.py
@@ -15,7 +15,7 @@
"""Common utilities used in testing."""
-from tempest.test import BaseTestCase
+from tempest import test
class skip_unless_attr(object):
@@ -30,7 +30,7 @@
"""Wrapped skipper function."""
testobj = args[0]
if not getattr(testobj, self.attr, False):
- raise BaseTestCase.skipException(self.message)
+ raise test.BaseTestCase.skipException(self.message)
func(*args, **kw)
_skipper.__name__ = func.__name__
_skipper.__doc__ = func.__doc__
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
new file mode 100644
index 0000000..47094f0
--- /dev/null
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -0,0 +1,67 @@
+# Copyright 2014 OpenStack Foundation
+# 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.volume.base import BaseVolumeV1AdminTest
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest.openstack.common import log as logging
+from tempest import test
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class VolumesBackupsTest(BaseVolumeV1AdminTest):
+ _interface = "json"
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumesBackupsTest, cls).setUpClass()
+
+ if not CONF.volume_feature_enabled.backup:
+ raise cls.skipException("Cinder backup feature disabled")
+
+ cls.volumes_adm_client = cls.os_adm.volumes_client
+ cls.backups_adm_client = cls.os_adm.backups_client
+ cls.volume = cls.create_volume()
+
+ @test.attr(type='smoke')
+ def test_volume_backup_create_get_restore_delete(self):
+ backup_name = data_utils.rand_name('Backup')
+ create_backup = self.backups_adm_client.create_backup
+ resp, backup = create_backup(self.volume['id'],
+ name=backup_name)
+ self.assertEqual(202, resp.status)
+ self.addCleanup(self.backups_adm_client.delete_backup,
+ backup['id'])
+ self.assertEqual(backup['name'], backup_name)
+ self.volumes_adm_client.wait_for_volume_status(self.volume['id'],
+ 'available')
+ self.backups_adm_client.wait_for_backup_status(backup['id'],
+ 'available')
+
+ resp, backup = self.backups_adm_client.get_backup(backup['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(backup['name'], backup_name)
+
+ resp, restore = self.backups_adm_client.restore_backup(backup['id'])
+ self.assertEqual(202, resp.status)
+ self.addCleanup(self.volumes_adm_client.delete_volume,
+ restore['volume_id'])
+ self.assertEqual(restore['backup_id'], backup['id'])
+ self.backups_adm_client.wait_for_backup_status(backup['id'],
+ 'available')
+ self.volumes_adm_client.wait_for_volume_status(restore['volume_id'],
+ 'available')
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 6b6f638..8824977 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -106,6 +106,7 @@
super(BaseVolumeV1Test, cls).setUpClass()
cls.snapshots_client = cls.os.snapshots_client
cls.volumes_client = cls.os.volumes_client
+ cls.backups_client = cls.os.backups_client
cls.volumes_extension_client = cls.os.volumes_extension_client
@classmethod
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index 7d2216d..e94c700 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from testtools.matchers import ContainsAll
+from testtools import matchers
from tempest.api.volume import base
from tempest import test
@@ -52,7 +52,7 @@
# Get the metadata of the volume
resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertEqual(200, resp.status)
- self.assertThat(body.items(), ContainsAll(metadata.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Delete one item metadata of the volume
rsp, body = self.volumes_client.delete_volume_metadata_item(
self.volume_id,
@@ -61,7 +61,7 @@
resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertNotIn("key1", body)
del metadata["key1"]
- self.assertThat(body.items(), ContainsAll(metadata.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
@test.attr(type='gate')
def test_update_volume_metadata(self):
@@ -81,7 +81,7 @@
# Get the metadata of the volume
resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertEqual(200, resp.status)
- self.assertThat(body.items(), ContainsAll(metadata.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Update metadata
resp, body = self.volumes_client.update_volume_metadata(
self.volume_id,
@@ -90,7 +90,7 @@
# Get the metadata of the volume
resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertEqual(200, resp.status)
- self.assertThat(body.items(), ContainsAll(update.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(update.items()))
@test.attr(type='gate')
def test_update_volume_metadata_item(self):
@@ -107,7 +107,7 @@
self.volume_id,
metadata)
self.assertEqual(200, resp.status)
- self.assertThat(body.items(), ContainsAll(metadata.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Update metadata item
resp, body = self.volumes_client.update_volume_metadata_item(
self.volume_id,
@@ -117,7 +117,7 @@
# Get the metadata of the volume
resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertEqual(200, resp.status)
- self.assertThat(body.items(), ContainsAll(expect.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(expect.items()))
class VolumeMetadataTestXML(VolumeMetadataTest):
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 5924c7e..a22ad32 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -16,9 +16,7 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest import config
-from tempest.test import attr
-from tempest.test import services
-from tempest.test import stresstest
+from tempest import test
CONF = config.CONF
@@ -50,9 +48,9 @@
super(VolumesActionsTest, cls).tearDownClass()
- @stresstest(class_setup_per='process')
- @attr(type='smoke')
- @services('compute')
+ @test.stresstest(class_setup_per='process')
+ @test.attr(type='smoke')
+ @test.services('compute')
def test_attach_detach_volume_to_instance(self):
# Volume is attached and detached successfully from an instance
mountpoint = '/dev/vdc'
@@ -65,9 +63,9 @@
self.assertEqual(202, resp.status)
self.client.wait_for_volume_status(self.volume['id'], 'available')
- @stresstest(class_setup_per='process')
- @attr(type='gate')
- @services('compute')
+ @test.stresstest(class_setup_per='process')
+ @test.attr(type='gate')
+ @test.services('compute')
def test_get_volume_attachment(self):
# Verify that a volume's attachment information is retrieved
mountpoint = '/dev/vdc'
@@ -91,8 +89,8 @@
self.assertEqual(self.volume['id'], attachment['id'])
self.assertEqual(self.volume['id'], attachment['volume_id'])
- @attr(type='gate')
- @services('image')
+ @test.attr(type='gate')
+ @test.services('image')
def test_volume_upload(self):
# NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
# it is shared with the other tests. After it is uploaded in Glance,
@@ -108,7 +106,7 @@
self.image_client.wait_for_image_status(image_id, 'active')
self.client.wait_for_volume_status(self.volume['id'], 'available')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_extend(self):
# Extend Volume Test.
extend_size = int(self.volume['size']) + 1
@@ -119,7 +117,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(int(volume['size']), extend_size)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_reserve_unreserve_volume(self):
# Mark volume as reserved.
resp, body = self.client.reserve_volume(self.volume['id'])
@@ -139,7 +137,7 @@
def _is_true(self, val):
return val in ['true', 'True', True]
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_readonly_update(self):
# Update volume readonly true
readonly = True
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index ce29b82..175da01 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -13,13 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from testtools.matchers import ContainsAll
+from testtools import matchers
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest import config
-from tempest.test import attr
-from tempest.test import services
+from tempest import test
CONF = config.CONF
@@ -78,7 +77,7 @@
'The fetched Volume id is different '
'from the created Volume')
self.assertThat(fetched_volume['metadata'].items(),
- ContainsAll(metadata.items()),
+ matchers.ContainsAll(metadata.items()),
'The fetched Volume metadata misses data '
'from the created Volume')
@@ -114,7 +113,7 @@
self.assertEqual(new_v_name, updated_volume['display_name'])
self.assertEqual(new_desc, updated_volume['display_description'])
self.assertThat(updated_volume['metadata'].items(),
- ContainsAll(metadata.items()),
+ matchers.ContainsAll(metadata.items()),
'The fetched Volume metadata misses data '
'from the created Volume')
# Test volume create when display_name is none and display_description
@@ -146,16 +145,16 @@
if 'imageRef' not in kwargs:
self.assertEqual(boot_flag, False)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_volume_create_get_update_delete(self):
self._volume_create_get_update_delete()
- @attr(type='smoke')
- @services('image')
+ @test.attr(type='smoke')
+ @test.services('image')
def test_volume_create_get_update_delete_from_image(self):
self._volume_create_get_update_delete(imageRef=CONF.compute.image_ref)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_create_get_update_delete_as_clone(self):
origin = self.create_volume()
self._volume_create_get_update_delete(source_volid=origin['id'])
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 049544d..c356342 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -18,8 +18,8 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
-from tempest.test import attr
-from testtools.matchers import ContainsAll
+from tempest import test
+from testtools import matchers
LOG = logging.getLogger(__name__)
@@ -111,12 +111,12 @@
('details' if with_detail else '', key)
if key == 'metadata':
self.assertThat(volume[key].items(),
- ContainsAll(params[key].items()),
+ matchers.ContainsAll(params[key].items()),
msg)
else:
self.assertEqual(params[key], volume[key], msg)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_volume_list(self):
# Get a list of Volumes
# Fetch all volumes
@@ -125,7 +125,7 @@
self.assertVolumesIn(fetched_list, self.volume_list,
fields=VOLUME_FIELDS)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_with_details(self):
# Get a list of Volumes with details
# Fetch all Volumes
@@ -133,7 +133,7 @@
self.assertEqual(200, resp.status)
self.assertVolumesIn(fetched_list, self.volume_list)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'display_name': volume['display_name']}
@@ -143,7 +143,7 @@
self.assertEqual(fetched_vol[0]['display_name'],
volume['display_name'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_details_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'display_name': volume['display_name']}
@@ -153,7 +153,7 @@
self.assertEqual(fetched_vol[0]['display_name'],
volume['display_name'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volumes_list_by_status(self):
params = {'status': 'available'}
resp, fetched_list = self.client.list_volumes(params)
@@ -163,7 +163,7 @@
self.assertVolumesIn(fetched_list, self.volume_list,
fields=VOLUME_FIELDS)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volumes_list_details_by_status(self):
params = {'status': 'available'}
resp, fetched_list = self.client.list_volumes_with_detail(params)
@@ -172,7 +172,7 @@
self.assertEqual('available', volume['status'])
self.assertVolumesIn(fetched_list, self.volume_list)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volumes_list_by_availability_zone(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
@@ -184,7 +184,7 @@
self.assertVolumesIn(fetched_list, self.volume_list,
fields=VOLUME_FIELDS)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volumes_list_details_by_availability_zone(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
@@ -195,19 +195,19 @@
self.assertEqual(zone, volume['availability_zone'])
self.assertVolumesIn(fetched_list, self.volume_list)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_with_param_metadata(self):
# Test to list volumes when metadata param is given
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_with_detail_param_metadata(self):
# Test to list volumes details when metadata param is given
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params, with_detail=True)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_param_display_name_and_status(self):
# Test to list volume when display name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
@@ -215,7 +215,7 @@
'status': 'available'}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_volume_list_with_detail_param_display_name_and_status(self):
# Test to list volume when name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
diff --git a/tempest/clients.py b/tempest/clients.py
index d0eb1cc..3db05e5 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -148,6 +148,7 @@
VolumeHostsClientJSON
from tempest.services.volume.json.admin.volume_types_client import \
VolumeTypesClientJSON
+from tempest.services.volume.json.backups_client import BackupsClientJSON
from tempest.services.volume.json.extensions_client import \
ExtensionsClientJSON as VolumeExtensionClientJSON
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
@@ -158,6 +159,7 @@
VolumeHostsClientXML
from tempest.services.volume.xml.admin.volume_types_client import \
VolumeTypesClientXML
+from tempest.services.volume.xml.backups_client import BackupsClientXML
from tempest.services.volume.xml.extensions_client import \
ExtensionsClientXML as VolumeExtensionClientXML
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
@@ -212,6 +214,7 @@
auth_provider)
self.floating_ips_client = FloatingIPsClientXML(
auth_provider)
+ self.backups_client = BackupsClientXML(auth_provider)
self.snapshots_client = SnapshotsClientXML(auth_provider)
self.volumes_client = VolumesClientXML(auth_provider)
self.volumes_v2_client = VolumesV2ClientXML(auth_provider)
@@ -277,6 +280,7 @@
auth_provider)
self.floating_ips_client = FloatingIPsClientJSON(
auth_provider)
+ self.backups_client = BackupsClientJSON(auth_provider)
self.snapshots_client = SnapshotsClientJSON(auth_provider)
self.volumes_client = VolumesClientJSON(auth_provider)
self.volumes_v2_client = VolumesV2ClientJSON(auth_provider)
diff --git a/tempest/config.py b/tempest/config.py
index 210c857..49d65f9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -397,6 +397,9 @@
cfg.BoolOpt('multi_backend',
default=False,
help="Runs Cinder multi-backend test (requires 2 backends)"),
+ cfg.BoolOpt('backup',
+ default=True,
+ help='Runs Cinder volumes backup test'),
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled extensions with a special entry all '
@@ -606,7 +609,12 @@
default=False,
help='Prevent the cleaning (tearDownClass()) between'
' each stress test run if an exception occurs'
- ' during this run.')
+ ' during this run.'),
+ cfg.BoolOpt('full_clean_stack',
+ default=False,
+ help='Allows a full cleaning process after a stress test.'
+ ' Caution : this cleanup will remove every objects of'
+ ' every tenant.')
]
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 3b3f3eb..ac88faa 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -100,6 +100,10 @@
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
+class VolumeBackupException(TempestException):
+ message = "Volume backup %(backup_id)s failed and is in ERROR status"
+
+
class StackBuildErrorException(TempestException):
message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
"due to '%(stack_status_reason)s'")
diff --git a/tempest/services/volume/json/backups_client.py b/tempest/services/volume/json/backups_client.py
new file mode 100644
index 0000000..baaf5a0
--- /dev/null
+++ b/tempest/services/volume/json/backups_client.py
@@ -0,0 +1,89 @@
+# Copyright 2014 OpenStack Foundation
+# 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 json
+import time
+
+from tempest.common import rest_client
+from tempest import config
+from tempest import exceptions
+
+CONF = config.CONF
+
+
+class BackupsClientJSON(rest_client.RestClient):
+ """
+ Client class to send CRUD Volume backup API requests to a Cinder endpoint
+ """
+
+ def __init__(self, auth_provider):
+ super(BackupsClientJSON, self).__init__(auth_provider)
+ self.service = CONF.volume.catalog_type
+ self.build_interval = CONF.volume.build_interval
+ self.build_timeout = CONF.volume.build_timeout
+
+ def create_backup(self, volume_id, container=None, name=None,
+ description=None):
+ """Creates a backup of volume."""
+ post_body = {'volume_id': volume_id}
+ if container:
+ post_body['container'] = container
+ if name:
+ post_body['name'] = name
+ if description:
+ post_body['description'] = description
+ post_body = json.dumps({'backup': post_body})
+ resp, body = self.post('backups', post_body)
+ body = json.loads(body)
+ return resp, body['backup']
+
+ def restore_backup(self, backup_id, volume_id=None):
+ """Restore volume from backup."""
+ post_body = {'volume_id': volume_id}
+ post_body = json.dumps({'restore': post_body})
+ resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
+ body = json.loads(body)
+ return resp, body['restore']
+
+ def delete_backup(self, backup_id):
+ """Delete a backup of volume."""
+ resp, body = self.delete('backups/%s' % (str(backup_id)))
+ return resp, body
+
+ def get_backup(self, backup_id):
+ """Returns the details of a single backup."""
+ url = "backups/%s" % str(backup_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['backup']
+
+ def wait_for_backup_status(self, backup_id, status):
+ """Waits for a Backup to reach a given status."""
+ resp, body = self.get_backup(backup_id)
+ backup_status = body['status']
+ start = int(time.time())
+
+ while backup_status != status:
+ time.sleep(self.build_interval)
+ resp, body = self.get_backup(backup_id)
+ backup_status = body['status']
+ if backup_status == 'error':
+ raise exceptions.VolumeBackupException(backup_id=backup_id)
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = ('Volume backup %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (backup_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
diff --git a/tempest/services/volume/xml/backups_client.py b/tempest/services/volume/xml/backups_client.py
new file mode 100644
index 0000000..6a71f8b
--- /dev/null
+++ b/tempest/services/volume/xml/backups_client.py
@@ -0,0 +1,25 @@
+# Copyright 2014 OpenStack Foundation
+# 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.common.rest_client import RestClientXML
+
+
+class BackupsClientXML(RestClientXML):
+ """
+ Client class to send CRUD Volume Backup API requests to a Cinder endpoint
+ """
+
+ #TODO(gfidente): XML client isn't yet implemented because of bug 1270589
+ pass
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index d4689c4..3715636 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -220,7 +220,7 @@
LOG.info("Run %d actions (%d failed)" %
(sum_runs, sum_fails))
- if not had_errors:
+ if not had_errors and CONF.stress.full_clean_stack:
LOG.info("cleaning up")
cleanup.cleanup()
if had_errors: