Merge "Set smoke/gate attributes for tests in "identity""
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 7920ab5..3147859 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -117,6 +117,10 @@
# performed, which requires XenServer pools in case of using XS)
use_block_migration_for_live_migration = false
+# Supports iSCSI block migration - depends on a XAPI supporting
+# relax-xsm-sr-check
+block_migrate_supports_cinder_iscsi = false
+
# By default, rely on the status of the diskConfig extension to
# decide if to execute disk config tests. When set to false, tests
# are forced to skip, regardless of the extension status
@@ -285,3 +289,26 @@
# Status change wait interval
build_interval = 1
+
+[orchestration]
+# Status change wait interval
+build_interval = 1
+
+# Status change wait timout. This may vary across environments as some some
+# tests spawn full VMs, which could be slow if the test is already in a VM.
+build_timeout = 300
+
+# Whether or not Heat is expected to be available
+heat_available = false
+
+# Instance type for tests. Needs to be big enough for a
+# full OS plus the test workload
+instance_type = m1.tiny
+
+# Name of heat-cfntools enabled image to use when launching test instances
+# If not specified, tests that spawn instances will not run
+#image_ref = ubuntu-vm-heat-cfntools
+
+# Name of existing keypair to launch servers with. The default is not to specify
+# any key, which will generate a keypair for each test class
+#keypair_name = heat_key
diff --git a/tempest/README.rst b/tempest/README.rst
index c41ef96..6718ee0 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -12,11 +12,11 @@
to make this clear.
tempest/
- 3rdparty/ - 3rd party api tests
api/ - API tests
cli/ - CLI tests
scenario/ - complex scenario tests
stress/ - stress tests
+ thirdparty/ - 3rd party api tests
whitebox/ - white box testing
Each of these directories contains different types of tests. What
@@ -24,17 +24,6 @@
documented in a README.rst file in the directory.
-3rdparty
-------------
-
-Many openstack components include 3rdparty API support. It is
-completely legitmate for Tempest to include tests of 3rdparty APIs,
-but those should be kept seperate from the normal OpenStack
-validation.
-
-TODO: tempest/tests/boto should become tempest/3rdparty/boto
-
-
api
------------
@@ -88,6 +77,17 @@
moves into here.
+thirdparty
+------------
+
+Many openstack components include 3rdparty API support. It is
+completely legitmate for Tempest to include tests of 3rdparty APIs,
+but those should be kept seperate from the normal OpenStack
+validation.
+
+TODO: tempest/tests/boto should become tempest/3rdparty/boto
+
+
whitebox
----------
diff --git a/tempest/clients.py b/tempest/clients.py
index 9b2c1f5..037a1c4 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -40,6 +40,7 @@
from tempest.services.compute.json.security_groups_client import \
SecurityGroupsClientJSON
from tempest.services.compute.json.servers_client import ServersClientJSON
+from tempest.services.compute.json.services_client import ServicesClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
@@ -59,6 +60,7 @@
from tempest.services.compute.xml.security_groups_client \
import SecurityGroupsClientXML
from tempest.services.compute.xml.servers_client import ServersClientXML
+from tempest.services.compute.xml.services_client import ServicesClientXML
from tempest.services.compute.xml.volumes_extensions_client import \
VolumesExtensionsClientXML
from tempest.services.identity.json.identity_client import IdentityClientJSON
@@ -86,6 +88,8 @@
from tempest.services.object_storage.object_client import ObjectClient
from tempest.services.object_storage.object_client import \
ObjectClientCustomizedHeader
+from tempest.services.orchestration.json.orchestration_client import \
+ OrchestrationClient
from tempest.services.volume.json.admin.volume_types_client import \
VolumeTypesClientJSON
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
@@ -207,6 +211,11 @@
"xml": AggregatesClientXML,
}
+SERVICES_CLIENT = {
+ "json": ServicesClientJSON,
+ "xml": ServicesClientXML,
+}
+
class Manager(object):
@@ -277,6 +286,7 @@
AVAILABILITY_ZONE_CLIENT[interface](*client_args)
self.service_client = SERVICE_CLIENT[interface](*client_args)
self.aggregates_client = AGGREGATES_CLIENT[interface](*client_args)
+ self.services_client = SERVICES_CLIENT[interface](*client_args)
except KeyError:
msg = "Unsupported interface type `%s'" % interface
raise exceptions.InvalidConfiguration(msg)
@@ -287,6 +297,7 @@
self.image_client_v2 = ImageClientV2JSON(*client_args)
self.container_client = ContainerClient(*client_args)
self.object_client = ObjectClient(*client_args)
+ self.orchestration_client = OrchestrationClient(*client_args)
self.ec2api_client = botoclients.APIClientEC2(*client_args)
self.s3_client = botoclients.ObjectClientS3(*client_args)
self.custom_object_client = ObjectClientCustomizedHeader(*client_args)
@@ -337,3 +348,17 @@
conf.compute_admin.password,
conf.compute_admin.tenant_name,
interface=interface)
+
+
+class OrchestrationManager(Manager):
+ """
+ Manager object that uses the admin credentials for its
+ so that heat templates can create users
+ """
+ def __init__(self, interface='json'):
+ conf = config.TempestConfig()
+ base = super(OrchestrationManager, self)
+ base.__init__(conf.identity.admin_username,
+ conf.identity.admin_password,
+ conf.identity.admin_tenant_name,
+ interface=interface)
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 448708e..04cc851 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -47,9 +47,10 @@
self.channel_timeout = float(channel_timeout)
self.buf_size = 1024
- def _get_ssh_connection(self):
+ def _get_ssh_connection(self, sleep=1.5, backoff=1.01):
"""Returns an ssh connection to the specified host."""
_timeout = True
+ bsleep = sleep
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(
paramiko.AutoAddPolicy())
@@ -64,10 +65,10 @@
timeout=self.timeout, pkey=self.pkey)
_timeout = False
break
- except socket.error:
- continue
- except paramiko.AuthenticationException:
- time.sleep(5)
+ except (socket.error,
+ paramiko.AuthenticationException):
+ time.sleep(bsleep)
+ bsleep *= backoff
continue
if _timeout:
raise exceptions.SSHTimeout(host=self.host,
diff --git a/tempest/config.py b/tempest/config.py
index d43c5d7..6d6bc2b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -129,6 +129,10 @@
default=False,
help="Does the test environment use block devices for live "
"migration"),
+ cfg.BoolOpt('block_migrate_supports_cinder_iscsi',
+ default=False,
+ help="Does the test environment block migration support "
+ "cinder iSCSI volumes"),
cfg.BoolOpt('change_password_available',
default=False,
help="Does the test environment support changing the admin "
@@ -351,6 +355,48 @@
for opt in ObjectStoreConfig:
conf.register_opt(opt, group='object-storage')
+
+orchestration_group = cfg.OptGroup(name='orchestration',
+ title='Orchestration Service Options')
+
+OrchestrationGroup = [
+ cfg.StrOpt('catalog_type',
+ default='orchestration',
+ help="Catalog type of the Orchestration service."),
+ cfg.BoolOpt('allow_tenant_isolation',
+ default=False,
+ help="Allows test cases to create/destroy tenants and "
+ "users. This option enables isolated test cases and "
+ "better parallel execution, but also requires that "
+ "OpenStack Identity API admin credentials are known."),
+ cfg.IntOpt('build_interval',
+ default=1,
+ help="Time in seconds between build status checks."),
+ cfg.IntOpt('build_timeout',
+ default=300,
+ help="Timeout in seconds to wait for a stack to build."),
+ cfg.BoolOpt('heat_available',
+ default=False,
+ help="Whether or not Heat is expected to be available"),
+ cfg.StrOpt('instance_type',
+ default='m1.tiny',
+ help="Instance type for tests. Needs to be big enough for a "
+ "full OS plus the test workload"),
+ cfg.StrOpt('image_ref',
+ default=None,
+ help="Name of heat-cfntools enabled image to use when "
+ "launching test instances."),
+ cfg.StrOpt('keypair_name',
+ default=None,
+ help="Name of existing keypair to launch servers with."),
+]
+
+
+def register_orchestration_opts(conf):
+ conf.register_group(orchestration_group)
+ for opt in OrchestrationGroup:
+ conf.register_opt(opt, group='orchestration')
+
boto_group = cfg.OptGroup(name='boto',
title='EC2/S3 options')
BotoConfig = [
@@ -485,6 +531,7 @@
register_network_opts(cfg.CONF)
register_volume_opts(cfg.CONF)
register_object_storage_opts(cfg.CONF)
+ register_orchestration_opts(cfg.CONF)
register_boto_opts(cfg.CONF)
register_compute_admin_opts(cfg.CONF)
register_stress_opts(cfg.CONF)
@@ -495,6 +542,7 @@
self.network = cfg.CONF.network
self.volume = cfg.CONF.volume
self.object_storage = cfg.CONF['object-storage']
+ self.orchestration = cfg.CONF.orchestration
self.boto = cfg.CONF.boto
self.compute_admin = cfg.CONF['compute-admin']
self.stress = cfg.CONF.stress
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 235a2e7..448fbdf 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -90,6 +90,11 @@
message = "Snapshot %(snapshot_id)s failed to build 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'")
+
+
class BadRequest(RestClientException):
message = "Bad request"
diff --git a/tempest/manager.py b/tempest/manager.py
index 6f23727..047ad41 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -17,15 +17,6 @@
import logging
-# Default client libs
-import glanceclient
-import keystoneclient.v2_0.client
-import novaclient.client
-try:
- import quantumclient.v2_0.client
-except ImportError:
- pass
-
import tempest.config
from tempest import exceptions
# Tempest REST Fuzz testing client libs
@@ -86,121 +77,6 @@
pass
-class DefaultClientManager(Manager):
-
- """
- Manager that provides the default clients to access the various
- OpenStack APIs.
- """
-
- NOVACLIENT_VERSION = '2'
-
- def __init__(self):
- super(DefaultClientManager, self).__init__()
- self.compute_client = self._get_compute_client()
- self.image_client = self._get_image_client()
- self.identity_client = self._get_identity_client()
- self.network_client = self._get_network_client()
- self.client_attr_names = [
- 'compute_client',
- 'image_client',
- 'identity_client',
- 'network_client',
- ]
-
- def _get_compute_client(self, username=None, password=None,
- tenant_name=None):
- # Novaclient will not execute operations for anyone but the
- # identified user, so a new client needs to be created for
- # each user that operations need to be performed for.
- if not username:
- username = self.config.identity.username
- if not password:
- password = self.config.identity.password
- if not tenant_name:
- tenant_name = self.config.identity.tenant_name
-
- if None in (username, password, tenant_name):
- msg = ("Missing required credentials for compute client. "
- "username: %(username)s, password: %(password)s, "
- "tenant_name: %(tenant_name)s") % locals()
- raise exceptions.InvalidConfiguration(msg)
-
- auth_url = self.config.identity.uri
- dscv = self.config.identity.disable_ssl_certificate_validation
-
- client_args = (username, password, tenant_name, auth_url)
-
- # Create our default Nova client to use in testing
- service_type = self.config.compute.catalog_type
- return novaclient.client.Client(self.NOVACLIENT_VERSION,
- *client_args,
- service_type=service_type,
- no_cache=True,
- insecure=dscv)
-
- def _get_image_client(self):
- keystone = self._get_identity_client()
- token = keystone.auth_token
- endpoint = keystone.service_catalog.url_for(service_type='image',
- endpoint_type='publicURL')
- dscv = self.config.identity.disable_ssl_certificate_validation
- return glanceclient.Client('1', endpoint=endpoint, token=token,
- insecure=dscv)
-
- def _get_identity_client(self, username=None, password=None,
- tenant_name=None):
- # This identity client is not intended to check the security
- # of the identity service, so use admin credentials by default.
- if not username:
- username = self.config.identity.admin_username
- if not password:
- password = self.config.identity.admin_password
- if not tenant_name:
- tenant_name = self.config.identity.admin_tenant_name
-
- if None in (username, password, tenant_name):
- msg = ("Missing required credentials for identity client. "
- "username: %(username)s, password: %(password)s, "
- "tenant_name: %(tenant_name)s") % locals()
- raise exceptions.InvalidConfiguration(msg)
-
- auth_url = self.config.identity.uri
- dscv = self.config.identity.disable_ssl_certificate_validation
-
- return keystoneclient.v2_0.client.Client(username=username,
- password=password,
- tenant_name=tenant_name,
- auth_url=auth_url,
- insecure=dscv)
-
- def _get_network_client(self):
- # The intended configuration is for the network client to have
- # admin privileges and indicate for whom resources are being
- # created via a 'tenant_id' parameter. This will often be
- # preferable to authenticating as a specific user because
- # working with certain resources (public routers and networks)
- # often requires admin privileges anyway.
- username = self.config.identity.admin_username
- password = self.config.identity.admin_password
- tenant_name = self.config.identity.admin_tenant_name
-
- if None in (username, password, tenant_name):
- msg = ("Missing required credentials for network client. "
- "username: %(username)s, password: %(password)s, "
- "tenant_name: %(tenant_name)s") % locals()
- raise exceptions.InvalidConfiguration(msg)
-
- auth_url = self.config.identity.uri
- dscv = self.config.identity.disable_ssl_certificate_validation
-
- return quantumclient.v2_0.client.Client(username=username,
- password=password,
- tenant_name=tenant_name,
- auth_url=auth_url,
- insecure=dscv)
-
-
class ComputeFuzzClientManager(FuzzClientManager):
"""
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
new file mode 100644
index 0000000..c5fa0d3
--- /dev/null
+++ b/tempest/scenario/README.rst
@@ -0,0 +1,45 @@
+Tempest Guide to Scenario tests
+========
+
+
+What are these tests?
+--------
+
+Scenario tests are "through path" tests of OpenStack
+function. Complicated setups where one part might depend on completion
+of a previous part. They ideally involve the integration between
+multiple OpenStack services to exercise the touch points between them.
+
+An example would be: start with a blank environment, upload a glance
+image, deploy a vm from it, ssh to the guest, make changes, capture
+that vm's image back into glance as a snapshot, and launch a second vm
+from that snapshot.
+
+
+Why are these tests in tempest?
+--------
+This is one of tempests core purposes, testing the integration between
+projects.
+
+
+Scope of these tests
+--------
+Scenario tests should always test at least 2 services in
+interaction. They should use the official python client libraries for
+OpenStack, as they provide a more realistic approach in how people
+will interact with the services.
+
+TODO: once we have service tags, tests should be tagged with which
+services they exercise.
+
+
+Example of a good test
+--------
+While we are looking for interaction of 2 or more services, be
+specific in your interactions. A giant "this is my data center" smoke
+test is hard to debug when it goes wrong.
+
+A flow of interactions between glance and nova, like in the
+introduction, is a good example. Especially if it involves a repeated
+interaction when a resource is setup, modified, detached, and then
+reused later again.
diff --git a/tempest/tests/boto/__init__.py b/tempest/scenario/__init__.py
similarity index 100%
copy from tempest/tests/boto/__init__.py
copy to tempest/scenario/__init__.py
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
new file mode 100644
index 0000000..4f94195
--- /dev/null
+++ b/tempest/scenario/manager.py
@@ -0,0 +1,422 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# Copyright 2013 IBM Corp.
+# 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 logging
+import subprocess
+
+# Default client libs
+import glanceclient
+import keystoneclient.v2_0.client
+import netaddr
+import novaclient.client
+try:
+ # TODO(sdague): is there are reason this is still optional
+ from quantumclient.common import exceptions as exc
+ import quantumclient.v2_0.client
+
+except ImportError:
+ pass
+
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+import tempest.manager
+import tempest.test
+from tempest.tests.network import common as net_common
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OfficialClientManager(tempest.manager.Manager):
+ """
+ Manager that provides access to the official python clients for
+ calling various OpenStack APIs.
+ """
+
+ NOVACLIENT_VERSION = '2'
+
+ def __init__(self):
+ super(OfficialClientManager, self).__init__()
+ self.compute_client = self._get_compute_client()
+ self.image_client = self._get_image_client()
+ self.identity_client = self._get_identity_client()
+ self.network_client = self._get_network_client()
+ self.client_attr_names = [
+ 'compute_client',
+ 'image_client',
+ 'identity_client',
+ 'network_client',
+ ]
+
+ def _get_compute_client(self, username=None, password=None,
+ tenant_name=None):
+ # Novaclient will not execute operations for anyone but the
+ # identified user, so a new client needs to be created for
+ # each user that operations need to be performed for.
+ if not username:
+ username = self.config.identity.username
+ if not password:
+ password = self.config.identity.password
+ if not tenant_name:
+ tenant_name = self.config.identity.tenant_name
+
+ if None in (username, password, tenant_name):
+ msg = ("Missing required credentials for compute client. "
+ "username: %(username)s, password: %(password)s, "
+ "tenant_name: %(tenant_name)s") % locals()
+ raise exceptions.InvalidConfiguration(msg)
+
+ auth_url = self.config.identity.uri
+ dscv = self.config.identity.disable_ssl_certificate_validation
+
+ client_args = (username, password, tenant_name, auth_url)
+
+ # Create our default Nova client to use in testing
+ service_type = self.config.compute.catalog_type
+ return novaclient.client.Client(self.NOVACLIENT_VERSION,
+ *client_args,
+ service_type=service_type,
+ no_cache=True,
+ insecure=dscv)
+
+ def _get_image_client(self):
+ keystone = self._get_identity_client()
+ token = keystone.auth_token
+ endpoint = keystone.service_catalog.url_for(service_type='image',
+ endpoint_type='publicURL')
+ dscv = self.config.identity.disable_ssl_certificate_validation
+ return glanceclient.Client('1', endpoint=endpoint, token=token,
+ insecure=dscv)
+
+ def _get_identity_client(self, username=None, password=None,
+ tenant_name=None):
+ # This identity client is not intended to check the security
+ # of the identity service, so use admin credentials by default.
+ if not username:
+ username = self.config.identity.admin_username
+ if not password:
+ password = self.config.identity.admin_password
+ if not tenant_name:
+ tenant_name = self.config.identity.admin_tenant_name
+
+ if None in (username, password, tenant_name):
+ msg = ("Missing required credentials for identity client. "
+ "username: %(username)s, password: %(password)s, "
+ "tenant_name: %(tenant_name)s") % locals()
+ raise exceptions.InvalidConfiguration(msg)
+
+ auth_url = self.config.identity.uri
+ dscv = self.config.identity.disable_ssl_certificate_validation
+
+ return keystoneclient.v2_0.client.Client(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ auth_url=auth_url,
+ insecure=dscv)
+
+ def _get_network_client(self):
+ # The intended configuration is for the network client to have
+ # admin privileges and indicate for whom resources are being
+ # created via a 'tenant_id' parameter. This will often be
+ # preferable to authenticating as a specific user because
+ # working with certain resources (public routers and networks)
+ # often requires admin privileges anyway.
+ username = self.config.identity.admin_username
+ password = self.config.identity.admin_password
+ tenant_name = self.config.identity.admin_tenant_name
+
+ if None in (username, password, tenant_name):
+ msg = ("Missing required credentials for network client. "
+ "username: %(username)s, password: %(password)s, "
+ "tenant_name: %(tenant_name)s") % locals()
+ raise exceptions.InvalidConfiguration(msg)
+
+ auth_url = self.config.identity.uri
+ dscv = self.config.identity.disable_ssl_certificate_validation
+
+ return quantumclient.v2_0.client.Client(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ auth_url=auth_url,
+ insecure=dscv)
+
+
+class OfficialClientTest(tempest.test.TestCase):
+ """
+ Official Client test base class for scenario testing.
+
+ Official Client tests are tests that have the following characteristics:
+
+ * Test basic operations of an API, typically in an order that
+ a regular user would perform those operations
+ * Test only the correct inputs and action paths -- no fuzz or
+ random input data is sent, only valid inputs.
+ * Use only the default client tool for calling an API
+ """
+
+ manager_class = OfficialClientManager
+
+ @classmethod
+ def tearDownClass(cls):
+ # NOTE(jaypipes): Because scenario tests are typically run in a
+ # specific order, and because test methods in scenario tests
+ # generally create resources in a particular order, we destroy
+ # resources in the reverse order in which resources are added to
+ # the scenario test class object
+ while cls.os_resources:
+ thing = cls.os_resources.pop()
+ LOG.debug("Deleting %r from shared resources of %s" %
+ (thing, cls.__name__))
+
+ try:
+ # OpenStack resources are assumed to have a delete()
+ # method which destroys the resource...
+ thing.delete()
+ except Exception as e:
+ # If the resource is already missing, mission accomplished.
+ if e.__class__.__name__ == 'NotFound':
+ continue
+ raise
+
+ def is_deletion_complete():
+ # Deletion testing is only required for objects whose
+ # existence cannot be checked via retrieval.
+ if isinstance(thing, dict):
+ return True
+ try:
+ thing.get()
+ except Exception as e:
+ # Clients are expected to return an exception
+ # called 'NotFound' if retrieval fails.
+ if e.__class__.__name__ == 'NotFound':
+ return True
+ raise
+ return False
+
+ # Block until resource deletion has completed or timed-out
+ tempest.test.call_until_true(is_deletion_complete, 10, 1)
+
+
+class NetworkScenarioTest(OfficialClientTest):
+ """
+ Base class for network scenario tests
+ """
+
+ @classmethod
+ def check_preconditions(cls):
+ if (cls.config.network.quantum_available):
+ cls.enabled = True
+ #verify that quantum_available is telling the truth
+ try:
+ cls.network_client.list_networks()
+ except exc.EndpointNotFound:
+ cls.enabled = False
+ raise
+ else:
+ cls.enabled = False
+ msg = 'Quantum not available'
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setUpClass(cls):
+ super(NetworkScenarioTest, cls).setUpClass()
+ cls.tenant_id = cls.manager._get_identity_client(
+ cls.config.identity.username,
+ cls.config.identity.password,
+ cls.config.identity.tenant_name).tenant_id
+
+ def _create_keypair(self, client, namestart='keypair-smoke-'):
+ kp_name = rand_name(namestart)
+ keypair = client.keypairs.create(kp_name)
+ try:
+ self.assertEqual(keypair.id, kp_name)
+ self.set_resource(kp_name, keypair)
+ except AttributeError:
+ self.fail("Keypair object not successfully created.")
+ return keypair
+
+ def _create_security_group(self, client, namestart='secgroup-smoke-'):
+ # Create security group
+ sg_name = rand_name(namestart)
+ sg_desc = sg_name + " description"
+ secgroup = client.security_groups.create(sg_name, sg_desc)
+ try:
+ self.assertEqual(secgroup.name, sg_name)
+ self.assertEqual(secgroup.description, sg_desc)
+ self.set_resource(sg_name, secgroup)
+ except AttributeError:
+ self.fail("SecurityGroup object not successfully created.")
+
+ # Add rules to the security group
+ rulesets = [
+ {
+ # ssh
+ 'ip_protocol': 'tcp',
+ 'from_port': 22,
+ 'to_port': 22,
+ 'cidr': '0.0.0.0/0',
+ 'group_id': secgroup.id
+ },
+ {
+ # ping
+ 'ip_protocol': 'icmp',
+ 'from_port': -1,
+ 'to_port': -1,
+ 'cidr': '0.0.0.0/0',
+ 'group_id': secgroup.id
+ }
+ ]
+ for ruleset in rulesets:
+ try:
+ client.security_group_rules.create(secgroup.id, **ruleset)
+ except Exception:
+ self.fail("Failed to create rule in security group.")
+
+ return secgroup
+
+ def _create_network(self, tenant_id, namestart='network-smoke-'):
+ name = rand_name(namestart)
+ body = dict(
+ network=dict(
+ name=name,
+ tenant_id=tenant_id,
+ ),
+ )
+ result = self.network_client.create_network(body=body)
+ network = net_common.DeletableNetwork(client=self.network_client,
+ **result['network'])
+ self.assertEqual(network.name, name)
+ self.set_resource(name, network)
+ return network
+
+ def _list_networks(self):
+ nets = self.network_client.list_networks()
+ return nets['networks']
+
+ def _list_subnets(self):
+ subnets = self.network_client.list_subnets()
+ return subnets['subnets']
+
+ def _list_routers(self):
+ routers = self.network_client.list_routers()
+ return routers['routers']
+
+ def _create_subnet(self, network, namestart='subnet-smoke-'):
+ """
+ Create a subnet for the given network within the cidr block
+ configured for tenant networks.
+ """
+ cfg = self.config.network
+ tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
+ result = None
+ # Repeatedly attempt subnet creation with sequential cidr
+ # blocks until an unallocated block is found.
+ for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
+ body = dict(
+ subnet=dict(
+ ip_version=4,
+ network_id=network.id,
+ tenant_id=network.tenant_id,
+ cidr=str(subnet_cidr),
+ ),
+ )
+ try:
+ result = self.network_client.create_subnet(body=body)
+ break
+ except exc.QuantumClientException as e:
+ is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+ if not is_overlapping_cidr:
+ raise
+ self.assertIsNotNone(result, 'Unable to allocate tenant network')
+ subnet = net_common.DeletableSubnet(client=self.network_client,
+ **result['subnet'])
+ self.assertEqual(subnet.cidr, str(subnet_cidr))
+ self.set_resource(rand_name(namestart), subnet)
+ return subnet
+
+ def _create_port(self, network, namestart='port-quotatest-'):
+ name = rand_name(namestart)
+ body = dict(
+ port=dict(name=name,
+ network_id=network.id,
+ tenant_id=network.tenant_id))
+ result = self.network_client.create_port(body=body)
+ self.assertIsNotNone(result, 'Unable to allocate port')
+ port = net_common.DeletablePort(client=self.network_client,
+ **result['port'])
+ self.set_resource(name, port)
+ return port
+
+ def _create_server(self, client, network, name, key_name, security_groups):
+ flavor_id = self.config.compute.flavor_ref
+ base_image_id = self.config.compute.image_ref
+ create_kwargs = {
+ 'nics': [
+ {'net-id': network.id},
+ ],
+ 'key_name': key_name,
+ 'security_groups': security_groups,
+ }
+ server = client.servers.create(name, base_image_id, flavor_id,
+ **create_kwargs)
+ try:
+ self.assertEqual(server.name, name)
+ self.set_resource(name, server)
+ except AttributeError:
+ self.fail("Server not successfully created.")
+ self.status_timeout(client.servers, server.id, 'ACTIVE')
+ # The instance retrieved on creation is missing network
+ # details, necessitating retrieval after it becomes active to
+ # ensure correct details.
+ server = client.servers.get(server.id)
+ self.set_resource(name, server)
+ return server
+
+ def _create_floating_ip(self, server, external_network_id):
+ result = self.network_client.list_ports(device_id=server.id)
+ ports = result.get('ports', [])
+ self.assertEqual(len(ports), 1,
+ "Unable to determine which port to target.")
+ port_id = ports[0]['id']
+ body = dict(
+ floatingip=dict(
+ floating_network_id=external_network_id,
+ port_id=port_id,
+ tenant_id=server.tenant_id,
+ )
+ )
+ result = self.network_client.create_floatingip(body=body)
+ floating_ip = net_common.DeletableFloatingIp(
+ client=self.network_client,
+ **result['floatingip'])
+ self.set_resource(rand_name('floatingip-'), floating_ip)
+ return floating_ip
+
+ def _ping_ip_address(self, ip_address):
+ cmd = ['ping', '-c1', '-w1', ip_address]
+
+ def ping():
+ proc = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ proc.wait()
+ if proc.returncode == 0:
+ return True
+
+ # TODO(mnewby) Allow configuration of execution and sleep duration.
+ return tempest.test.call_until_true(ping, 20, 1)
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
similarity index 98%
rename from tempest/tests/network/test_network_basic_ops.py
rename to tempest/scenario/test_network_basic_ops.py
index 92ca65f..ee2dc0d 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -17,11 +17,12 @@
# under the License.
from tempest.common.utils.data_utils import rand_name
+from tempest.scenario import manager
from tempest.test import attr
-import tempest.tests.network.common as net_common
+from tempest.tests.network import common as net_common
-class TestNetworkBasicOps(net_common.TestNetworkSmokeCommon):
+class TestNetworkBasicOps(manager.NetworkScenarioTest):
"""
This smoke test suite assumes that Nova has been configured to
diff --git a/tempest/tests/network/test_network_quota_basic.py b/tempest/scenario/test_network_quotas.py
similarity index 95%
rename from tempest/tests/network/test_network_quota_basic.py
rename to tempest/scenario/test_network_quotas.py
index eaec708..8c3af73 100644
--- a/tempest/tests/network/test_network_quota_basic.py
+++ b/tempest/scenario/test_network_quotas.py
@@ -16,12 +16,12 @@
# under the License.
from quantumclient.common import exceptions as exc
-from tempest.tests.network.common import TestNetworkSmokeCommon
+from tempest.scenario.manager import NetworkScenarioTest
MAX_REASONABLE_ITERATIONS = 51 # more than enough. Default for port is 50.
-class TestNetworkQuotaBasic(TestNetworkSmokeCommon):
+class TestNetworkQuotaBasic(NetworkScenarioTest):
"""
This test suite contains tests that each loop trying to grab a
particular resource until a quota limit is hit.
diff --git a/tempest/tests/compute/servers/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
similarity index 96%
rename from tempest/tests/compute/servers/test_server_advanced_ops.py
rename to tempest/scenario/test_server_advanced_ops.py
index ad859d0..e48157e 100644
--- a/tempest/tests/compute/servers/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -19,12 +19,12 @@
from tempest.common.utils.data_utils import rand_name
-from tempest import test
+from tempest.scenario import manager
LOG = logging.getLogger(__name__)
-class TestServerAdvancedOps(test.DefaultClientSmokeTest):
+class TestServerAdvancedOps(manager.OfficialClientTest):
"""
This test case stresses some advanced server instance operations:
diff --git a/tempest/tests/compute/servers/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
similarity index 97%
rename from tempest/tests/compute/servers/test_server_basic_ops.py
rename to tempest/scenario/test_server_basic_ops.py
index fdbbd3c..c5c6728 100644
--- a/tempest/tests/compute/servers/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -18,12 +18,12 @@
import logging
from tempest.common.utils.data_utils import rand_name
-from tempest import test
+from tempest.scenario import manager
LOG = logging.getLogger(__name__)
-class TestServerBasicOps(test.DefaultClientSmokeTest):
+class TestServerBasicOps(manager.OfficialClientTest):
"""
This smoke test case follows this basic set of operations:
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 5b1e48f..a910dec 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -43,7 +43,8 @@
body = json.loads(body)
return resp, body['quota_set']
- def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
+ def update_quota_set(self, tenant_id, force=None,
+ injected_file_content_bytes=None,
metadata_items=None, ram=None, floating_ips=None,
fixed_ips=None, key_pairs=None, instances=None,
security_group_rules=None, injected_files=None,
@@ -54,6 +55,9 @@
"""
post_body = {}
+ if force is not None:
+ post_body['force'] = force
+
if injected_file_content_bytes is not None:
post_body['injected_file_content_bytes'] = \
injected_file_content_bytes
diff --git a/tempest/services/compute/json/services_client.py b/tempest/services/compute/json/services_client.py
new file mode 100644
index 0000000..d054f72
--- /dev/null
+++ b/tempest/services/compute/json/services_client.py
@@ -0,0 +1,33 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class ServicesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServicesClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_services(self):
+ resp, body = self.get("os-services")
+ body = json.loads(body)
+ return resp, body['services']
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 8912443..ef5362c 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -64,7 +64,8 @@
body = self._format_quota(body)
return resp, body
- def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
+ def update_quota_set(self, tenant_id, force=None,
+ injected_file_content_bytes=None,
metadata_items=None, ram=None, floating_ips=None,
fixed_ips=None, key_pairs=None, instances=None,
security_group_rules=None, injected_files=None,
@@ -76,6 +77,9 @@
post_body = Element("quota_set",
xmlns=XMLNS_11)
+ if force is not None:
+ post_body.add_attr('force', force)
+
if injected_file_content_bytes is not None:
post_body.add_attr('injected_file_content_bytes',
injected_file_content_bytes)
diff --git a/tempest/services/compute/xml/services_client.py b/tempest/services/compute/xml/services_client.py
new file mode 100644
index 0000000..ce23403
--- /dev/null
+++ b/tempest/services/compute/xml/services_client.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC 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 lxml import etree
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class ServicesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServicesClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_services(self):
+ resp, body = self.get("os-services", self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
diff --git a/tempest/tests/boto/__init__.py b/tempest/services/orchestration/__init__.py
similarity index 100%
copy from tempest/tests/boto/__init__.py
copy to tempest/services/orchestration/__init__.py
diff --git a/tempest/tests/boto/__init__.py b/tempest/services/orchestration/json/__init__.py
similarity index 100%
copy from tempest/tests/boto/__init__.py
copy to tempest/services/orchestration/json/__init__.py
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
new file mode 100644
index 0000000..81162df
--- /dev/null
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -0,0 +1,99 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM Corp.
+# 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
+import urllib
+
+from tempest.common import rest_client
+from tempest import exceptions
+
+
+class OrchestrationClient(rest_client.RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(OrchestrationClient, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.orchestration.catalog_type
+ self.build_interval = self.config.orchestration.build_interval
+ self.build_timeout = self.config.orchestration.build_timeout
+
+ def list_stacks(self, params=None):
+ """Lists all stacks for a user."""
+
+ uri = 'stacks'
+ if params:
+ uri += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def create_stack(self, name, disable_rollback=True, parameters={},
+ timeout_mins=60, template=None, template_url=None):
+ post_body = {
+ "stack_name": name,
+ "disable_rollback": disable_rollback,
+ "parameters": parameters,
+ "timeout_mins": timeout_mins,
+ "template": "HeatTemplateFormatVersion: '2012-12-12'\n"
+ }
+ if template:
+ post_body['template'] = template
+ if template_url:
+ post_body['template_url'] = template_url
+ body = json.dumps(post_body)
+ uri = 'stacks'
+ resp, body = self.post(uri, headers=self.headers, body=body)
+ return resp, body
+
+ def get_stack(self, stack_identifier):
+ """Returns the details of a single stack."""
+ url = "stacks/%s" % stack_identifier
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['stack']
+
+ def delete_stack(self, stack_identifier):
+ """Deletes the specified Stack."""
+ return self.delete("stacks/%s" % str(stack_identifier))
+
+ def wait_for_stack_status(self, stack_identifier, status, failure_status=(
+ 'CREATE_FAILED',
+ 'DELETE_FAILED',
+ 'UPDATE_FAILED',
+ 'ROLLBACK_FAILED')):
+ """Waits for a Volume to reach a given status."""
+ stack_status = None
+ start = int(time.time())
+
+ while stack_status != status:
+ resp, body = self.get_stack(stack_identifier)
+ stack_name = body['stack_name']
+ stack_status = body['stack_status']
+ if stack_status in failure_status:
+ raise exceptions.StackBuildErrorException(
+ stack_identifier=stack_identifier,
+ stack_status=stack_status,
+ stack_status_reason=body['stack_status_reason'])
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = ('Stack %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (stack_name, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+ time.sleep(self.build_interval)
diff --git a/tempest/test.py b/tempest/test.py
index 9d6c2d3..c69a94c 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -39,9 +39,13 @@
def decorator(f):
if 'type' in kwargs and isinstance(kwargs['type'], str):
f = testtools.testcase.attr(kwargs['type'])(f)
+ if kwargs['type'] == 'smoke':
+ f = testtools.testcase.attr('gate')(f)
elif 'type' in kwargs and isinstance(kwargs['type'], list):
for attr in kwargs['type']:
f = testtools.testcase.attr(attr)(f)
+ if attr == 'smoke':
+ f = testtools.testcase.attr('gate')(f)
return nose.plugins.attrib.attr(*args, **kwargs)(f)
return decorator
@@ -146,63 +150,6 @@
% (thing_id, expected_status))
-class DefaultClientSmokeTest(TestCase):
-
- """
- Base smoke test case class that provides the default clients to
- access the various OpenStack APIs.
-
- Smoke tests are tests that have the following characteristics:
-
- * Test basic operations of an API, typically in an order that
- a regular user would perform those operations
- * Test only the correct inputs and action paths -- no fuzz or
- random input data is sent, only valid inputs.
- * Use only the default client tool for calling an API
- """
-
- manager_class = manager.DefaultClientManager
-
- @classmethod
- def tearDownClass(cls):
- # NOTE(jaypipes): Because smoke tests are typically run in a specific
- # order, and because test methods in smoke tests generally create
- # resources in a particular order, we destroy resources in the reverse
- # order in which resources are added to the smoke test class object
- while cls.os_resources:
- thing = cls.os_resources.pop()
- LOG.debug("Deleting %r from shared resources of %s" %
- (thing, cls.__name__))
-
- try:
- # OpenStack resources are assumed to have a delete()
- # method which destroys the resource...
- thing.delete()
- except Exception as e:
- # If the resource is already missing, mission accomplished.
- if e.__class__.__name__ == 'NotFound':
- continue
- raise
-
- def is_deletion_complete():
- # Deletion testing is only required for objects whose
- # existence cannot be checked via retrieval.
- if isinstance(thing, dict):
- return True
- try:
- thing.get()
- except Exception as e:
- # Clients are expected to return an exception
- # called 'NotFound' if retrieval fails.
- if e.__class__.__name__ == 'NotFound':
- return True
- raise
- return False
-
- # Block until resource deletion has completed or timed-out
- call_until_true(is_deletion_complete, 10, 1)
-
-
class ComputeFuzzClientTest(TestCase):
"""
diff --git a/tempest/tests/compute/__init__.py b/tempest/tests/compute/__init__.py
index 36893e3..968f17e 100644
--- a/tempest/tests/compute/__init__.py
+++ b/tempest/tests/compute/__init__.py
@@ -28,7 +28,6 @@
CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled
RESIZE_AVAILABLE = CONFIG.compute.resize_available
CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
-WHITEBOX_ENABLED = CONFIG.whitebox.whitebox_enabled
DISK_CONFIG_ENABLED = True
DISK_CONFIG_ENABLED_OVERRIDE = CONFIG.compute.disk_config_enabled_override
FLAVOR_EXTRA_DATA_ENABLED = True
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 8f520f9..7160aed 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
from tempest.tests.compute import base
@@ -30,6 +31,7 @@
cls.client = cls.os.quotas_client
cls.adm_client = cls.os_adm.quotas_client
cls.identity_admin_client = cls._get_identity_admin_client()
+ cls.sg_client = cls.security_groups_client
resp, tenants = cls.identity_admin_client.list_tenants()
@@ -69,8 +71,13 @@
self.assertEqual(expected_quota_set, quota_set)
def test_update_all_quota_resources_for_tenant(self):
+ self.skipTest("This test require the change in nova component "
+ "https://review.openstack.org/#/c/25887/, the related "
+ "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
+ "once the change is merged I will enable this testcase")
# Admin can update all the resource quota limits for a tenant
- new_quota_set = {'injected_file_content_bytes': 20480,
+ new_quota_set = {'force': True,
+ 'injected_file_content_bytes': 20480,
'metadata_items': 256, 'injected_files': 10,
'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10,
'key_pairs': 200, 'injected_file_path_bytes': 512,
@@ -117,12 +124,17 @@
"defaults")
def test_create_server_when_cpu_quota_is_full(self):
+ self.skipTest("This test require the change in nova component "
+ "https://review.openstack.org/#/c/25887/, the related "
+ "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
+ "once the change is merged I will enable this testcase")
# Disallow server creation when tenant's vcpu quota is full
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
default_vcpu_quota = quota_set['cores']
vcpu_quota = 0 # Set the quota to zero to conserve resources
resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
cores=vcpu_quota)
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
@@ -130,12 +142,17 @@
self.assertRaises(exceptions.OverLimit, self.create_server)
def test_create_server_when_memory_quota_is_full(self):
+ self.skipTest("This test require the change in nova component "
+ "https://review.openstack.org/#/c/25887/, the related "
+ "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
+ "once the change is merged I will enable this testcase")
# Disallow server creation when tenant's memory quota is full
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
default_mem_quota = quota_set['ram']
mem_quota = 0 # Set the quota to zero to conserve resources
self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
ram=mem_quota)
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
@@ -146,17 +163,76 @@
@attr(type='negative')
def test_create_server_when_instances_quota_is_full(self):
+ self.skipTest("This test require the change in nova component "
+ "https://review.openstack.org/#/c/25887/, the related "
+ "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
+ "once the change is merged I will enable this testcase")
#Once instances quota limit is reached, disallow server creation
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
default_instances_quota = quota_set['instances']
instances_quota = 0 # Set quota to zero to disallow server creation
self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
instances=instances_quota)
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
instances=default_instances_quota)
self.assertRaises(exceptions.OverLimit, self.create_server)
+ @attr(type='negative')
+ def test_security_groups_exceed_limit(self):
+ # Negative test: Creation Security Groups over limit should FAIL
+
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_sg_quota = quota_set['security_groups']
+ sg_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set =\
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ security_groups=sg_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id,
+ security_groups=default_sg_quota)
+
+ # Check we cannot create anymore
+ self.assertRaises(exceptions.OverLimit,
+ self.sg_client.create_security_group,
+ "sg-overlimit", "sg-desc")
+
+ @attr(type='negative')
+ def test_security_groups_rules_exceed_limit(self):
+ # Negative test: Creation of Security Group Rules should FAIL
+ # when we reach limit maxSecurityGroupRules
+
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_sg_rules_quota = quota_set['security_group_rules']
+ sg_rules_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set =\
+ self.adm_client.update_quota_set(
+ self.demo_tenant_id,
+ security_group_rules=sg_rules_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id,
+ security_group_rules=default_sg_rules_quota)
+
+ s_name = rand_name('securitygroup-')
+ s_description = rand_name('description-')
+ resp, securitygroup =\
+ self.sg_client.create_security_group(s_name, s_description)
+ self.addCleanup(self.sg_client.delete_security_group,
+ securitygroup['id'])
+
+ secgroup_id = securitygroup['id']
+ ip_protocol = 'tcp'
+
+ # Check we cannot create SG rule anymore
+ self.assertRaises(exceptions.OverLimit,
+ self.sg_client.create_security_group_rule,
+ secgroup_id, ip_protocol, 1025, 1025)
+
class QuotasAdminTestXML(QuotasAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/tests/compute/admin/test_services.py b/tempest/tests/compute/admin/test_services.py
new file mode 100644
index 0000000..0577164
--- /dev/null
+++ b/tempest/tests/compute/admin/test_services.py
@@ -0,0 +1,52 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC 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 import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class ServicesAdminTestJSON(base.BaseComputeAdminTest):
+
+ """
+ Tests Services API. List and Enable/Disable require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServicesAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.services_client
+ cls.non_admin_client = cls.services_client
+
+ @attr(type='positive')
+ def test_list_services(self):
+ # List Compute services
+ resp, services = self.client.list_services()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(services) >= 2)
+
+ @attr(type='negative')
+ def test_list_services_with_non_admin_user(self):
+ # List Compute service with non admin user
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_services)
+
+
+class ServicesAdminTestXML(ServicesAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index b313e0b..fbefe35 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -63,6 +63,7 @@
cls.fixed_ips_client = os.fixed_ips_client
cls.availability_zone_client = os.availability_zone_client
cls.aggregates_client = os.aggregates_client
+ cls.services_client = os.services_client
cls.build_interval = cls.config.compute.build_interval
cls.build_timeout = cls.config.compute.build_timeout
cls.ssh_user = cls.config.compute.ssh_user
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index d9e4153..1dae8a5 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -100,14 +100,6 @@
self.assertRaises(exceptions.Duplicate, self.client.create_image,
server['id'], snapshot_name)
- @attr(type='negative')
- def test_create_image_when_server_is_building(self):
- # Return error when creating an image of a server that is building
- resp, server = self.create_server(wait_until='BUILD')
- snapshot_name = rand_name('test-snap-')
- self.assertRaises(exceptions.Duplicate, self.client.create_image,
- server['id'], snapshot_name)
-
@testtools.skip("Until Bug #1039739 is fixed")
@attr(type='negative')
def test_create_image_when_server_is_rebooting(self):
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index 30ff882..e22d45a 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -91,6 +91,13 @@
self.created_server_ids.append(server_id)
return server_id
+ def _volume_clean_up(self, server_id, volume_id):
+ resp, body = self.volumes_client.get_volume(volume_id)
+ if body['status'] == 'in-use':
+ self.servers_client.detach_volume(server_id, volume_id)
+ self.volumes_client.wait_for_volume_status(volume_id, 'available')
+ self.volumes_client.delete_volume(volume_id)
+
@attr(type='positive')
@testtools.skipIf(not CONF.compute.live_migration_available,
'Live migration not available')
@@ -117,6 +124,37 @@
server_id, target_host)
self.assertEquals('ACTIVE', self._get_server_status(server_id))
+ @attr(type='positive')
+ @testtools.skipIf(not CONF.compute.live_migration_available or
+ not CONF.compute.use_block_migration_for_live_migration,
+ 'Block Live migration not available')
+ @testtools.skipIf(not CONF.compute.block_migrate_supports_cinder_iscsi,
+ 'Block Live migration not configured for iSCSI')
+ def test_iscsi_volume(self):
+ # Live block migrate an instance to another host
+ if len(self._get_compute_hostnames()) < 2:
+ raise self.skipTest(
+ "Less than 2 compute nodes, skipping migration test.")
+ server_id = self._get_an_active_server()
+ actual_host = self._get_host_for_server(server_id)
+ target_host = self._get_host_other_than(actual_host)
+
+ resp, volume = self.volumes_client.create_volume(1,
+ display_name='test')
+
+ self.volumes_client.wait_for_volume_status(volume['id'],
+ 'available')
+ self.addCleanup(self._volume_clean_up, server_id, volume['id'])
+
+ # Attach the volume to the server
+ self.servers_client.attach_volume(server_id, volume['id'],
+ device='/dev/xvdb')
+ self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
+
+ self._migrate_server_to(server_id, target_host)
+ self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+ self.assertEquals(target_host, self._get_host_for_server(server_id))
+
@classmethod
def tearDownClass(cls):
for server_id in cls.created_server_ids:
diff --git a/tempest/tests/identity/admin/v3/test_endpoints.py b/tempest/tests/identity/admin/v3/test_endpoints.py
old mode 100755
new mode 100644
diff --git a/tempest/tests/image/v1/test_image_members.py b/tempest/tests/image/v1/test_image_members.py
index 92052fc..7293d59 100644
--- a/tempest/tests/image/v1/test_image_members.py
+++ b/tempest/tests/image/v1/test_image_members.py
@@ -17,6 +17,7 @@
import cStringIO as StringIO
from tempest import clients
+from tempest.test import attr
from tempest.tests.image import base
@@ -45,6 +46,7 @@
image_id = image['id']
return image_id
+ @attr(type='gate')
def test_add_image_member(self):
image = self._create_image()
resp = self.client.add_member(self.tenants[0], image)
@@ -55,6 +57,7 @@
members = map(lambda x: x['member_id'], members)
self.assertIn(self.tenants[0], members)
+ @attr(type='gate')
def test_get_shared_images(self):
image = self._create_image()
resp = self.client.add_member(self.tenants[0], image)
@@ -69,6 +72,7 @@
self.assertIn(share_image, images)
self.assertIn(image, images)
+ @attr(type='gate')
def test_remove_member(self):
image_id = self._create_image()
resp = self.client.add_member(self.tenants[0], image_id)
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index 19c0aa0..70e5762 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -25,18 +25,18 @@
class CreateRegisterImagesTest(base.BaseV1ImageTest):
"""Here we test the registration and creation of images."""
- @attr(type='negative')
+ @attr(type='gate')
def test_register_with_invalid_container_format(self):
# Negative tests for invalid data supplied to POST /images
self.assertRaises(exceptions.BadRequest, self.client.create_image,
'test', 'wrong', 'vhd')
- @attr(type='negative')
+ @attr(type='gate')
def test_register_with_invalid_disk_format(self):
self.assertRaises(exceptions.BadRequest, self.client.create_image,
'test', 'bare', 'wrong')
- @attr(type='image')
+ @attr(type='gate')
def test_register_then_upload(self):
# Register, then upload an image
properties = {'prop1': 'val1'}
@@ -60,7 +60,7 @@
self.assertTrue('size' in body)
self.assertEqual(1024, body.get('size'))
- @attr(type='image')
+ @attr(type='gate')
def test_register_remote_image(self):
# Register a new remote image
resp, body = self.create_image(name='New Remote Image',
@@ -80,6 +80,7 @@
self.assertEqual(properties['key1'], 'value1')
self.assertEqual(properties['key2'], 'value2')
+ @attr(type='gate')
def test_register_http_image(self):
resp, body = self.create_image(name='New Http Image',
container_format='bare',
@@ -94,7 +95,7 @@
resp, body = self.client.get_image(image_id)
self.assertEqual(resp['status'], '200')
- @attr(type='image')
+ @attr(type='gate')
def test_register_image_with_min_ram(self):
# Register an image with min ram
properties = {'prop1': 'val1'}
@@ -182,7 +183,7 @@
image_id = image['id']
return image_id
- @attr(type='image')
+ @attr(type='gate')
def test_index_no_params(self):
# Simple test to see all fixture images returned
resp, images_list = self.client.image_list()
@@ -191,7 +192,7 @@
for image_id in self.created_images:
self.assertTrue(image_id in image_list)
- @attr(type='image')
+ @attr(type='gate')
def test_index_disk_format(self):
resp, images_list = self.client.image_list(disk_format='ami')
self.assertEqual(resp['status'], '200')
@@ -201,7 +202,7 @@
self.assertTrue(self.ami_set <= result_set)
self.assertFalse(self.created_set - self.ami_set <= result_set)
- @attr(type='image')
+ @attr(type='gate')
def test_index_container_format(self):
resp, images_list = self.client.image_list(container_format='bare')
self.assertEqual(resp['status'], '200')
@@ -211,7 +212,7 @@
self.assertTrue(self.bare_set <= result_set)
self.assertFalse(self.created_set - self.bare_set <= result_set)
- @attr(type='image')
+ @attr(type='gate')
def test_index_max_size(self):
resp, images_list = self.client.image_list(size_max=42)
self.assertEqual(resp['status'], '200')
@@ -221,7 +222,7 @@
self.assertTrue(self.size42_set <= result_set)
self.assertFalse(self.created_set - self.size42_set <= result_set)
- @attr(type='image')
+ @attr(type='gate')
def test_index_min_size(self):
resp, images_list = self.client.image_list(size_min=142)
self.assertEqual(resp['status'], '200')
@@ -231,7 +232,7 @@
self.assertTrue(self.size142_set <= result_set)
self.assertFalse(self.size42_set <= result_set)
- @attr(type='image')
+ @attr(type='gate')
def test_index_status_active_detail(self):
resp, images_list = self.client.image_list_detail(status='active',
sort_key='size',
@@ -244,7 +245,7 @@
top_size = size
self.assertEqual(image['status'], 'active')
- @attr(type='image')
+ @attr(type='gate')
def test_index_name(self):
resp, images_list = self.client.image_list_detail(
name='New Remote Image dup')
diff --git a/tempest/tests/image/v2/test_images.py b/tempest/tests/image/v2/test_images.py
index 15db519..6356b4c 100644
--- a/tempest/tests/image/v2/test_images.py
+++ b/tempest/tests/image/v2/test_images.py
@@ -30,18 +30,18 @@
Here we test the registration and creation of images
"""
- @attr(type='negative')
+ @attr(type='gate')
def test_register_with_invalid_container_format(self):
# Negative tests for invalid data supplied to POST /images
self.assertRaises(exceptions.BadRequest, self.client.create_image,
'test', 'wrong', 'vhd')
- @attr(type='negative')
+ @attr(type='gate')
def test_register_with_invalid_disk_format(self):
self.assertRaises(exceptions.BadRequest, self.client.create_image,
'test', 'bare', 'wrong')
- @attr(type='image')
+ @attr(type='gate')
def test_register_then_upload(self):
# Register, then upload an image
resp, body = self.create_image(name='New Name',
@@ -98,7 +98,7 @@
return image_id
- @attr(type='image')
+ @attr(type='gate')
def test_index_no_params(self):
# Simple test to see all fixture images returned
resp, images_list = self.client.image_list()
diff --git a/tempest/tests/network/common.py b/tempest/tests/network/common.py
index 6811acf..22eb1d3 100644
--- a/tempest/tests/network/common.py
+++ b/tempest/tests/network/common.py
@@ -15,14 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import subprocess
-
-import netaddr
-
-from quantumclient.common import exceptions as exc
-from tempest.common.utils.data_utils import rand_name
-from tempest import test
-
class AttributeDict(dict):
@@ -100,212 +92,3 @@
def delete(self):
self.client.delete_port(self.id)
-
-
-class TestNetworkSmokeCommon(test.DefaultClientSmokeTest):
- """
- Base class for network smoke tests
- """
-
- @classmethod
- def check_preconditions(cls):
- if (cls.config.network.quantum_available):
- cls.enabled = True
- #verify that quantum_available is telling the truth
- try:
- cls.network_client.list_networks()
- except exc.EndpointNotFound:
- cls.enabled = False
- raise
- else:
- cls.enabled = False
- msg = 'Quantum not available'
- raise cls.skipException(msg)
-
- @classmethod
- def setUpClass(cls):
- super(TestNetworkSmokeCommon, cls).setUpClass()
- cls.tenant_id = cls.manager._get_identity_client(
- cls.config.identity.username,
- cls.config.identity.password,
- cls.config.identity.tenant_name).tenant_id
-
- def _create_keypair(self, client, namestart='keypair-smoke-'):
- kp_name = rand_name(namestart)
- keypair = client.keypairs.create(kp_name)
- try:
- self.assertEqual(keypair.id, kp_name)
- self.set_resource(kp_name, keypair)
- except AttributeError:
- self.fail("Keypair object not successfully created.")
- return keypair
-
- def _create_security_group(self, client, namestart='secgroup-smoke-'):
- # Create security group
- sg_name = rand_name(namestart)
- sg_desc = sg_name + " description"
- secgroup = client.security_groups.create(sg_name, sg_desc)
- try:
- self.assertEqual(secgroup.name, sg_name)
- self.assertEqual(secgroup.description, sg_desc)
- self.set_resource(sg_name, secgroup)
- except AttributeError:
- self.fail("SecurityGroup object not successfully created.")
-
- # Add rules to the security group
- rulesets = [
- {
- # ssh
- 'ip_protocol': 'tcp',
- 'from_port': 22,
- 'to_port': 22,
- 'cidr': '0.0.0.0/0',
- 'group_id': secgroup.id
- },
- {
- # ping
- 'ip_protocol': 'icmp',
- 'from_port': -1,
- 'to_port': -1,
- 'cidr': '0.0.0.0/0',
- 'group_id': secgroup.id
- }
- ]
- for ruleset in rulesets:
- try:
- client.security_group_rules.create(secgroup.id, **ruleset)
- except Exception:
- self.fail("Failed to create rule in security group.")
-
- return secgroup
-
- def _create_network(self, tenant_id, namestart='network-smoke-'):
- name = rand_name(namestart)
- body = dict(
- network=dict(
- name=name,
- tenant_id=tenant_id,
- ),
- )
- result = self.network_client.create_network(body=body)
- network = DeletableNetwork(client=self.network_client,
- **result['network'])
- self.assertEqual(network.name, name)
- self.set_resource(name, network)
- return network
-
- def _list_networks(self):
- nets = self.network_client.list_networks()
- return nets['networks']
-
- def _list_subnets(self):
- subnets = self.network_client.list_subnets()
- return subnets['subnets']
-
- def _list_routers(self):
- routers = self.network_client.list_routers()
- return routers['routers']
-
- def _create_subnet(self, network, namestart='subnet-smoke-'):
- """
- Create a subnet for the given network within the cidr block
- configured for tenant networks.
- """
- cfg = self.config.network
- tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
- result = None
- # Repeatedly attempt subnet creation with sequential cidr
- # blocks until an unallocated block is found.
- for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
- body = dict(
- subnet=dict(
- ip_version=4,
- network_id=network.id,
- tenant_id=network.tenant_id,
- cidr=str(subnet_cidr),
- ),
- )
- try:
- result = self.network_client.create_subnet(body=body)
- break
- except exc.QuantumClientException as e:
- is_overlapping_cidr = 'overlaps with another subnet' in str(e)
- if not is_overlapping_cidr:
- raise
- self.assertIsNotNone(result, 'Unable to allocate tenant network')
- subnet = DeletableSubnet(client=self.network_client,
- **result['subnet'])
- self.assertEqual(subnet.cidr, str(subnet_cidr))
- self.set_resource(rand_name(namestart), subnet)
- return subnet
-
- def _create_port(self, network, namestart='port-quotatest-'):
- name = rand_name(namestart)
- body = dict(
- port=dict(name=name,
- network_id=network.id,
- tenant_id=network.tenant_id))
- result = self.network_client.create_port(body=body)
- self.assertIsNotNone(result, 'Unable to allocate port')
- port = DeletablePort(client=self.network_client,
- **result['port'])
- self.set_resource(name, port)
- return port
-
- def _create_server(self, client, network, name, key_name, security_groups):
- flavor_id = self.config.compute.flavor_ref
- base_image_id = self.config.compute.image_ref
- create_kwargs = {
- 'nics': [
- {'net-id': network.id},
- ],
- 'key_name': key_name,
- 'security_groups': security_groups,
- }
- server = client.servers.create(name, base_image_id, flavor_id,
- **create_kwargs)
- try:
- self.assertEqual(server.name, name)
- self.set_resource(name, server)
- except AttributeError:
- self.fail("Server not successfully created.")
- self.status_timeout(client.servers, server.id, 'ACTIVE')
- # The instance retrieved on creation is missing network
- # details, necessitating retrieval after it becomes active to
- # ensure correct details.
- server = client.servers.get(server.id)
- self.set_resource(name, server)
- return server
-
- def _create_floating_ip(self, server, external_network_id):
- result = self.network_client.list_ports(device_id=server.id)
- ports = result.get('ports', [])
- self.assertEqual(len(ports), 1,
- "Unable to determine which port to target.")
- port_id = ports[0]['id']
- body = dict(
- floatingip=dict(
- floating_network_id=external_network_id,
- port_id=port_id,
- tenant_id=server.tenant_id,
- )
- )
- result = self.network_client.create_floatingip(body=body)
- floating_ip = DeletableFloatingIp(client=self.network_client,
- **result['floatingip'])
- self.set_resource(rand_name('floatingip-'), floating_ip)
- return floating_ip
-
- def _ping_ip_address(self, ip_address):
- cmd = ['ping', '-c1', '-w1', ip_address]
-
- def ping():
- proc = subprocess.Popen(cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode == 0:
- return True
-
- # TODO(mnewby) Allow configuration of execution and sleep duration.
- return test.call_until_true(ping, 20, 1)
diff --git a/tempest/tests/object_storage/test_account_services.py b/tempest/tests/object_storage/test_account_services.py
index 14f94f7..eb66de8 100644
--- a/tempest/tests/object_storage/test_account_services.py
+++ b/tempest/tests/object_storage/test_account_services.py
@@ -22,12 +22,9 @@
class AccountTest(base.BaseObjectTest):
-
@classmethod
def setUpClass(cls):
super(AccountTest, cls).setUpClass()
-
- #Create a container
cls.container_name = rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
@@ -37,8 +34,7 @@
@attr(type='smoke')
def test_list_containers(self):
- # List of all containers should not be empty
-
+ # list of all containers should not be empty
params = {'format': 'json'}
resp, container_list = \
self.account_client.list_account_containers(params=params)
@@ -49,8 +45,7 @@
@attr(type='smoke')
def test_list_account_metadata(self):
- # List all account metadata
-
+ # list all account metadata
resp, metadata = self.account_client.list_account_metadata()
self.assertEqual(resp['status'], '204')
self.assertIn('x-account-object-count', resp)
@@ -59,8 +54,7 @@
@attr(type='smoke')
def test_create_account_metadata(self):
- # Add metadata to account
-
+ # add metadata to account
metadata = {'test-account-meta': 'Meta!'}
resp, _ = \
self.account_client.create_account_metadata(metadata=metadata)
@@ -72,8 +66,7 @@
@attr(type='smoke')
def test_delete_account_metadata(self):
- # Delete metadata from account
-
+ # delete metadata from account
metadata = ['test-account-meta']
resp, _ = \
self.account_client.delete_account_metadata(metadata=metadata)
@@ -84,11 +77,10 @@
@attr(type='negative')
def test_list_containers_with_non_authorized_user(self):
- #Listing containers with using non authorized user
+ # list containers using non-authorized user
- # Randomly creating user
+ # create user
self.data.setup_test_user()
-
resp, body = \
self.token_client.auth(self.data.test_user,
self.data.test_password,
@@ -97,14 +89,11 @@
self.token_client.get_token(self.data.test_user,
self.data.test_password,
self.data.test_tenant)
-
custom_headers = {'X-Auth-Token': new_token}
-
params = {'format': 'json'}
- # Trying to list containers with non authorized user token
+ # list containers with non-authorized user token
self.assertRaises(exceptions.Unauthorized,
self.custom_account_client.list_account_containers,
params=params, metadata=custom_headers)
-
- #Attempt to the delete the user setup created
+ # delete the user which was created
self.data.teardown_all()
diff --git a/tempest/tests/object_storage/test_container_services.py b/tempest/tests/object_storage/test_container_services.py
index 223744c..508132a 100644
--- a/tempest/tests/object_storage/test_container_services.py
+++ b/tempest/tests/object_storage/test_container_services.py
@@ -22,7 +22,6 @@
class ContainerTest(base.BaseObjectTest):
-
@classmethod
def setUpClass(cls):
super(ContainerTest, cls).setUpClass()
@@ -31,73 +30,58 @@
@classmethod
def tearDownClass(cls):
for container in cls.containers:
- #Get list of all object in the container
objlist = \
cls.container_client.list_all_container_objects(container)
-
- #Attempt to delete every object in the container
+ # delete every object in the container
for obj in objlist:
resp, _ = \
cls.object_client.delete_object(container, obj['name'])
-
- #Attempt to delete the container
+ # delete the container
resp, _ = cls.container_client.delete_container(container)
@attr(type='smoke')
def test_create_container(self):
- # Create a container, test responses
-
- #Create a container
container_name = rand_name(name='TestContainer')
resp, body = self.container_client.create_container(container_name)
self.containers.append(container_name)
-
self.assertTrue(resp['status'] in ('202', '201'))
@attr(type='smoke')
def test_delete_container(self):
- # Create and Delete a container, test responses
-
- #Create a container
+ # create a container
container_name = rand_name(name='TestContainer')
resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
-
- #Delete Container
+ # delete container
resp, _ = self.container_client.delete_container(container_name)
self.assertEqual(resp['status'], '204')
self.containers.remove(container_name)
@attr(type='smoke')
def test_list_container_contents_json(self):
- # Add metadata to object
+ # add metadata to an object
- #Create a container
+ # create a container
container_name = rand_name(name='TestContainer')
resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
-
- #Create Object
+ # create object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(container_name,
object_name, data)
-
- #Set Object Metadata
+ # set object metadata
meta_key = rand_name(name='Meta-Test-')
meta_value = rand_name(name='MetaValue-')
orig_metadata = {meta_key: meta_value}
-
resp, _ = self.object_client.update_object_metadata(container_name,
object_name,
orig_metadata)
-
- #Get Container contents list json format
+ # get container contents list
params = {'format': 'json'}
resp, object_list = \
self.container_client.\
list_container_contents(container_name, params=params)
-
self.assertEqual(resp['status'], '200')
self.assertIsNotNone(object_list)
@@ -106,14 +90,13 @@
@attr(type='smoke')
def test_container_metadata(self):
- # Update/Retrieve/Delete Container Metadata
+ # update/retrieve/delete container metadata
- # Create a container
+ # create a container
container_name = rand_name(name='TestContainer')
resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
-
- # Update container metadata
+ # update container metadata
metadata = {'name': 'Pictures',
'description': 'Travel'
}
@@ -122,7 +105,7 @@
metadata=metadata)
self.assertEqual(resp['status'], '204')
- # List container metadata
+ # list container metadata
resp, _ = self.container_client.list_container_metadata(
container_name)
self.assertEqual(resp['status'], '204')
@@ -131,18 +114,19 @@
self.assertEqual(resp['x-container-meta-name'], 'Pictures')
self.assertEqual(resp['x-container-meta-description'], 'Travel')
- # Delete container metadata
+ # delete container metadata
resp, _ = self.container_client.delete_container_metadata(
container_name,
metadata=metadata.keys())
self.assertEqual(resp['status'], '204')
+ # check if the metadata are no longer there
resp, _ = self.container_client.list_container_metadata(container_name)
self.assertEqual(resp['status'], '204')
self.assertNotIn('x-container-meta-name', resp)
self.assertNotIn('x-container-meta-description', resp)
- # Delete Container
+ # delete container
resp, _ = self.container_client.delete_container(container_name)
self.assertEqual(resp['status'], '204')
self.containers.remove(container_name)
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index d5fa96c..666d356 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -23,11 +23,9 @@
class ContainerSyncTest(base.BaseObjectTest):
-
@classmethod
def setUpClass(cls):
super(ContainerSyncTest, cls).setUpClass()
-
cls.containers = []
cls.objects = []
container_sync_timeout = \
@@ -36,8 +34,7 @@
int(cls.config.object_storage.container_sync_interval)
cls.attempts = \
int(container_sync_timeout / cls.container_sync_interval)
-
- # Define container and object clients
+ # define container and object clients
cls.clients = {}
cls.clients[rand_name(name='TestContainerSync')] = \
(cls.container_client, cls.object_client)
@@ -50,29 +47,25 @@
@classmethod
def tearDownClass(cls):
for cont_name, client in cls.clients.items():
- #Get list of all object in the container
objlist = client[0].list_all_container_objects(cont_name)
-
- #Attempt to delete every object in the container
+ # delete every object in the container
if objlist:
for obj in objlist:
resp, _ = client[1].delete_object(cont_name, obj['name'])
-
- #Attempt to delete the container
+ # delete the container
resp, _ = client[0].delete_container(cont_name)
@testtools.skip('Until Bug #1093743 is resolved.')
@attr(type='positive')
def test_container_synchronization(self):
- #Container to container synchronization
- #To allow/accept sync requests to/from other accounts
+ # container to container synchronization
+ # to allow/accept sync requests to/from other accounts
- #Switch container synchronization on and create objects in a containers
+ # turn container synchronization on and create object in container
for cont in (self.containers, self.containers[::-1]):
cont_client = [self.clients[c][0] for c in cont]
obj_client = [self.clients[c][1] for c in cont]
-
- #tell first container to syncronize to a second
+ # tell first container to synchronize to a second
headers = {'X-Container-Sync-Key': 'sync_key',
'X-Container-Sync-To': "%s/%s" %
(cont_client[1].base_url, str(cont[1]))}
@@ -81,8 +74,7 @@
self.assertTrue(resp['status'] in ('202', '201'),
'Error installing X-Container-Sync-To '
'for the container "%s"' % (cont[0]))
-
- #Create Object in container
+ # create object in container
object_name = rand_name(name='TestSyncObject')
data = object_name[::-1] # arbitrary_string()
resp, _ = obj_client[0].create_object(cont[0], object_name, data)
@@ -92,7 +84,7 @@
% (object_name, cont[0]))
self.objects.append(object_name)
- #Wait for Container contents list json format will be not empty
+ # wait until container contents list is not empty
cont_client = [self.clients[c][0] for c in self.containers]
params = {'format': 'json'}
while self.attempts > 0:
@@ -112,15 +104,13 @@
'Error listing the destination container`s'
' "%s" contents' % (self.containers[1]))
object_list_1 = dict((obj['name'], obj) for obj in object_list_1)
- # check that containers is not empty and has equal keys()
- # or wait for next attepmt
+ # check that containers are not empty and have equal keys()
+ # or wait for next attempt
if not object_list_0 or not object_list_1 or \
set(object_list_0.keys()) != set(object_list_1.keys()):
time.sleep(self.container_sync_interval)
self.attempts -= 1
else:
break
-
- # Check for synchronization
self.assertEqual(object_list_0, object_list_1,
'Different object lists in containers.')
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
index e1b1dbd..76370b1 100644
--- a/tempest/tests/object_storage/test_object_expiry.py
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -25,12 +25,9 @@
class ObjectExpiryTest(base.BaseObjectTest):
-
@classmethod
def setUpClass(cls):
super(ObjectExpiryTest, cls).setUpClass()
-
- #Create a container
cls.container_name = rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
@@ -41,54 +38,45 @@
But delete action for the expired object is raising
NotFound exception and also non empty container cannot be deleted.
"""
-
- #Get list of all object in the container
objlist = \
cls.container_client.list_all_container_objects(cls.container_name)
-
- #Attempt to delete every object in the container
+ # delete every object in the container
if objlist:
for obj in objlist:
resp, _ = cls.object_client.delete_object(cls.container_name,
obj['name'])
-
- #Attempt to delete the container
+ # delete the container
resp, _ = cls.container_client.delete_container(cls.container_name)
@testtools.skip('Until Bug #1069849 is resolved.')
@attr(type='regression')
def test_get_object_after_expiry_time(self):
- # GET object after expiry time
- #TODO(harika-vakadi): Similar test case has to be created for
+ #TODO(harika-vakadi): similar test case has to be created for
# "X-Delete-At", after this test case works.
- #Create Object
+ # create object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
-
- #Update object metadata with expiry time of 3 seconds
+ # update object metadata with expiry time of 3 seconds
metadata = {'X-Delete-After': '3'}
resp, _ = \
self.object_client.update_object_metadata(self.container_name,
object_name, metadata,
metadata_prefix='')
-
resp, _ = \
self.object_client.list_object_metadata(self.container_name,
object_name)
-
self.assertEqual(resp['status'], '200')
self.assertIn('x-delete-at', resp)
-
resp, body = self.object_client.get_object(self.container_name,
object_name)
self.assertEqual(resp['status'], '200')
- # Check data
+ # check data
self.assertEqual(body, data)
- # Sleep for over 5 seconds, so that object is expired
+ # sleep for over 5 seconds, so that object expires
time.sleep(5)
- # Verification of raised exception after object gets expired
+ # object should not be there anymore
self.assertRaises(exceptions.NotFound, self.object_client.get_object,
self.container_name, object_name)
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index 4fcc617..2c196cf 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -25,55 +25,42 @@
class ObjectTest(base.BaseObjectTest):
-
@classmethod
def setUpClass(cls):
super(ObjectTest, cls).setUpClass()
-
- #Create a container
cls.container_name = rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
- # Randomly creating user
cls.data.setup_test_user()
-
resp, body = cls.token_client.auth(cls.data.test_user,
cls.data.test_password,
cls.data.test_tenant)
cls.new_token = cls.token_client.get_token(cls.data.test_user,
cls.data.test_password,
cls.data.test_tenant)
-
cls.custom_headers = {'X-Auth-Token': cls.new_token}
@classmethod
def tearDownClass(cls):
- #Get list of all object in the container
objlist = cls.container_client.list_all_container_objects(
cls.container_name)
-
- #Attempt to delete every object in the container
+ # delete every object in the container
for obj in objlist:
resp, _ = cls.object_client.delete_object(cls.container_name,
obj['name'])
-
- #Attempt to delete the container
+ # delete the container
resp, _ = cls.container_client.delete_container(cls.container_name)
-
- #Attempt to the delete the user setup created
+ # delete the user setup created
cls.data.teardown_all()
@attr(type='smoke')
def test_create_object(self):
- # Create storage object, test response
-
- #Create Object
+ # create object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
-
- #Create another Object
+ # create another object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
@@ -82,42 +69,36 @@
@attr(type='smoke')
def test_delete_object(self):
- # Create and delete a storage object, test responses
-
- #Create Object
+ # create object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
-
+ # delete object
resp, _ = self.object_client.delete_object(self.container_name,
object_name)
self.assertEqual(resp['status'], '204')
@attr(type='smoke')
def test_object_metadata(self):
- # Add metadata to storage object, test if metadata is retrievable
+ # add metadata to storage object, test if metadata is retrievable
- #Create Object
+ # create Object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
-
- #Set Object Metadata
+ # set object metadata
meta_key = rand_name(name='test-')
meta_value = rand_name(name='MetaValue-')
orig_metadata = {meta_key: meta_value}
-
resp, _ = self.object_client.update_object_metadata(
- self.container_name, object_name,
- orig_metadata)
+ self.container_name, object_name, orig_metadata)
self.assertEqual(resp['status'], '202')
- #Get Object Metadata
+ # get object metadata
resp, resp_metadata = self.object_client.list_object_metadata(
self.container_name, object_name)
-
self.assertEqual(resp['status'], '200')
actual_meta_key = 'x-object-meta-' + meta_key
self.assertTrue(actual_meta_key in resp)
@@ -125,138 +106,121 @@
@attr(type='smoke')
def test_get_object(self):
- # Retrieve object's data(in response body)
+ # retrieve object's data (in response body)
- #Create Object
+ # create object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
-
+ # get object
resp, body = self.object_client.get_object(self.container_name,
object_name)
self.assertEqual(resp['status'], '200')
- # Check data
self.assertEqual(body, data)
@attr(type='smoke')
def test_copy_object_in_same_container(self):
- # Copy storage object
-
- # Create source Object
+ # create source object
src_object_name = rand_name(name='SrcObject')
src_data = arbitrary_string(size=len(src_object_name) * 2,
base_text=src_object_name)
resp, _ = self.object_client.create_object(self.container_name,
- src_object_name, src_data)
-
- # Create destination Object
+ src_object_name,
+ src_data)
+ # create destination object
dst_object_name = rand_name(name='DstObject')
dst_data = arbitrary_string(size=len(dst_object_name) * 3,
base_text=dst_object_name)
resp, _ = self.object_client.create_object(self.container_name,
- dst_object_name, dst_data)
-
- # Copy source object to destination
+ dst_object_name,
+ dst_data)
+ # copy source object to destination
resp, _ = self.object_client.copy_object_in_same_container(
self.container_name, src_object_name, dst_object_name)
self.assertEqual(resp['status'], '201')
-
- # Check data
+ # check data
resp, body = self.object_client.get_object(self.container_name,
dst_object_name)
self.assertEqual(body, src_data)
@attr(type='smoke')
def test_copy_object_to_itself(self):
- # Change the content type of an existing object
+ # change the content type of an existing object
- # Create Object
+ # create object
object_name = rand_name(name='TestObject')
data = arbitrary_string()
- resp, _ = self.object_client.create_object(self.container_name,
- object_name, data)
- # Get the old content type
+ self.object_client.create_object(self.container_name,
+ object_name, data)
+ # get the old content type
resp_tmp, _ = self.object_client.list_object_metadata(
- self.container_name,
- object_name)
- # Change the content type of the object
+ self.container_name, object_name)
+ # change the content type of the object
metadata = {'content-type': 'text/plain; charset=UTF-8'}
self.assertNotEqual(resp_tmp['content-type'], metadata['content-type'])
resp, _ = self.object_client.copy_object_in_same_container(
self.container_name, object_name, object_name, metadata)
self.assertEqual(resp['status'], '201')
-
- # Check the content type
+ # check the content type
resp, _ = self.object_client.list_object_metadata(self.container_name,
object_name)
self.assertEqual(resp['content-type'], metadata['content-type'])
@attr(type='smoke')
def test_copy_object_2d_way(self):
- # Copy storage object
-
- # Create source Object
+ # create source object
src_object_name = rand_name(name='SrcObject')
src_data = arbitrary_string(size=len(src_object_name) * 2,
base_text=src_object_name)
resp, _ = self.object_client.create_object(self.container_name,
src_object_name, src_data)
-
- # Create destination Object
+ # create destination object
dst_object_name = rand_name(name='DstObject')
dst_data = arbitrary_string(size=len(dst_object_name) * 3,
base_text=dst_object_name)
resp, _ = self.object_client.create_object(self.container_name,
dst_object_name, dst_data)
-
- # Copy source object to destination
+ # copy source object to destination
resp, _ = self.object_client.copy_object_2d_way(self.container_name,
src_object_name,
dst_object_name)
self.assertEqual(resp['status'], '201')
-
- # Check data
+ # check data
resp, body = self.object_client.get_object(self.container_name,
dst_object_name)
self.assertEqual(body, src_data)
@attr(type='smoke')
def test_copy_object_across_containers(self):
- # Copy storage object across containers
-
- #Create a container so as to use as source container
+ # create a container to use as asource container
src_container_name = rand_name(name='TestSourceContainer')
self.container_client.create_container(src_container_name)
-
- #Create a container so as to use as destination container
+ # create a container to use as a destination container
dst_container_name = rand_name(name='TestDestinationContainer')
self.container_client.create_container(dst_container_name)
-
- # Create Object in source container
+ # create object in source container
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 2,
base_text=object_name)
resp, _ = self.object_client.create_object(src_container_name,
object_name, data)
- #Set Object Metadata
+ # set object metadata
meta_key = rand_name(name='test-')
meta_value = rand_name(name='MetaValue-')
orig_metadata = {meta_key: meta_value}
-
resp, _ = self.object_client.update_object_metadata(src_container_name,
object_name,
orig_metadata)
self.assertEqual(resp['status'], '202')
-
try:
- # Copy object from source container to destination container
+ # copy object from source container to destination container
resp, _ = self.object_client.copy_object_across_containers(
src_container_name, object_name, dst_container_name,
object_name)
self.assertEqual(resp['status'], '201')
- # Check if object is present in destination container
+ # check if object is present in destination container
resp, body = self.object_client.get_object(dst_container_name,
object_name)
self.assertEqual(body, data)
@@ -268,254 +232,25 @@
self.fail("Got exception :%s ; while copying"
" object across containers" % e)
finally:
- #Delete objects from respective containers
+ # delete objects from respective containers
resp, _ = self.object_client.delete_object(dst_container_name,
object_name)
resp, _ = self.object_client.delete_object(src_container_name,
object_name)
- #Delete containers created in this method
+ # delete containers created in this method
resp, _ = self.container_client.delete_container(
src_container_name)
resp, _ = self.container_client.delete_container(
dst_container_name)
- @attr(type='smoke')
- def test_access_public_container_object_without_using_creds(self):
- # Make container public-readable, and access the object
- # anonymously, e.g. without using credentials
-
- try:
- resp_meta = None
- # Update Container Metadata to make public readable
- cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
-
- # Create Object
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
- resp, _ = self.object_client.create_object(self.container_name,
- object_name, data)
- self.assertEqual(resp['status'], '201')
-
- # List container metadata
- resp_meta, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp_meta['status'], '204')
- self.assertIn('x-container-read', resp_meta)
- self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings')
-
- # Trying to Get Object with empty Headers as it is public readable
- resp, body = self.custom_object_client.get_object(
- self.container_name, object_name,
- metadata={})
- self.assertEqual(body, data)
- finally:
- if resp_meta['status'] == '204':
- # Delete updated container metadata, to revert back.
- resp, body = self.container_client.delete_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
-
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-read', resp)
- self.assertEqual(resp['x-container-read'], 'x')
-
- @attr(type='smoke')
- def test_access_public_object_with_another_user_creds(self):
- #Make container public-readable, and access the object
- #anonymously, e.g. using another user credentials
-
- try:
- resp_meta = None
- cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
- # Create Object
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name) * 1,
- base_text=object_name)
- resp, _ = self.object_client.create_object(self.container_name,
- object_name, data)
- self.assertEqual(resp['status'], '201')
-
- # List container metadata
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-read', resp)
- self.assertEqual(resp['x-container-read'], '.r:*,.rlistings')
-
- # Trying to GET Auth Token of Alternate user
- token = self.identity_client_alt.get_auth()
- headers = {'X-Auth-Token': token}
-
- # Trying to create object with Alternate user creds
- resp, body = self.custom_object_client.get_object(
- self.container_name, object_name,
- metadata=headers)
- self.assertEqual(body, data)
-
- except Exception as e:
- self.fail("Failed to get public readable object with another"
- " user creds raised exception is %s" % e)
-
- finally:
- if resp_meta['status'] == '204':
- # Delete updated container metadata, to revert back.
- resp, body = self.container_client.delete_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
-
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-read', resp)
- self.assertEqual(resp['x-container-read'], 'x')
-
- @testtools.skip('Until Bug #1020722 is resolved.')
- @attr(type='smoke')
- def test_write_public_object_without_using_creds(self):
- #Make container public-writable, and create object
- #anonymously, e.g. without using credentials
- try:
- resp_meta = None
- # Update Container Metadata to make public readable
- cont_headers = {'X-Container-Write': '-*'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
- # List container metadata
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
-
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-write', resp)
- self.assertEqual(resp['x-container-write'], '-*')
-
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
-
- headers = {'Content-Type': 'application/json',
- 'Accept': 'application/json'}
-
- #Trying to Create object without using creds
- resp, body = self.custom_object_client.create_object(
- self.container_name, object_name,
- data, metadata=headers)
- self.assertEqual(resp['status'], '201')
-
- except Exception as e:
- self.fail("Failed to create public writable object without using"
- " creds raised exception is %s" % e)
-
- finally:
- if resp_meta['status'] == '204':
- # Delete updated container metadata, to revert back.
- resp, body = self.container_client.delete_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
-
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-write', resp)
- self.assertEqual(resp['x-container-write'], 'x')
-
- @testtools.skip('Until Bug #1020722 is resolved.')
- @attr(type='smoke')
- def test_write_public_with_another_user_creds(self):
- #Make container public-writable, and create object
- #anonymously, e.g. with another user credentials
-
- try:
- resp_meta = None
- # Update Container Metadata to make public readable
- cont_headers = {'X-Container-Write': '-*'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
- # List container metadata
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
-
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-write', resp)
- self.assertEqual(resp['x-container-write'], '-*')
-
- #Trying to GET auth token of Alternate user
- token = self.identity_client_alt.get_auth()
-
- headers = {'Content-Type': 'application/json',
- 'Accept': 'application/json',
- 'X-Auth-Token': token}
-
- #Trying to Create an object with another user creds
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
- resp, body = self.custom_object_client.create_object(
- self.container_name, object_name,
- data, metadata=headers)
- self.assertEqual(resp['status'], '201')
-
- except Exception as e:
- self.fail("Failed to create public writable object with another"
- " user creds raised exception is %s" % e)
-
- finally:
- if resp_meta['status'] == '204':
- # Delete updated container metadata, to revert back.
- resp, body = self.container_client.delete_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
-
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-write', resp)
- self.assertEqual(resp['x-container-write'], 'x')
-
- @attr(type='negative')
- def test_access_object_without_using_creds(self):
- # Attempt to access the object anonymously, e.g.
- # not using any credentials
-
- # Create Object
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
- resp, _ = self.object_client.create_object(self.container_name,
- object_name, data)
- self.assertEqual(resp['status'], '201')
-
- # Trying to Get Object with empty Headers
- self.assertRaises(exceptions.Unauthorized,
- self.custom_object_client.get_object,
- self.container_name, object_name, metadata={})
-
@attr(type='negative')
def test_write_object_without_using_creds(self):
- # Attempt to write to the object anonymously, e.g.
- # not using any credentials
-
- # Trying to Create Object with empty Headers
+ # trying to create object with empty headers
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name),
base_text=object_name)
obj_headers = {'Content-Type': 'application/json',
'Accept': 'application/json'}
-
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.create_object,
self.container_name, object_name, data,
@@ -523,30 +258,25 @@
@attr(type='negative')
def test_delete_object_without_using_creds(self):
- # Attempt to delete the object anonymously,
- # e.g. not using any credentials
-
- # Create Object
+ # create object
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name),
base_text=object_name)
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
-
- # Trying to Delete Object with empty Headers
+ # trying to delete object with empty headers
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.delete_object,
self.container_name, object_name)
@attr(type='negative')
def test_write_object_with_non_authorized_user(self):
- #Attempt to upload another file using non authorized user
-
+ # attempt to upload another file using non-authorized user
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 5,
base_text=object_name)
- # Trying to Create Object with non authorized user token
+ # trying to create object with non-authorized user
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.create_object,
self.container_name, object_name, data,
@@ -554,18 +284,14 @@
@attr(type='negative')
def test_read_object_with_non_authorized_user(self):
- #Attempt to download the file using non authorized user
-
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 5,
base_text=object_name)
-
resp, body = self.object_client.create_object(
- self.container_name, object_name,
- data)
+ self.container_name, object_name, data)
self.assertEqual(resp['status'], '201')
- # Trying to Get Object with non authorized user token
+ # trying to get object with non authorized user token
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.get_object,
self.container_name, object_name,
@@ -573,18 +299,13 @@
@attr(type='negative')
def test_delete_object_with_non_authorized_user(self):
- #Attempt to delete container using non authorized user
-
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 5,
base_text=object_name)
-
resp, body = self.object_client.create_object(
- self.container_name, object_name,
- data)
+ self.container_name, object_name, data)
self.assertEqual(resp['status'], '201')
-
- # Trying to Delete Object with non authorized user token
+ # trying to delete object with non-authorized user token
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.delete_object,
self.container_name, object_name,
@@ -593,11 +314,11 @@
@testtools.skip('Until Bug #1097137 is resolved.')
@attr(type='positive')
def test_get_object_using_temp_url(self):
- #Access object using temp url within expiry time
+ # access object using temporary URL within expiration time
try:
- #Update Account Metadata
- # Flag to check if account metadata got updated
+ # update account metadata
+ # flag to check if account metadata got updated
flag = False
key = 'Meta'
metadata = {'Temp-URL-Key': key}
@@ -605,27 +326,23 @@
metadata=metadata)
self.assertEqual(resp['status'], '204')
flag = True
-
resp, _ = self.account_client.list_account_metadata()
self.assertIn('x-account-meta-temp-url-key', resp)
self.assertEqual(resp['x-account-meta-temp-url-key'], key)
- # Create Object
+ # create object
object_name = rand_name(name='ObjectTemp')
data = arbitrary_string(size=len(object_name),
base_text=object_name)
self.object_client.create_object(self.container_name,
object_name, data)
-
expires = int(time.time() + 10)
- #Trying to GET object using temp URL with in expiry time
+ # trying to get object using temp url with in expiry time
_, body = self.object_client.get_object_using_temp_url(
self.container_name, object_name,
expires, key)
-
self.assertEqual(body, data)
-
finally:
if flag:
resp, _ = self.account_client.delete_account_metadata(
@@ -635,35 +352,183 @@
@attr(type='positive')
def test_object_upload_in_segments(self):
- #Attempt to upload object in segments
-
- #Create Object
+ # create object
object_name = rand_name(name='LObject')
data = arbitrary_string(size=len(object_name),
base_text=object_name)
segments = 10
self.object_client.create_object(self.container_name,
object_name, data)
- #Uploading 10 segments
+ # uploading 10 segments
for i in range(segments):
resp, _ = self.object_client.create_object_segments(
- self.container_name, object_name,
- i, data)
- # Creating a Manifest File (Metadata Update)
-
+ self.container_name, object_name, i, data)
+ # creating a manifest file (metadata update)
metadata = {'X-Object-Manifest': '%s/%s/'
% (self.container_name, object_name)}
resp, _ = self.object_client.update_object_metadata(
- self.container_name, object_name,
- metadata, metadata_prefix='')
+ self.container_name, object_name, metadata, metadata_prefix='')
resp, _ = self.object_client.list_object_metadata(
self.container_name, object_name)
self.assertIn('x-object-manifest', resp)
self.assertEqual(resp['x-object-manifest'],
'%s/%s/' % (self.container_name, object_name))
- #Downloading the object
+ # downloading the object
resp, body = self.object_client.get_object(
self.container_name, object_name)
-
self.assertEqual(data * segments, body)
+
+
+class PublicObjectTest(base.BaseObjectTest):
+ def setUp(self):
+ super(PublicObjectTest, self).setUp()
+ self.container_name = rand_name(name='TestContainer')
+ self.container_client.create_container(self.container_name)
+
+ def tearDown(self):
+ objlist = self.container_client.list_all_container_objects(
+ self.container_name)
+ # delete every object in the container
+ for obj in objlist:
+ resp, _ = self.object_client.delete_object(
+ self.container_name, obj['name'])
+ # delete the container
+ resp, _ = self.container_client.delete_container(self.container_name)
+ super(PublicObjectTest, self).tearDown()
+
+ @attr(type='smoke')
+ def test_access_public_container_object_without_using_creds(self):
+ # make container public-readable and access an object in it object
+ # anonymously, without using credentials
+
+ # update container metadata to make it publicly readable
+ cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
+ resp_meta, body = self.container_client.update_container_metadata(
+ self.container_name, metadata=cont_headers, metadata_prefix='')
+ self.assertEqual(resp_meta['status'], '204')
+ # create object
+ object_name = rand_name(name='Object')
+ data = arbitrary_string(size=len(object_name),
+ base_text=object_name)
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data)
+ self.assertEqual(resp['status'], '201')
+
+ # list container metadata
+ resp_meta, _ = self.container_client.list_container_metadata(
+ self.container_name)
+ self.assertEqual(resp_meta['status'], '204')
+ self.assertIn('x-container-read', resp_meta)
+ self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings')
+
+ # trying to get object with empty headers as it is public readable
+ resp, body = self.custom_object_client.get_object(
+ self.container_name, object_name, metadata={})
+ self.assertEqual(body, data)
+
+ @attr(type='smoke')
+ def test_access_public_object_with_another_user_creds(self):
+ # make container public-readable and access an object in it using
+ # another user's credentials
+ try:
+ cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
+ resp_meta, body = self.container_client.update_container_metadata(
+ self.container_name, metadata=cont_headers,
+ metadata_prefix='')
+ self.assertEqual(resp_meta['status'], '204')
+ # create object
+ object_name = rand_name(name='Object')
+ data = arbitrary_string(size=len(object_name) * 1,
+ base_text=object_name)
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data)
+ self.assertEqual(resp['status'], '201')
+
+ # list container metadata
+ resp, _ = self.container_client.list_container_metadata(
+ self.container_name)
+ self.assertEqual(resp['status'], '204')
+ self.assertIn('x-container-read', resp)
+ self.assertEqual(resp['x-container-read'], '.r:*,.rlistings')
+
+ # get auth token of alternative user
+ token = self.identity_client_alt.get_auth()
+ headers = {'X-Auth-Token': token}
+ # access object using alternate user creds
+ resp, body = self.custom_object_client.get_object(
+ self.container_name, object_name,
+ metadata=headers)
+ self.assertEqual(body, data)
+
+ except Exception as e:
+ self.fail("Failed to get public readable object with another"
+ " user creds raised exception is %s" % e)
+
+ @testtools.skip('Until Bug #1020722 is resolved.')
+ @attr(type='smoke')
+ def test_write_public_object_without_using_creds(self):
+ # make container public-writable, and create object anonymously, e.g.
+ # without using credentials
+ try:
+ # update container metadata to make publicly writable
+ cont_headers = {'X-Container-Write': '-*'}
+ resp_meta, body = self.container_client.update_container_metadata(
+ self.container_name, metadata=cont_headers, metadata_prefix='')
+ self.assertEqual(resp_meta['status'], '204')
+ # list container metadata
+ resp, _ = self.container_client.list_container_metadata(
+ self.container_name)
+ self.assertEqual(resp['status'], '204')
+ self.assertIn('x-container-write', resp)
+ self.assertEqual(resp['x-container-write'], '-*')
+
+ object_name = rand_name(name='Object')
+ data = arbitrary_string(size=len(object_name),
+ base_text=object_name)
+ headers = {'Content-Type': 'application/json',
+ 'Accept': 'application/json'}
+ # create object as anonymous user
+ resp, body = self.custom_object_client.create_object(
+ self.container_name, object_name, data, metadata=headers)
+ self.assertEqual(resp['status'], '201')
+
+ except Exception as e:
+ self.fail("Failed to create public writable object without using"
+ " creds raised exception is %s" % e)
+
+ @testtools.skip('Until Bug #1020722 is resolved.')
+ @attr(type='smoke')
+ def test_write_public_with_another_user_creds(self):
+ # make container public-writable, and create object with another user's
+ # credentials
+ try:
+ # update container metadata to make it publicly writable
+ cont_headers = {'X-Container-Write': '-*'}
+ resp_meta, body = self.container_client.update_container_metadata(
+ self.container_name, metadata=cont_headers,
+ metadata_prefix='')
+ self.assertEqual(resp_meta['status'], '204')
+ # list container metadata
+ resp, _ = self.container_client.list_container_metadata(
+ self.container_name)
+ self.assertEqual(resp['status'], '204')
+ self.assertIn('x-container-write', resp)
+ self.assertEqual(resp['x-container-write'], '-*')
+
+ # trying to get auth token of alternative user
+ token = self.identity_client_alt.get_auth()
+ headers = {'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'X-Auth-Token': token}
+
+ # trying to create an object with another user's creds
+ object_name = rand_name(name='Object')
+ data = arbitrary_string(size=len(object_name),
+ base_text=object_name)
+ resp, body = self.custom_object_client.create_object(
+ self.container_name, object_name, data, metadata=headers)
+ self.assertEqual(resp['status'], '201')
+ except Exception as e:
+ self.fail("Failed to create public writable object with another"
+ " user creds raised exception is %s" % e)
diff --git a/tempest/tests/object_storage/test_object_version.py b/tempest/tests/object_storage/test_object_version.py
index 80cfc27..4a16965 100644
--- a/tempest/tests/object_storage/test_object_version.py
+++ b/tempest/tests/object_storage/test_object_version.py
@@ -21,7 +21,6 @@
class ContainerTest(base.BaseObjectTest):
-
@classmethod
def setUpClass(cls):
super(ContainerTest, cls).setUpClass()
@@ -30,16 +29,13 @@
@classmethod
def tearDownClass(cls):
for container in cls.containers:
- #Get list of all object in the container
objlist = \
cls.container_client.list_all_container_objects(container)
-
- #Attempt to delete every object in the container
+ # delete every object in the container
for obj in objlist:
resp, _ = \
cls.object_client.delete_object(container, obj['name'])
-
- #Attempt to delete the container
+ # delete the container
resp, _ = cls.container_client.delete_container(container)
def assertContainer(self, container, count, byte, versioned):
@@ -54,16 +50,13 @@
@attr(type='smoke')
def test_versioned_container(self):
- # Versioned container responses tests
-
- # Create a containers
+ # create container
vers_container_name = rand_name(name='TestVersionContainer')
resp, body = self.container_client.create_container(
vers_container_name)
self.containers.append(vers_container_name)
self.assertIn(resp['status'], ('202', '201'))
- self.assertContainer(vers_container_name, '0', '0',
- 'Missing Header')
+ self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
base_container_name = rand_name(name='TestBaseContainer')
headers = {'X-versions-Location': vers_container_name}
@@ -75,18 +68,17 @@
self.assertIn(resp['status'], ('202', '201'))
self.assertContainer(base_container_name, '0', '0',
vers_container_name)
- # Create Object
object_name = rand_name(name='TestObject')
+ # create object
resp, _ = self.object_client.create_object(base_container_name,
object_name, '1')
-
+ # create 2nd version of object
resp, _ = self.object_client.create_object(base_container_name,
object_name, '2')
-
resp, body = self.object_client.get_object(base_container_name,
object_name)
self.assertEqual(body, '2')
- # Delete Object version 2
+ # delete object version 2
resp, _ = self.object_client.delete_object(base_container_name,
object_name)
self.assertContainer(base_container_name, '1', '1',
@@ -94,21 +86,18 @@
resp, body = self.object_client.get_object(base_container_name,
object_name)
self.assertEqual(body, '1')
-
- # Delete Object version 1
+ # delete object version 1
resp, _ = self.object_client.delete_object(base_container_name,
object_name)
- # Containers are Empty
+ # containers should be empty
self.assertContainer(base_container_name, '0', '0',
vers_container_name)
self.assertContainer(vers_container_name, '0', '0',
'Missing Header')
-
- # Delete Containers
+ # delete containers
resp, _ = self.container_client.delete_container(base_container_name)
self.assertEqual(resp['status'], '204')
self.containers.remove(base_container_name)
-
resp, _ = self.container_client.delete_container(vers_container_name)
self.assertEqual(resp['status'], '204')
self.containers.remove(vers_container_name)
diff --git a/tempest/tests/volume/admin/test_multi_backend.py b/tempest/tests/volume/admin/test_multi_backend.py
index 3d5fae4..93b3b77 100644
--- a/tempest/tests/volume/admin/test_multi_backend.py
+++ b/tempest/tests/volume/admin/test_multi_backend.py
@@ -22,6 +22,7 @@
from tempest import config
from tempest.services.volume.json.admin import volume_types_client
from tempest.services.volume.json import volumes_client
+from tempest.test import attr
from tempest.tests.volume import base
LOG = logging.getLogger(__name__)
@@ -108,6 +109,7 @@
super(VolumeMultiBackendTest, cls).tearDownClass()
+ @attr(type=['smoke'])
def test_multi_backend_enabled(self):
# this test checks that multi backend is enabled for at least the
# computes where the volumes created in setUp were made
@@ -131,6 +133,7 @@
"%(volume_host2)s") % locals()
self.assertTrue(len(volume_host2.split("@")) > 1, msg)
+ @attr(type='gate')
def test_backend_name_distinction(self):
# this test checks that the two volumes created at setUp doesn't
# belong to the same backend (if they are in the same backend, that
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index 13efca7..a35f017 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -17,6 +17,7 @@
from tempest.common.utils.data_utils import rand_name
from tempest.services.volume.json.admin import volume_types_client
+from tempest.test import attr
from tempest.tests.volume.base import BaseVolumeTest
@@ -37,6 +38,7 @@
auth_url,
adm_tenant)
+ @attr(type=['smoke'])
def test_volume_type_list(self):
# List Volume types.
try:
@@ -46,6 +48,7 @@
except Exception:
self.fail("Could not list volume types")
+ @attr(type=['smoke'])
def test_create_get_delete_volume_with_volume_type_and_extra_specs(self):
# Create/get/delete volume with volume_type and extra spec.
try:
@@ -97,6 +100,7 @@
resp, _ = self.client.delete_volume_type(body['id'])
self.assertEqual(202, resp.status)
+ @attr(type=['smoke'])
def test_volume_type_create_delete(self):
# Create/Delete volume type.
try:
@@ -119,6 +123,7 @@
except Exception:
self.fail("Could not create a volume_type")
+ @attr(type=['smoke'])
def test_volume_type_create_get(self):
# Create/get volume type.
try:
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs.py b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
index 1cd7653..aeb58c7 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
@@ -16,6 +16,7 @@
# under the License.
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
from tempest.tests.volume import base
@@ -33,6 +34,7 @@
cls.client.delete_volume_type(cls.volume_type['id'])
super(VolumeTypesExtraSpecsTest, cls).tearDownClass()
+ @attr(type=['smoke'])
def test_volume_type_extra_specs_list(self):
# List Volume types extra specs.
try:
@@ -51,6 +53,7 @@
except Exception:
self.fail("Could not list volume types extra specs")
+ @attr(type=['gate'])
def test_volume_type_extra_specs_update(self):
# Update volume type extra specs
try:
@@ -74,6 +77,7 @@
except Exception:
self.fail("Couldnt update volume type extra spec")
+ @attr(type=['smoke'])
def test_volume_type_extra_spec_create_get_delete(self):
# Create/Get/Delete volume type extra spec.
try:
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
index bd6e279..4a1a0b2 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
@@ -19,6 +19,7 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.volume import base
@@ -39,6 +40,7 @@
cls.client.delete_volume_type(cls.volume_type['id'])
super(ExtraSpecsNegativeTest, cls).tearDownClass()
+ @attr(type='gate')
def test_update_no_body(self):
# Should not update volume type extra specs with no body
extra_spec = {"spec1": "val2"}
@@ -46,6 +48,7 @@
self.client.update_volume_type_extra_specs,
self.volume_type['id'], extra_spec.keys()[0], None)
+ @attr(type='gate')
def test_update_nonexistent_extra_spec_id(self):
# Should not update volume type extra specs with nonexistent id.
extra_spec = {"spec1": "val2"}
@@ -54,6 +57,7 @@
self.volume_type['id'], str(uuid.uuid4()),
extra_spec)
+ @attr(type='gate')
def test_update_none_extra_spec_id(self):
# Should not update volume type extra specs with none id.
extra_spec = {"spec1": "val2"}
@@ -61,6 +65,7 @@
self.client.update_volume_type_extra_specs,
self.volume_type['id'], None, extra_spec)
+ @attr(type='gate')
def test_update_multiple_extra_spec(self):
# Should not update volume type extra specs with multiple specs as
# body.
@@ -70,6 +75,7 @@
self.volume_type['id'], extra_spec.keys()[0],
extra_spec)
+ @attr(type='gate')
def test_create_nonexistent_type_id(self):
# Should not create volume type extra spec for nonexistent volume
# type id.
@@ -78,18 +84,21 @@
self.client.create_volume_type_extra_specs,
str(uuid.uuid4()), extra_specs)
+ @attr(type='gate')
def test_create_none_body(self):
# Should not create volume type extra spec for none POST body.
self.assertRaises(exceptions.BadRequest,
self.client.create_volume_type_extra_specs,
self.volume_type['id'], None)
+ @attr(type='gate')
def test_create_invalid_body(self):
# Should not create volume type extra spec for invalid POST body.
self.assertRaises(exceptions.BadRequest,
self.client.create_volume_type_extra_specs,
self.volume_type['id'], ['invalid'])
+ @attr(type='gate')
def test_delete_nonexistent_volume_type_id(self):
# Should not delete volume type extra spec for nonexistent
# type id.
@@ -98,12 +107,14 @@
self.client.delete_volume_type_extra_specs,
str(uuid.uuid4()), extra_specs.keys()[0])
+ @attr(type='gate')
def test_list_nonexistent_volume_type_id(self):
# Should not list volume type extra spec for nonexistent type id.
self.assertRaises(exceptions.NotFound,
self.client.list_volume_types_extra_specs,
str(uuid.uuid4()))
+ @attr(type='gate')
def test_get_nonexistent_volume_type_id(self):
# Should not get volume type extra spec for nonexistent type id.
extra_specs = {"spec1": "val1"}
@@ -111,6 +122,7 @@
self.client.get_volume_type_extra_specs,
str(uuid.uuid4()), extra_specs.keys()[0])
+ @attr(type='gate')
def test_get_nonexistent_extra_spec_id(self):
# Should not get volume type extra spec for nonexistent extra spec
# id.
diff --git a/tempest/tests/volume/admin/test_volume_types_negative.py b/tempest/tests/volume/admin/test_volume_types_negative.py
index daf804d..bd358b8 100644
--- a/tempest/tests/volume/admin/test_volume_types_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_negative.py
@@ -18,12 +18,14 @@
import uuid
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.volume import base
class VolumeTypesNegativeTest(base.BaseVolumeAdminTest):
_interface = 'json'
+ @attr(type='gate')
def test_create_with_nonexistent_volume_type(self):
# Should not be able to create volume with nonexistent volume_type.
self.assertRaises(exceptions.NotFound,
@@ -31,16 +33,19 @@
display_name=str(uuid.uuid4()),
volume_type=str(uuid.uuid4()))
+ @attr(type='gate')
def test_create_with_empty_name(self):
# Should not be able to create volume type with an empty name.
self.assertRaises(exceptions.BadRequest,
self.client.create_volume_type, '')
+ @attr(type='gate')
def test_get_nonexistent_type_id(self):
# Should not be able to get volume type with nonexistent type id.
self.assertRaises(exceptions.NotFound, self.client.get_volume_type,
str(uuid.uuid4()))
+ @attr(type='gate')
def test_delete_nonexistent_type_id(self):
# Should not be able to delete volume type with nonexistent type id.
self.assertRaises(exceptions.NotFound, self.client.delete_volume_type,
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index e6eb8d8..8664a7d 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -52,7 +52,7 @@
super(VolumesActionsTest, cls).tearDownClass()
- @attr(type='smoke')
+ @attr(type=['smoke'])
def test_attach_detach_volume_to_instance(self):
# Volume is attached and detached successfully from an instance
try:
@@ -70,6 +70,7 @@
self.assertEqual(202, resp.status)
self.client.wait_for_volume_status(self.volume['id'], 'available')
+ @attr(type='gate')
def test_get_volume_attachment(self):
# Verify that a volume's attachment information is retrieved
mountpoint = '/dev/vdc'
diff --git a/tempest/tests/volume/test_volumes_get.py b/tempest/tests/volume/test_volumes_get.py
index 8e80e18..65748e8 100644
--- a/tempest/tests/volume/test_volumes_get.py
+++ b/tempest/tests/volume/test_volumes_get.py
@@ -78,7 +78,7 @@
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(volume['id'])
- @attr(type='positive')
+ @attr(type='gate')
def test_volume_get_metadata_none(self):
# Create a volume without passing metadata, get details, and delete
try:
@@ -105,11 +105,11 @@
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(volume['id'])
- @attr(type='smoke')
+ @attr(type=['smoke'])
def test_volume_create_get_delete(self):
self._volume_create_get_delete(image_ref=None)
- @attr(type='smoke')
+ @attr(type=['smoke'])
def test_volume_from_image(self):
self._volume_create_get_delete(image_ref=self.config.compute.image_ref)
diff --git a/tempest/tests/volume/test_volumes_list.py b/tempest/tests/volume/test_volumes_list.py
index a8fedb9..7f5c756 100644
--- a/tempest/tests/volume/test_volumes_list.py
+++ b/tempest/tests/volume/test_volumes_list.py
@@ -76,7 +76,7 @@
cls.client.wait_for_resource_deletion(volid)
super(VolumesListTest, cls).tearDownClass()
- @attr(type='smoke')
+ @attr(type=['smoke'])
def test_volume_list(self):
# Get a list of Volumes
# Fetch all volumes
@@ -89,7 +89,7 @@
', '.join(m_vol['display_name']
for m_vol in missing_vols))
- @attr(type='smoke')
+ @attr(type='gate')
def test_volume_list_with_details(self):
# Get a list of Volumes with details
# Fetch all Volumes
diff --git a/tempest/tests/volume/test_volumes_negative.py b/tempest/tests/volume/test_volumes_negative.py
index c7d4374..f02bb3f 100644
--- a/tempest/tests/volume/test_volumes_negative.py
+++ b/tempest/tests/volume/test_volumes_negative.py
@@ -17,6 +17,7 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
from tempest.tests.volume import base
@@ -28,6 +29,7 @@
super(VolumesNegativeTest, cls).setUpClass()
cls.client = cls.volumes_client
+ @attr(type='gate')
def test_volume_get_nonexistant_volume_id(self):
# Should not be able to get a nonexistant volume
#Creating a nonexistant volume id
@@ -43,6 +45,7 @@
self.assertRaises(exceptions.NotFound, self.client.get_volume,
non_exist_id)
+ @attr(type='gate')
def test_volume_delete_nonexistant_volume_id(self):
# Should not be able to delete a nonexistant Volume
# Creating nonexistant volume id
@@ -58,6 +61,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
non_exist_id)
+ @attr(type='gate')
def test_create_volume_with_invalid_size(self):
# Should not be able to create volume with invalid size
# in request
@@ -66,6 +70,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
+ @attr(type='gate')
def test_create_volume_with_out_passing_size(self):
# Should not be able to create volume without passing size
# in request
@@ -74,6 +79,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
+ @attr(type='gate')
def test_create_volume_with_size_zero(self):
# Should not be able to create volume with size zero
v_name = rand_name('Volume-')
@@ -81,20 +87,24 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
+ @attr(type='gate')
def test_get_invalid_volume_id(self):
# Should not be able to get volume with invalid id
self.assertRaises(exceptions.NotFound, self.client.get_volume,
'#$%%&^&^')
+ @attr(type='gate')
def test_get_volume_without_passing_volume_id(self):
# Should not be able to get volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.get_volume, '')
+ @attr(type='gate')
def test_delete_invalid_volume_id(self):
# Should not be able to delete volume when invalid ID is passed
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
'!@#$%^&*()')
+ @attr(type='gate')
def test_delete_volume_without_passing_volume_id(self):
# Should not be able to delete volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
diff --git a/tempest/tests/volume/test_volumes_snapshots.py b/tempest/tests/volume/test_volumes_snapshots.py
index ba8ba6c..935d42e 100644
--- a/tempest/tests/volume/test_volumes_snapshots.py
+++ b/tempest/tests/volume/test_volumes_snapshots.py
@@ -37,7 +37,7 @@
def tearDownClass(cls):
super(VolumesSnapshotTest, cls).tearDownClass()
- @attr(type='smoke')
+ @attr(type=['smoke'])
def test_snapshot_create_get_delete(self):
# Create a snapshot, get some of the details and then deletes it
resp, snapshot = self.snapshots_client.create_snapshot(
@@ -52,6 +52,7 @@
self.snapshots_client.delete_snapshot(snapshot['id'])
self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+ @attr(type=['smoke'])
def test_volume_from_snapshot(self):
# Create a temporary snap using wrapper method from base, then
# create a snap based volume, check resp code and deletes it
diff --git a/tempest/thirdparty/README.rst b/tempest/thirdparty/README.rst
new file mode 100644
index 0000000..41d31f3
--- /dev/null
+++ b/tempest/thirdparty/README.rst
@@ -0,0 +1,33 @@
+Tempest Guide to Third Party API tests
+========
+
+
+What are these tests?
+--------
+
+Third party tests are tests for non native OpenStack APIs that are
+part of OpenStack projects. If we ship an API, we're really required
+to ensure that it's working.
+
+An example is that Nova Compute currently has EC2 API support in tree,
+which should be tested as part of normal process.
+
+
+Why are these tests in tempest?
+--------
+
+If we ship an API in an OpenStack component, there should be tests in
+tempest to exercise it in some way.
+
+
+Scope of these tests
+--------
+
+Third party API testing should be limited to the functional testing of
+third party API compliance. Complex scenarios should be avoided, and
+instead exercised with the OpenStack API, unless the third party API
+can't be tested without those scenarios.
+
+Whenever possible third party API testing should use a client as close
+to the third party API as possible. The point of these tests is API
+validation.
diff --git a/tempest/tests/boto/__init__.py b/tempest/thirdparty/__init__.py
similarity index 100%
copy from tempest/tests/boto/__init__.py
copy to tempest/thirdparty/__init__.py
diff --git a/tempest/tests/boto/__init__.py b/tempest/thirdparty/boto/__init__.py
similarity index 100%
rename from tempest/tests/boto/__init__.py
rename to tempest/thirdparty/boto/__init__.py
diff --git a/tempest/testboto.py b/tempest/thirdparty/boto/test.py
similarity index 98%
rename from tempest/testboto.py
rename to tempest/thirdparty/boto/test.py
index 9e652cb..afa5c69 100644
--- a/tempest/testboto.py
+++ b/tempest/thirdparty/boto/test.py
@@ -32,9 +32,9 @@
import tempest.config
from tempest import exceptions
import tempest.test
-from tempest.tests.boto.utils.wait import re_search_wait
-from tempest.tests.boto.utils.wait import state_wait
-from tempest.tests.boto.utils.wait import wait_exception
+from tempest.thirdparty.boto.utils.wait import re_search_wait
+from tempest.thirdparty.boto.utils.wait import state_wait
+from tempest.thirdparty.boto.utils.wait import wait_exception
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
similarity index 97%
rename from tempest/tests/boto/test_ec2_instance_run.py
rename to tempest/thirdparty/boto/test_ec2_instance_run.py
index b6b93d8..bbe11d1 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -25,10 +25,10 @@
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest import exceptions
from tempest.test import attr
-from tempest.testboto import BotoTestCase
-from tempest.tests.boto.utils.s3 import s3_upload_dir
-from tempest.tests.boto.utils.wait import re_search_wait
-from tempest.tests.boto.utils.wait import state_wait
+from tempest.thirdparty.boto.test import BotoTestCase
+from tempest.thirdparty.boto.utils.s3 import s3_upload_dir
+from tempest.thirdparty.boto.utils.wait import re_search_wait
+from tempest.thirdparty.boto.utils.wait import state_wait
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/boto/test_ec2_keys.py b/tempest/thirdparty/boto/test_ec2_keys.py
similarity index 97%
rename from tempest/tests/boto/test_ec2_keys.py
rename to tempest/thirdparty/boto/test_ec2_keys.py
index d96ee11..5304649 100644
--- a/tempest/tests/boto/test_ec2_keys.py
+++ b/tempest/thirdparty/boto/test_ec2_keys.py
@@ -20,7 +20,7 @@
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
-from tempest.testboto import BotoTestCase
+from tempest.thirdparty.boto.test import BotoTestCase
def compare_key_pairs(a, b):
diff --git a/tempest/tests/boto/test_ec2_network.py b/tempest/thirdparty/boto/test_ec2_network.py
similarity index 96%
rename from tempest/tests/boto/test_ec2_network.py
rename to tempest/thirdparty/boto/test_ec2_network.py
index ef307a1..6878df1 100644
--- a/tempest/tests/boto/test_ec2_network.py
+++ b/tempest/thirdparty/boto/test_ec2_network.py
@@ -19,7 +19,7 @@
from tempest import clients
from tempest.test import attr
-from tempest.testboto import BotoTestCase
+from tempest.thirdparty.boto.test import BotoTestCase
@attr("EC2")
diff --git a/tempest/tests/boto/test_ec2_security_groups.py b/tempest/thirdparty/boto/test_ec2_security_groups.py
similarity index 98%
rename from tempest/tests/boto/test_ec2_security_groups.py
rename to tempest/thirdparty/boto/test_ec2_security_groups.py
index dd46a91..54a94f8 100644
--- a/tempest/tests/boto/test_ec2_security_groups.py
+++ b/tempest/thirdparty/boto/test_ec2_security_groups.py
@@ -18,7 +18,7 @@
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
-from tempest.testboto import BotoTestCase
+from tempest.thirdparty.boto.test import BotoTestCase
@attr("EC2")
diff --git a/tempest/tests/boto/test_ec2_volumes.py b/tempest/thirdparty/boto/test_ec2_volumes.py
similarity index 97%
rename from tempest/tests/boto/test_ec2_volumes.py
rename to tempest/thirdparty/boto/test_ec2_volumes.py
index 37a913e..b4d763d 100644
--- a/tempest/tests/boto/test_ec2_volumes.py
+++ b/tempest/thirdparty/boto/test_ec2_volumes.py
@@ -19,7 +19,7 @@
from tempest import clients
from tempest.test import attr
-from tempest.testboto import BotoTestCase
+from tempest.thirdparty.boto.test import BotoTestCase
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/boto/test_s3_buckets.py b/tempest/thirdparty/boto/test_s3_buckets.py
similarity index 96%
rename from tempest/tests/boto/test_s3_buckets.py
rename to tempest/thirdparty/boto/test_s3_buckets.py
index 0a05ae0..3b7c5a7 100644
--- a/tempest/tests/boto/test_s3_buckets.py
+++ b/tempest/thirdparty/boto/test_s3_buckets.py
@@ -20,7 +20,7 @@
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
-from tempest.testboto import BotoTestCase
+from tempest.thirdparty.boto.test import BotoTestCase
@attr("S3")
diff --git a/tempest/tests/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
similarity index 96%
rename from tempest/tests/boto/test_s3_ec2_images.py
rename to tempest/thirdparty/boto/test_s3_ec2_images.py
index f77743e..594f416 100644
--- a/tempest/tests/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -22,9 +22,9 @@
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
-from tempest.testboto import BotoTestCase
-from tempest.tests.boto.utils.s3 import s3_upload_dir
-from tempest.tests.boto.utils.wait import state_wait
+from tempest.thirdparty.boto.test import BotoTestCase
+from tempest.thirdparty.boto.utils.s3 import s3_upload_dir
+from tempest.thirdparty.boto.utils.wait import state_wait
@attr("S3", "EC2")
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/thirdparty/boto/test_s3_objects.py
similarity index 96%
rename from tempest/tests/boto/test_s3_objects.py
rename to tempest/thirdparty/boto/test_s3_objects.py
index 9d4d79c..b256bc4 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/thirdparty/boto/test_s3_objects.py
@@ -22,7 +22,7 @@
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
-from tempest.testboto import BotoTestCase
+from tempest.thirdparty.boto.test import BotoTestCase
@attr("S3")
diff --git a/tempest/tests/boto/utils/__init__.py b/tempest/thirdparty/boto/utils/__init__.py
similarity index 100%
rename from tempest/tests/boto/utils/__init__.py
rename to tempest/thirdparty/boto/utils/__init__.py
diff --git a/tempest/tests/boto/utils/s3.py b/tempest/thirdparty/boto/utils/s3.py
similarity index 100%
rename from tempest/tests/boto/utils/s3.py
rename to tempest/thirdparty/boto/utils/s3.py
diff --git a/tempest/tests/boto/utils/wait.py b/tempest/thirdparty/boto/utils/wait.py
similarity index 100%
rename from tempest/tests/boto/utils/wait.py
rename to tempest/thirdparty/boto/utils/wait.py
diff --git a/tempest/whitebox/README.rst b/tempest/whitebox/README.rst
new file mode 100644
index 0000000..dabf758
--- /dev/null
+++ b/tempest/whitebox/README.rst
@@ -0,0 +1,46 @@
+Tempest Guide to Whitebox tests
+========
+
+
+What are these tests?
+--------
+
+When you hit the OpenStack API, this causes internal state changes in
+the system. This might be database transitions, vm modifications,
+other deep state changes which aren't really accessible from the
+OpenStack API. These side effects are sometimes important to
+validate.
+
+White box testing is an approach there. In white box testing you are
+given database access to the environment, and can verify internal
+record changes after an API call.
+
+This is an optional part of testing, and requires extra setup, but can
+be useful for validating Tempest internals.
+
+
+Why are these tests in tempest?
+--------
+
+Especially when it comes to something like VM state changing, which is
+a coordination of numerous running daemons, and a functioning VM, it's
+very difficult to get a realistic test like this in unit tests.
+
+
+Scope of these tests
+--------
+
+White box tests should be limitted to tests where black box testing
+(using the OpenStack API to verify results) isn't sufficient.
+
+As these poke at internals of OpenStack, it should also be realized
+that these tests are very tightly coupled to current implementation of
+OpenStack. They will need to be maintained agressively to keep up with
+internals changes in OpenStack projects.
+
+
+Example of a good test
+--------
+
+Pushing VMs through a series of state transitions, and ensuring along
+the way the database state transitions match what's expected.
diff --git a/tempest/tests/boto/__init__.py b/tempest/whitebox/__init__.py
similarity index 100%
copy from tempest/tests/boto/__init__.py
copy to tempest/whitebox/__init__.py
diff --git a/tempest/whitebox.py b/tempest/whitebox/manager.py
similarity index 98%
rename from tempest/whitebox.py
rename to tempest/whitebox/manager.py
index cf9fff0..a75edb0 100644
--- a/tempest/whitebox.py
+++ b/tempest/whitebox/manager.py
@@ -27,7 +27,6 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest import test
-from tempest.tests import compute
LOG = logging.getLogger(__name__)
@@ -56,12 +55,11 @@
@classmethod
def setUpClass(cls):
- if not compute.WHITEBOX_ENABLED:
+ super(ComputeWhiteboxTest, cls).setUpClass()
+ if not cls.config.whitebox.whitebox_enabled:
msg = "Whitebox testing disabled"
raise cls.skipException(msg)
- super(ComputeWhiteboxTest, cls).setUpClass()
-
# Add some convenience attributes that tests use...
cls.nova_dir = cls.config.whitebox.source_dir
cls.compute_bin_dir = cls.config.whitebox.bin_dir
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/whitebox/test_images_whitebox.py
similarity index 98%
rename from tempest/tests/compute/images/test_images_whitebox.py
rename to tempest/whitebox/test_images_whitebox.py
index 9ec05dd..304677f 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/whitebox/test_images_whitebox.py
@@ -19,11 +19,11 @@
from tempest import exceptions
from tempest.test import attr
from tempest.tests.compute import base
-from tempest import whitebox
+from tempest.whitebox import manager
@attr(type='whitebox')
-class ImagesWhiteboxTest(whitebox.ComputeWhiteboxTest, base.BaseComputeTest):
+class ImagesWhiteboxTest(manager.ComputeWhiteboxTest, base.BaseComputeTest):
_interface = 'json'
@classmethod
diff --git a/tempest/tests/compute/servers/test_servers_whitebox.py b/tempest/whitebox/test_servers_whitebox.py
similarity index 98%
rename from tempest/tests/compute/servers/test_servers_whitebox.py
rename to tempest/whitebox/test_servers_whitebox.py
index 6b192dd..2eab393 100644
--- a/tempest/tests/compute/servers/test_servers_whitebox.py
+++ b/tempest/whitebox/test_servers_whitebox.py
@@ -18,11 +18,11 @@
from tempest import exceptions
from tempest.test import attr
from tempest.tests.identity.base import BaseIdentityAdminTest
-from tempest import whitebox
+from tempest.whitebox import manager
@attr(type='whitebox')
-class ServersWhiteboxTest(whitebox.ComputeWhiteboxTest):
+class ServersWhiteboxTest(manager.ComputeWhiteboxTest):
_interface = 'json'
@classmethod
diff --git a/tempest/tests/boto/utils/__init__.py b/tools/__init__.py
similarity index 100%
copy from tempest/tests/boto/utils/__init__.py
copy to tools/__init__.py
diff --git a/tempest/tests/boto/__init__.py b/tools/hacking/__init__.py
similarity index 100%
copy from tempest/tests/boto/__init__.py
copy to tools/hacking/__init__.py
diff --git a/tools/hacking/tempest.py b/tools/hacking/tempest.py
new file mode 100644
index 0000000..1db8419
--- /dev/null
+++ b/tools/hacking/tempest.py
@@ -0,0 +1,42 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# 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 re
+
+
+SKIP_DECORATOR = '@testtools.skip('
+
+
+def skip_bugs(physical_line):
+ """Check skip lines for proper bug entries
+
+ T101: Bug not in skip line
+ T102: Bug in message formatted incorrectly
+ """
+
+ pos = physical_line.find(SKIP_DECORATOR)
+
+ skip_re = re.compile(r'^\s*@testtools.skip.*')
+
+ if pos != -1 and skip_re.match(physical_line):
+ bug = re.compile(r'^.*\bbug\b.*', re.IGNORECASE)
+ if bug.match(physical_line) is None:
+ return (pos, 'T101: skips must have an associated bug')
+
+ bug_re = re.compile(r'.*skip\(.*Bug\s\#\d+', re.IGNORECASE)
+
+ if bug_re.match(physical_line) is None:
+ return (pos, 'T102: Bug number formatted incorrectly')
diff --git a/tox.ini b/tox.ini
index 7d3d245..2449c86 100644
--- a/tox.ini
+++ b/tox.ini
@@ -20,7 +20,7 @@
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
commands =
- nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/tests tempest/cli
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/tests tempest/scenario tempest/thirdparty tempest/cli
[testenv:smoke]
sitepackages = True
@@ -46,7 +46,7 @@
NOSE_OPENSTACK_STDOUT=1
commands =
python -m tools/tempest_coverage -c start --combine
- nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/tests tempest/cli
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/tests tempest/scenario tempest/thirdparty tempest/cli
python -m tools/tempest_coverage -c report --html
[testenv:pep8]
@@ -54,6 +54,9 @@
deps = -r{toxinidir}/tools/pip-requires
-r{toxinidir}/tools/test-requires
+[hacking]
+local-check = tools.hacking.tempest.skip_bugs
+
[flake8]
ignore = E125,H302,H404
show-source = True