Merge "Make sure isolated tenants are not left behind."
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 80c60d5..b64b047 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -164,6 +164,9 @@
# The version of the OpenStack Images API to use
api_version = 1
+# HTTP image to use for glance http image testing
+http_image = http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz
+
[network]
# This section contains configuration options used when executing tests
# against the OpenStack Network API.
diff --git a/tempest/README.rst b/tempest/README.rst
new file mode 100644
index 0000000..c41ef96
--- /dev/null
+++ b/tempest/README.rst
@@ -0,0 +1,98 @@
+Tempest Field Guide
+-----------
+
+Tempest is designed to be useful for a large number of different
+environments. This includes being useful for gating commits to
+OpenStack core projects, being used to validate OpenStack cloud
+implementations for both correctness, as well as a burn in tool for
+OpenStack clouds.
+
+As such Tempest tests come in many flavors, each with their own rules
+and guidelines. Below is the proposed Havana restructuring for Tempest
+to make this clear.
+
+tempest/
+ 3rdparty/ - 3rd party api tests
+ api/ - API tests
+ cli/ - CLI tests
+ scenario/ - complex scenario tests
+ stress/ - stress tests
+ whitebox/ - white box testing
+
+Each of these directories contains different types of tests. What
+belongs in each directory, the rules and examples for good tests, are
+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
+------------
+
+API tests are validation tests for the OpenStack API. They should not
+use the existing python clients for OpenStack, but should instead use
+the tempest implementations of clients. This allows us to test both
+XML and JSON. Having raw clients also lets us pass invalid JSON and
+XML to the APIs and see the results, something we could not get with
+the native clients.
+
+When it makes sense, API testing should be moved closer to the
+projects themselves, possibly as functional tests in their unit test
+frameworks.
+
+TODO: The bulk of tempest/tests should move to tempest/api
+
+
+cli
+------------
+
+CLI tests use the openstack CLI to interact with the OpenStack
+cloud. CLI testing in unit tests is somewhat difficult because unlike
+server testing, there is no access to server code to
+instantiate. Tempest seems like a logical place for this, as it
+prereqs having a running OpenStack cloud.
+
+TODO: the top level cli directory moves to tempest/cli
+
+
+scenario
+------------
+
+Scenario tests are complex "through path" tests for OpenStack
+functionality. They are typically a series of steps where complicated
+state requiring multiple services is set up exercised, and torn down.
+
+Scenario tests can and should use the OpenStack python clients.
+
+TODO: tests/network/test_network_basic_ops.py,
+tests/compute/servers/*_ops.py should move to tempest/scenario (others)
+
+
+stress
+-----------
+
+Stress tests are designed to stress an OpenStack environment by
+running a high workload against it and seeing what breaks. Tools may
+be provided to help detect breaks (stack traces in the logs).
+
+TODO: old stress tests deleted, new_stress that david is working on
+moves into here.
+
+
+whitebox
+----------
+
+Whitebox tests are tests which require access to the database of the
+target OpenStack machine to verify internal state after opperations
+are made. White box tests are allowed to use the python clients.
+
+TODO: collect out whitebox tests to this location.
diff --git a/tempest/clients.py b/tempest/clients.py
index 2934f81..9b2c1f5 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -42,6 +42,7 @@
from tempest.services.compute.json.servers_client import ServersClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
+from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
from tempest.services.compute.xml.availability_zone_client import \
AvailabilityZoneClientXML
from tempest.services.compute.xml.extensions_client import ExtensionsClientXML
@@ -66,9 +67,13 @@
EndPointClientJSON
from tempest.services.identity.v3.json.identity_client import \
IdentityV3ClientJSON
+from tempest.services.identity.v3.json.service_client import \
+ ServiceClientJSON
from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
from tempest.services.identity.v3.xml.identity_client import \
IdentityV3ClientXML
+from tempest.services.identity.v3.xml.service_client import \
+ ServiceClientXML
from tempest.services.identity.xml.identity_client import IdentityClientXML
from tempest.services.identity.xml.identity_client import TokenClientXML
from tempest.services.image.v1.json.image_client import ImageClientJSON
@@ -192,6 +197,16 @@
"xml": AvailabilityZoneClientXML,
}
+SERVICE_CLIENT = {
+ "json": ServiceClientJSON,
+ "xml": ServiceClientXML,
+}
+
+AGGREGATES_CLIENT = {
+ "json": AggregatesClientJSON,
+ "xml": AggregatesClientXML,
+}
+
class Manager(object):
@@ -260,6 +275,8 @@
self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args)
self.availability_zone_client = \
AVAILABILITY_ZONE_CLIENT[interface](*client_args)
+ self.service_client = SERVICE_CLIENT[interface](*client_args)
+ self.aggregates_client = AGGREGATES_CLIENT[interface](*client_args)
except KeyError:
msg = "Unsupported interface type `%s'" % interface
raise exceptions.InvalidConfiguration(msg)
@@ -275,7 +292,6 @@
self.custom_object_client = ObjectClientCustomizedHeader(*client_args)
self.custom_account_client = \
AccountClientCustomizedHeader(*client_args)
- self.aggregates_client = AggregatesClientJSON(*client_args)
class AltManager(Manager):
diff --git a/tempest/config.py b/tempest/config.py
index e09da2b..a90767e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -243,7 +243,11 @@
help="Version of the API"),
cfg.StrOpt('catalog_type',
default='image',
- help='Catalog type of the Image service.')
+ help='Catalog type of the Image service.'),
+ cfg.StrOpt('http_image',
+ default='http://download.cirros-cloud.net/0.3.1/'
+ 'cirros-0.3.1-x86_64-uec.tar.gz',
+ help='http accessable image')
]
diff --git a/tempest/services/compute/xml/aggregates_client.py b/tempest/services/compute/xml/aggregates_client.py
new file mode 100644
index 0000000..0ef8e22
--- /dev/null
+++ b/tempest/services/compute/xml/aggregates_client.py
@@ -0,0 +1,103 @@
+# 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 import exceptions
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class AggregatesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AggregatesClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _format_aggregate(self, g):
+ agg = xml_to_json(g)
+ aggregate = {}
+ for key, value in agg.items():
+ if key == 'hosts':
+ aggregate['hosts'] = []
+ for k, v in value.items():
+ aggregate['hosts'].append(v)
+ elif key == 'availability_zone':
+ aggregate[key] = None if value == 'None' else value
+ else:
+ aggregate[key] = value
+ return aggregate
+
+ def _parse_array(self, node):
+ return [self._format_aggregate(x) for x in node]
+
+ def list_aggregates(self):
+ """Get aggregate list."""
+ resp, body = self.get("os-aggregates", self.headers)
+ aggregates = self._parse_array(etree.fromstring(body))
+ return resp, aggregates
+
+ def get_aggregate(self, aggregate_id):
+ """Get details of the given aggregate."""
+ resp, body = self.get("os-aggregates/%s" % str(aggregate_id),
+ self.headers)
+ aggregate = self._format_aggregate(etree.fromstring(body))
+ return resp, aggregate
+
+ def create_aggregate(self, name, availability_zone=None):
+ """Creates a new aggregate."""
+ post_body = Element("aggregate",
+ name=name,
+ availability_zone=availability_zone)
+ resp, body = self.post('os-aggregates',
+ str(Document(post_body)),
+ self.headers)
+ aggregate = self._format_aggregate(etree.fromstring(body))
+ return resp, aggregate
+
+ def delete_aggregate(self, aggregate_id):
+ """Deletes the given aggregate."""
+ return self.delete("os-aggregates/%s" % str(aggregate_id),
+ self.headers)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_aggregate(id)
+ except exceptions.NotFound:
+ return True
+ return False
+
+ def add_host(self, aggregate_id, host):
+ """Adds a host to the given aggregate."""
+ post_body = Element("add_host", host=host)
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ str(Document(post_body)),
+ self.headers)
+ aggregate = self._format_aggregate(etree.fromstring(body))
+ return resp, aggregate
+
+ def remove_host(self, aggregate_id, host):
+ """Removes a host from the given aggregate."""
+ post_body = Element("remove_host", host=host)
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ str(Document(post_body)),
+ self.headers)
+ aggregate = self._format_aggregate(etree.fromstring(body))
+ return resp, aggregate
diff --git a/tempest/services/identity/v3/json/service_client.py b/tempest/services/identity/v3/json/service_client.py
new file mode 100644
index 0000000..dde572e
--- /dev/null
+++ b/tempest/services/identity/v3/json/service_client.py
@@ -0,0 +1,63 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class ServiceClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServiceClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(ServiceClientJSON, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def update_service(self, service_id, **kwargs):
+ """Updates a service."""
+ resp, body = self.get_service(service_id)
+ name = kwargs.get('name', body['name'])
+ type = kwargs.get('type', body['type'])
+ desc = kwargs.get('description', body['description'])
+ patch_body = {
+ 'description': desc,
+ 'type': type,
+ 'name': name
+ }
+ patch_body = json.dumps({'service': patch_body})
+ resp, body = self.patch('services/%s' % service_id,
+ patch_body, self.headers)
+ body = json.loads(body)
+ return resp, body['service']
+
+ def get_service(self, service_id):
+ """Get Service."""
+ url = 'services/%s' % service_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['service']
diff --git a/tempest/services/identity/v3/xml/service_client.py b/tempest/services/identity/v3/xml/service_client.py
new file mode 100644
index 0000000..306245b
--- /dev/null
+++ b/tempest/services/identity/v3/xml/service_client.py
@@ -0,0 +1,81 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from urlparse import urlparse
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class ServiceClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServiceClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ array.append(xml_to_json(child))
+ return array
+
+ def _parse_body(self, body):
+ data = xml_to_json(body)
+ return data
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(ServiceClientXML, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def update_service(self, service_id, **kwargs):
+ """Updates a service_id."""
+ resp, body = self.get_service(service_id)
+ name = kwargs.get('name', body['name'])
+ description = kwargs.get('description', body['description'])
+ type = kwargs.get('type', body['type'])
+ update_service = Element("service",
+ xmlns=XMLNS,
+ id=service_id,
+ name=name,
+ description=description,
+ type=type)
+ resp, body = self.patch('services/%s' % service_id,
+ str(Document(update_service)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_service(self, service_id):
+ """Get Service."""
+ url = 'services/%s' % service_id
+ resp, body = self.get(url, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/test.py b/tempest/test.py
index 4db9827..de255d5 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -51,10 +51,8 @@
class BaseTestCase(testtools.TestCase,
testtools.testcase.WithAttributes,
testresources.ResourcedTestCase):
- def __init__(self, *args, **kwargs):
- super(BaseTestCase, self).__init__(*args, **kwargs)
- #NOTE(afazekas): inspection workaround
- BaseTestCase.config = config.TempestConfig()
+
+ config = config.TempestConfig()
@classmethod
def setUpClass(cls):
@@ -62,6 +60,29 @@
super(BaseTestCase, cls).setUpClass()
+def call_until_true(func, duration, sleep_for):
+ """
+ Call the given function until it returns True (and return True) or
+ until the specified duration (in seconds) elapses (and return
+ False).
+
+ :param func: A zero argument callable that returns True on success.
+ :param duration: The number of seconds for which to attempt a
+ successful call of the function.
+ :param sleep_for: The number of seconds to sleep after an unsuccessful
+ invocation of the function.
+ """
+ now = time.time()
+ timeout = now + duration
+ while now < timeout:
+ if func():
+ return True
+ LOG.debug("Sleeping for %d seconds", sleep_for)
+ time.sleep(sleep_for)
+ now = time.time()
+ return False
+
+
class TestCase(BaseTestCase):
"""Base test case class for all Tempest tests
@@ -96,57 +117,33 @@
self.os_resources.remove(thing)
del self.resource_keys[key]
-
-def call_until_true(func, duration, sleep_for):
- """
- Call the given function until it returns True (and return True) or
- until the specified duration (in seconds) elapses (and return
- False).
-
- :param func: A zero argument callable that returns True on success.
- :param duration: The number of seconds for which to attempt a successful
- call of the function.
- :param sleep_for: The number of seconds to sleep after an unsuccessful
- invocation of the function.
- """
- now = time.time()
- timeout = now + duration
- while now < timeout:
- if func():
- return True
- LOG.debug("Sleeping for %d seconds", sleep_for)
- time.sleep(sleep_for)
- now = time.time()
- return False
-
-
-def status_timeout(testcase, things, thing_id, expected_status):
- """
- Given a thing and an expected status, do a loop, sleeping
- for a configurable amount of time, checking for the
- expected status to show. At any time, if the returned
- status of the thing is ERROR, fail out.
- """
- def check_status():
- # python-novaclient has resources available to its client
- # that all implement a get() method taking an identifier
- # for the singular resource to retrieve.
- thing = things.get(thing_id)
- new_status = thing.status
- if new_status == 'ERROR':
- testcase.fail("%s failed to get to expected status."
+ def status_timeout(self, things, thing_id, expected_status):
+ """
+ Given a thing and an expected status, do a loop, sleeping
+ for a configurable amount of time, checking for the
+ expected status to show. At any time, if the returned
+ status of the thing is ERROR, fail out.
+ """
+ def check_status():
+ # python-novaclient has resources available to its client
+ # that all implement a get() method taking an identifier
+ # for the singular resource to retrieve.
+ thing = things.get(thing_id)
+ new_status = thing.status
+ if new_status == 'ERROR':
+ self.fail("%s failed to get to expected status."
"In ERROR state."
% thing)
- elif new_status == expected_status:
- return True # All good.
- LOG.debug("Waiting for %s to get to %s status. "
- "Currently in %s status",
- thing, expected_status, new_status)
- conf = config.TempestConfig()
- if not call_until_true(check_status,
- conf.compute.build_timeout,
- conf.compute.build_interval):
- testcase.fail("Timed out waiting for thing %s to become %s"
+ elif new_status == expected_status:
+ return True # All good.
+ LOG.debug("Waiting for %s to get to %s status. "
+ "Currently in %s status",
+ thing, expected_status, new_status)
+ conf = config.TempestConfig()
+ if not call_until_true(check_status,
+ conf.compute.build_timeout,
+ conf.compute.build_interval):
+ self.fail("Timed out waiting for thing %s to become %s"
% (thing_id, expected_status))
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 08dc330..b6b93d8 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -202,14 +202,13 @@
re_search_wait(_output, text)
part_lines = ssh.get_partitions().split('\n')
- # "attaching" invalid EC2 state ! #1074901
volume.attach(instance.id, "/dev/vdh")
def _volume_state():
volume.update(validate=True)
return volume.status
- #self.assertVolumeStatusWait(_volume_state, "in-use") # #1074901
+ self.assertVolumeStatusWait(_volume_state, "in-use")
re_search_wait(_volume_state, "in-use")
#NOTE(afazekas): Different Hypervisor backends names
@@ -229,9 +228,9 @@
#TODO(afazekas): Resource compare to the flavor settings
- volume.detach() # "detaching" invalid EC2 status #1074901
+ volume.detach()
- #self.assertVolumeStatusWait(_volume_state, "available")
+ self.assertVolumeStatusWait(_volume_state, "available")
re_search_wait(_volume_state, "available")
LOG.info("Volume %s state: %s", volume.id, volume.status)
diff --git a/tempest/tests/boto/test_ec2_volumes.py b/tempest/tests/boto/test_ec2_volumes.py
index dc8ff31..37a913e 100644
--- a/tempest/tests/boto/test_ec2_volumes.py
+++ b/tempest/tests/boto/test_ec2_volumes.py
@@ -39,7 +39,6 @@
cls.client = cls.os.ec2api_client
cls.zone = cls.client.get_good_zone()
-#NOTE(afazekas): as admin it can trigger the Bug #1074901
@attr(type='smoke')
def test_create_get_delete(self):
# EC2 Create, get, delete Volume
diff --git a/tempest/tests/compute/admin/test_aggregates.py b/tempest/tests/compute/admin/test_aggregates.py
index 06acc41..07df77f 100644
--- a/tempest/tests/compute/admin/test_aggregates.py
+++ b/tempest/tests/compute/admin/test_aggregates.py
@@ -27,13 +27,14 @@
Tests Aggregates API that require admin privileges
"""
+ _host_key = 'OS-EXT-SRV-ATTR:host'
_interface = 'json'
@classmethod
def setUpClass(cls):
super(AggregatesAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.aggregates_client
- cls.user_client = cls.os.aggregates_client
+ cls.user_client = cls.aggregates_client
cls.aggregate_name_prefix = 'test_aggregate_'
cls.az_name_prefix = 'test_az_'
@@ -212,7 +213,7 @@
availability_zone=az_name)
servers_client.wait_for_server_status(server['id'], 'ACTIVE')
resp, body = admin_servers_client.get_server(server['id'])
- self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host'])
+ self.assertEqual(self.host, body[self._host_key])
@attr(type='negative')
def test_aggregate_add_non_exist_host(self):
@@ -254,3 +255,9 @@
self.assertRaises(exceptions.Unauthorized,
self.user_client.remove_host,
aggregate['id'], self.host)
+
+
+class AggregatesAdminTestXML(AggregatesAdminTestJSON):
+ _host_key = (
+ '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host')
+ _interface = 'xml'
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 221cfb6..b313e0b 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -62,6 +62,7 @@
cls.interfaces_client = os.interfaces_client
cls.fixed_ips_client = os.fixed_ips_client
cls.availability_zone_client = os.availability_zone_client
+ cls.aggregates_client = os.aggregates_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/floating_ips/test_floating_ips_actions.py b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
index d800fb5..5fe911f 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -53,8 +53,8 @@
@classmethod
def tearDownClass(cls):
#Deleting the floating IP which is created in this method
- super(FloatingIPsTestJSON, cls).tearDownClass()
resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
+ super(FloatingIPsTestJSON, cls).tearDownClass()
@attr(type='positive')
def test_allocate_floating_ip(self):
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index 105a38a..9ec05dd 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -37,10 +37,10 @@
@classmethod
def tearDownClass(cls):
"""Delete images after a test is executed."""
- super(ImagesWhiteboxTest, cls).tearDownClass()
for image_id in cls.image_ids:
cls.client.delete_image(image_id)
cls.image_ids.remove(image_id)
+ super(ImagesWhiteboxTest, cls).tearDownClass()
@classmethod
def update_state(self, server_id, vm_state, task_state, deleted=0):
diff --git a/tempest/tests/compute/servers/test_server_advanced_ops.py b/tempest/tests/compute/servers/test_server_advanced_ops.py
index 8be9c54..ad859d0 100644
--- a/tempest/tests/compute/servers/test_server_advanced_ops.py
+++ b/tempest/tests/compute/servers/test_server_advanced_ops.py
@@ -66,18 +66,18 @@
self.assertEqual(self.instance.status, 'BUILD')
instance_id = self.get_resource('instance').id
- test.status_timeout(
- self, self.compute_client.servers, instance_id, 'ACTIVE')
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'ACTIVE')
instance = self.get_resource('instance')
instance_id = instance.id
resize_flavor = self.config.compute.flavor_ref_alt
LOG.debug("Resizing instance %s from flavor %s to flavor %s",
instance.id, instance.flavor, resize_flavor)
instance.resize(resize_flavor)
- test.status_timeout(self, self.compute_client.servers, instance_id,
+ self.status_timeout(self.compute_client.servers, instance_id,
'VERIFY_RESIZE')
LOG.debug("Confirming resize of instance %s", instance_id)
instance.confirm_resize()
- test.status_timeout(
- self, self.compute_client.servers, instance_id, 'ACTIVE')
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'ACTIVE')
diff --git a/tempest/tests/compute/servers/test_server_basic_ops.py b/tempest/tests/compute/servers/test_server_basic_ops.py
index e4e246a..fdbbd3c 100644
--- a/tempest/tests/compute/servers/test_server_basic_ops.py
+++ b/tempest/tests/compute/servers/test_server_basic_ops.py
@@ -101,8 +101,8 @@
def wait_on_active(self):
instance_id = self.get_resource('instance').id
- test.status_timeout(
- self, self.compute_client.servers, instance_id, 'ACTIVE')
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'ACTIVE')
def pause_server(self):
instance = self.get_resource('instance')
@@ -110,8 +110,8 @@
LOG.debug("Pausing instance %s. Current status: %s",
instance_id, instance.status)
instance.pause()
- test.status_timeout(
- self, self.compute_client.servers, instance_id, 'PAUSED')
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'PAUSED')
def unpause_server(self):
instance = self.get_resource('instance')
@@ -119,8 +119,8 @@
LOG.debug("Unpausing instance %s. Current status: %s",
instance_id, instance.status)
instance.unpause()
- test.status_timeout(
- self, self.compute_client.servers, instance_id, 'ACTIVE')
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'ACTIVE')
def suspend_server(self):
instance = self.get_resource('instance')
@@ -128,7 +128,7 @@
LOG.debug("Suspending instance %s. Current status: %s",
instance_id, instance.status)
instance.suspend()
- test.status_timeout(self, self.compute_client.servers,
+ self.status_timeout(self.compute_client.servers,
instance_id, 'SUSPENDED')
def resume_server(self):
@@ -137,8 +137,8 @@
LOG.debug("Resuming instance %s. Current status: %s",
instance_id, instance.status)
instance.resume()
- test.status_timeout(
- self, self.compute_client.servers, instance_id, 'ACTIVE')
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'ACTIVE')
def terminate_instance(self):
instance = self.get_resource('instance')
diff --git a/tempest/tests/compute/servers/test_server_rescue.py b/tempest/tests/compute/servers/test_server_rescue.py
index 04c5b27..862a86a 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -85,7 +85,6 @@
@classmethod
def tearDownClass(cls):
- super(ServerRescueTestJSON, cls).tearDownClass()
#Deleting the floating IP which is created in this method
cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
client = cls.volumes_extensions_client
@@ -93,6 +92,7 @@
client.delete_volume(str(cls.volume_to_detach['id']).strip())
resp, cls.sg = cls.security_groups_client.delete_security_group(
cls.sg_id)
+ super(ServerRescueTestJSON, cls).tearDownClass()
def tearDown(self):
super(ServerRescueTestJSON, self).tearDown()
diff --git a/tempest/tests/identity/admin/v3/test_services.py b/tempest/tests/identity/admin/v3/test_services.py
new file mode 100644
index 0000000..fef3bca
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_services.py
@@ -0,0 +1,56 @@
+#vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.tests.identity import base
+
+
+class ServicesTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ def test_update_service(self):
+ # Update description attribute of service
+ name = rand_name('service-')
+ type = rand_name('type--')
+ description = rand_name('description-')
+ resp, body = self.client.create_service(
+ name, type, description=description)
+ self.assertEqual('200', resp['status'])
+ #Deleting the service created in this method
+ self.addCleanup(self.client.delete_service, body['id'])
+
+ s_id = body['id']
+ resp1_desc = body['description']
+
+ s_desc2 = rand_name('desc2-')
+ resp, body = self.service_client.update_service(
+ s_id, description=s_desc2)
+ resp2_desc = body['description']
+ self.assertEqual('200', resp['status'])
+ self.assertNotEqual(resp1_desc, resp2_desc)
+
+ #Get service
+ resp, body = self.client.get_service(s_id)
+ resp3_desc = body['description']
+
+ self.assertNotEqual(resp1_desc, resp3_desc)
+ self.assertEqual(resp2_desc, resp3_desc)
+
+
+class ServicesTestXML(ServicesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 718b74f..6980425 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -30,6 +30,7 @@
cls.token_client = os.token_client
cls.endpoints_client = os.endpoints_client
cls.v3_client = os.identity_v3_client
+ cls.service_client = os.service_client
if not cls.client.has_admin_extensions():
raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index c01aeaf..19c0aa0 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -81,32 +81,10 @@
self.assertEqual(properties['key2'], 'value2')
def test_register_http_image(self):
- container_client = self.os.container_client
- object_client = self.os.object_client
- container_name = "image_container"
- object_name = "test_image.img"
- container_client.create_container(container_name)
- self.addCleanup(container_client.delete_container, container_name)
- cont_headers = {'X-Container-Read': '.r:*'}
- resp, _ = container_client.update_container_metadata(
- container_name,
- metadata=cont_headers,
- metadata_prefix='')
- self.assertEqual(resp['status'], '204')
-
- data = "TESTIMAGE"
- resp, _ = object_client.create_object(container_name,
- object_name, data)
- self.addCleanup(object_client.delete_object, container_name,
- object_name)
- self.assertEqual(resp['status'], '201')
- object_url = '/'.join((object_client.base_url,
- container_name,
- object_name))
resp, body = self.create_image(name='New Http Image',
container_format='bare',
disk_format='raw', is_public=True,
- copy_from=object_url)
+ copy_from=self.config.images.http_image)
self.assertTrue('id' in body)
image_id = body.get('id')
self.created_images.append(image_id)
@@ -115,7 +93,6 @@
self.client.wait_for_image_status(image_id, 'active')
resp, body = self.client.get_image(image_id)
self.assertEqual(resp['status'], '200')
- self.assertEqual(body, data)
@attr(type='image')
def test_register_image_with_min_ram(self):
diff --git a/tempest/tests/network/common.py b/tempest/tests/network/common.py
index 6246f54..6811acf 100644
--- a/tempest/tests/network/common.py
+++ b/tempest/tests/network/common.py
@@ -269,7 +269,7 @@
self.set_resource(name, server)
except AttributeError:
self.fail("Server not successfully created.")
- test.status_timeout(self, client.servers, server.id, 'ACTIVE')
+ 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.
diff --git a/tempest/tests/volume/admin/test_multi_backend.py b/tempest/tests/volume/admin/test_multi_backend.py
index 04007c9..3d5fae4 100644
--- a/tempest/tests/volume/admin/test_multi_backend.py
+++ b/tempest/tests/volume/admin/test_multi_backend.py
@@ -97,8 +97,6 @@
@classmethod
def tearDownClass(cls):
- super(VolumeMultiBackendTest, cls).tearDownClass()
-
## volumes deletion
for volume_id in cls.volume_id_list:
cls.client.delete_volume(volume_id)
@@ -108,6 +106,8 @@
for volume_type in cls.volume_type_list:
cls.client2.delete_volume_type(volume_type)
+ super(VolumeMultiBackendTest, cls).tearDownClass()
+
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
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 c8cf8d9..1cd7653 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
@@ -30,8 +30,8 @@
@classmethod
def tearDownClass(cls):
- super(VolumeTypesExtraSpecsTest, cls).tearDownClass()
cls.client.delete_volume_type(cls.volume_type['id'])
+ super(VolumeTypesExtraSpecsTest, cls).tearDownClass()
def test_volume_type_extra_specs_list(self):
# List Volume types extra specs.
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 13fcbbf..bd6e279 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
@@ -36,8 +36,8 @@
@classmethod
def tearDownClass(cls):
- super(ExtraSpecsNegativeTest, cls).tearDownClass()
cls.client.delete_volume_type(cls.volume_type['id'])
+ super(ExtraSpecsNegativeTest, cls).tearDownClass()
def test_update_no_body(self):
# Should not update volume type extra specs with no body
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index fb9b975..e6eb8d8 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -43,7 +43,6 @@
@classmethod
def tearDownClass(cls):
- super(VolumesActionsTest, cls).tearDownClass()
# Delete the test instance and volume
cls.client.delete_volume(cls.volume['id'])
cls.client.wait_for_resource_deletion(cls.volume['id'])
@@ -51,6 +50,8 @@
cls.servers_client.delete_server(cls.server['id'])
cls.client.wait_for_resource_deletion(cls.server['id'])
+ super(VolumesActionsTest, cls).tearDownClass()
+
@attr(type='smoke')
def test_attach_detach_volume_to_instance(self):
# Volume is attached and detached successfully from an instance
diff --git a/tox.ini b/tox.ini
index 4a2f80e..565a9ad 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,17 +9,52 @@
NOSE_OPENSTACK_YELLOW=3
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
-deps = -r{toxinidir}/tools/pip-requires
- -r{toxinidir}/tools/test-requires
-commands = nosetests {posargs}
+
+[testenv:full]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+ NOSE_WITH_OPENSTACK=1
+ NOSE_OPENSTACK_COLOR=1
+ NOSE_OPENSTACK_RED=15
+ NOSE_OPENSTACK_YELLOW=3
+ 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
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-cli.xml -sv cli
+
+[testenv:smoke]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+ NOSE_WITH_OPENSTACK=1
+ NOSE_OPENSTACK_COLOR=1
+ NOSE_OPENSTACK_RED=15
+ NOSE_OPENSTACK_YELLOW=3
+ NOSE_OPENSTACK_SHOW_ELAPSED=1
+ NOSE_OPENSTACK_STDOUT=1
+commands =
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --attr=type=smoke --xunit-file=nosetests-smoke.xml tempest
+
[testenv:coverage]
-commands = python -m tools/tempest_coverage -c start --combine
- nosetests {posargs}
- python -m tools/tempest_coverage -c report --html
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+ NOSE_WITH_OPENSTACK=1
+ NOSE_OPENSTACK_COLOR=1
+ NOSE_OPENSTACK_RED=15
+ NOSE_OPENSTACK_YELLOW=3
+ NOSE_OPENSTACK_SHOW_ELAPSED=1
+ 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
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-cli.xml -sv cli
+ python -m tools/tempest_coverage -c report --html
[testenv:pep8]
commands = flake8
+deps = -r{toxinidir}/tools/pip-requires
+ -r{toxinidir}/tools/test-requires
[flake8]
ignore = E125,H302,H404