Merge "add tests for certificates"
diff --git a/HACKING.rst b/HACKING.rst
index 7d995c3..377c647 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -163,3 +163,26 @@
- If the execution of a set of tests is required to be serialized then locking
can be used to perform this. See AggregatesAdminTest in
tempest.api.compute.admin for an example of using locking.
+
+Stress Tests in Tempest
+-----------------------
+Any tempest test case can be flagged as a stress test. With this flag it will
+be automatically discovery and used in the stress test runs. The stress test
+framework itself is a facility to spawn and control worker processes in order
+to find race conditions (see ``tempest/stress/`` for more information). Please
+note that these stress tests can't be used for benchmarking purposes since they
+don't measure any performance characteristics.
+
+Example::
+
+ @stresstest(class_setup_per='process')
+ def test_this_and_that(self):
+ ...
+
+This will flag the test ``test_this_and_that`` as a stress test. The parameter
+``class_setup_per`` gives control when the setUpClass function should be called.
+
+Good candidates for stress tests are:
+
+- Scenario tests
+- API tests that have a wide focus
diff --git a/etc/README.txt b/etc/README.txt
deleted file mode 100644
index c7e5e6e..0000000
--- a/etc/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Copy config.ini.sample to config.ini, and update it to reflect your environment.
diff --git a/etc/TEMPEST_README.txt b/etc/TEMPEST_README.txt
deleted file mode 100644
index 50fa688..0000000
--- a/etc/TEMPEST_README.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-To run:
--rename the /etc/tempest.conf.sample file to tempest.conf
--Set the fields in the file to values relevant to your system
--Set the "authentication" value (basic or keystone_v2 currently supported)
--From the root directory of the project, run "./run_tests.sh" this will
-create the venv to install the project dependencies and run nosetests tempest
-to run all the tests
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 87bf758..fc4f9cd 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -138,6 +138,11 @@
# this value as "compute"
catalog_type = compute
+# The type of endpoint for a Compute v3 API service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "computev3"
+catalog_v3_type = computev3
+
# The name of a region for compute. If empty or commented-out, the value of
# identity.region is used instead. If no such region is found in the service
# catalog, the first found one is used.
@@ -147,6 +152,9 @@
volume_device_name = vdb
[compute-feature-enabled]
+# Do we run the Nova V3 API tests?
+api_v3 = false
+
# Does the Compute API support creation of images?
create_image = true
diff --git a/include/sample_vm/README.txt b/include/sample_vm/README.txt
deleted file mode 100644
index 51b609d..0000000
--- a/include/sample_vm/README.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-You will need to download an image into this directory..
-Will also need to update the tests to reference this new image.
-
-You could use e.g. the Ubuntu Natty cloud images (this matches the sample configuration):
-$ wget http://cloud-images.ubuntu.com/releases/natty/release/ubuntu-11.04-server-cloudimg-amd64.tar.gz
-$ tar xvzf ubuntu-11.04-server-cloudimg-amd64.tar.gz
diff --git a/include/swift_objects/README.txt b/include/swift_objects/README.txt
deleted file mode 100644
index 3857524..0000000
--- a/include/swift_objects/README.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-## For the swift tests you will need three objects to upload for the test
-## examples below are a 512K object, a 500M object, and 1G object
-dd if=/dev/zero of=swift_small bs=512 count=1024
-dd if=/dev/zero of=swift_medium bs=512 count=1024000
-dd if=/dev/zero of=swift_large bs=1024 count=1024000
diff --git a/openstack-common.conf b/openstack-common.conf
index dabf5a0..38d58ee 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,6 +1,7 @@
[DEFAULT]
# The list of modules to copy from openstack-common
+module=config
module=install_venv_common
module=lockutils
module=log
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 4ff6b07..ff1ef81 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -255,11 +255,10 @@
self.client.add_host(aggregate['id'], self.host)
self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
server_name = rand_name('test_server_')
- servers_client = self.servers_client
admin_servers_client = self.os_adm.servers_client
- resp, server = self.create_server(name=server_name,
- availability_zone=az_name)
- servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.create_test_server(name=server_name,
+ availability_zone=az_name,
+ wait_until='ACTIVE')
resp, body = admin_servers_client.get_server(server['id'])
self.assertEqual(self.host, body[self._host_key])
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 766589e..427f728 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -31,7 +31,7 @@
raise cls.skipException(msg)
cls.client = cls.os_adm.fixed_ips_client
cls.non_admin_client = cls.fixed_ips_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
resp, server = cls.servers_client.get_server(server['id'])
for ip_set in server['addresses']:
for ip in server['addresses'][ip_set]:
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index e8ae3b4..43d4ea3 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -15,13 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_int_id
from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
@@ -43,16 +40,38 @@
cls.client = cls.os_adm.flavors_client
admin_client = cls._get_identity_admin_client()
- resp, tenants = admin_client.list_tenants()
- cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
- cls.flavors_client.tenant_name][0]
-
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
cls.vcpus = 1
cls.disk = 10
@attr(type='gate')
+ def test_flavor_access_list_with_private_flavor(self):
+ # Test to list flavor access successfully by querying private flavor
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(len(flavor_access), 1, str(flavor_access))
+ first_flavor = flavor_access[0]
+ self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
+ self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+
+ @attr(type='gate')
def test_flavor_access_add_remove(self):
# Test to add and remove flavor access to a given tenant.
flavor_name = rand_name(self.flavor_name_prefix)
@@ -89,84 +108,6 @@
self.assertEqual(resp.status, 200)
self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_add(self):
- # Test to add flavor access as a user without admin privileges.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.add_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_remove(self):
- # Test to remove flavor access as a user without admin privileges.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- # Add flavor access to a tenant.
- self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
- self.addCleanup(self.client.remove_flavor_access,
- new_flavor['id'], self.tenant_id)
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.remove_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_add_flavor_access_duplicate(self):
- # Create a new flavor.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
-
- # Add flavor access to a tenant.
- self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
- self.addCleanup(self.client.remove_flavor_access,
- new_flavor['id'], self.tenant_id)
-
- # An exception should be raised when adding flavor access to the same
- # tenant
- self.assertRaises(exceptions.Conflict,
- self.client.add_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_remove_flavor_access_not_found(self):
- # Create a new flavor.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
-
- # An exception should be raised when flavor access is not found
- self.assertRaises(exceptions.NotFound,
- self.client.remove_flavor_access,
- new_flavor['id'],
- str(uuid.uuid4()))
-
class FlavorsAdminTestXML(FlavorsAccessTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
new file mode 100644
index 0000000..4d2983c
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -0,0 +1,154 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM 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 uuid
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_int_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavor Access API extension.
+ Add and remove Flavor Access require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
+ if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ admin_client = cls._get_identity_admin_client()
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
+ cls.flavor_name_prefix = 'test_flavor_access_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_access_list_with_public_flavor(self):
+ # Test to list flavor access with exceptions by querying public flavor
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='True')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_flavor_access,
+ new_flavor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_add(self):
+ # Test to add flavor access as a user without admin privileges.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_remove(self):
+ # Test to remove flavor access as a user without admin privileges.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.remove_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_add_flavor_access_duplicate(self):
+ # Create a new flavor.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+
+ # An exception should be raised when adding flavor access to the same
+ # tenant
+ self.assertRaises(exceptions.Conflict,
+ self.client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_flavor_access_not_found(self):
+ # Create a new flavor.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # An exception should be raised when flavor access is not found
+ self.assertRaises(exceptions.NotFound,
+ self.client.remove_flavor_access,
+ new_flavor['id'],
+ str(uuid.uuid4()))
+
+
+class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 8e451a0..22e6cf1 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -32,18 +32,11 @@
super(HostsAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.hosts_client
- def _get_host_name(self):
- resp, hosts = self.client.list_hosts()
- self.assertEqual(200, resp.status)
- self.assertTrue(len(hosts) >= 1)
- hostname = hosts[0]['host_name']
- return hostname
-
@attr(type='gate')
def test_list_hosts(self):
resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
- self.assertTrue(len(hosts) >= 2)
+ self.assertTrue(len(hosts) >= 2, str(hosts))
@attr(type='gate')
def test_list_hosts_with_zone(self):
@@ -77,18 +70,24 @@
@attr(type='gate')
def test_show_host_detail(self):
- hostname = self._get_host_name()
-
- resp, resources = self.client.show_host_detail(hostname)
+ resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
- self.assertTrue(len(resources) >= 1)
- host_resource = resources[0]['resource']
- self.assertIsNotNone(host_resource)
- self.assertIsNotNone(host_resource['cpu'])
- self.assertIsNotNone(host_resource['disk_gb'])
- self.assertIsNotNone(host_resource['memory_mb'])
- self.assertIsNotNone(host_resource['project'])
- self.assertEqual(hostname, host_resource['host'])
+
+ hosts = [host for host in hosts if host['service'] == 'compute']
+ self.assertTrue(len(hosts) >= 1)
+
+ for host in hosts:
+ hostname = host['host_name']
+ resp, resources = self.client.show_host_detail(hostname)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(resources) >= 1)
+ host_resource = resources[0]['resource']
+ self.assertIsNotNone(host_resource)
+ self.assertIsNotNone(host_resource['cpu'])
+ self.assertIsNotNone(host_resource['disk_gb'])
+ self.assertIsNotNone(host_resource['memory_mb'])
+ self.assertIsNotNone(host_resource['project'])
+ self.assertEqual(hostname, host_resource['host'])
class HostsAdminTestXML(HostsAdminTestJSON):
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 6c4d350..ded0477 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -36,8 +36,6 @@
cls.identity_admin_client = cls._get_identity_admin_client()
cls.sg_client = cls.security_groups_client
- resp, tenants = cls.identity_admin_client.list_tenants()
-
# NOTE(afazekas): these test cases should always create and use a new
# tenant most of them should be skipped if we can't do that
cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get(
@@ -119,7 +117,7 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
cores=default_vcpu_quota)
- self.assertRaises(exceptions.OverLimit, self.create_server)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
@attr(type='gate')
def test_create_server_when_memory_quota_is_full(self):
@@ -134,7 +132,7 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
ram=default_mem_quota)
- self.assertRaises(exceptions.OverLimit, self.create_server)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
@attr(type='gate')
def test_update_quota_normal_user(self):
@@ -155,7 +153,7 @@
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)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
@skip_because(bug="1186354",
condition=config.TempestConfig().service_available.neutron)
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index ebc661c..8c6cf8d 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -42,13 +42,13 @@
cls.tenant_id = tenant['id']
cls.s1_name = rand_name('server')
- resp, server = cls.create_server(name=cls.s1_name,
- wait_until='ACTIVE')
+ resp, server = cls.create_test_server(name=cls.s1_name,
+ wait_until='ACTIVE')
cls.s1_id = server['id']
cls.s2_name = rand_name('server')
- resp, server = cls.create_server(name=cls.s2_name,
- wait_until='ACTIVE')
+ resp, server = cls.create_test_server(name=cls.s2_name,
+ wait_until='ACTIVE')
def _get_unused_flavor_id(self):
flavor_id = rand_int_id(start=1000)
@@ -83,7 +83,7 @@
@attr(type='gate')
def test_admin_delete_servers_of_others(self):
# Administrator can delete servers of others
- _, server = self.create_server()
+ _, server = self.create_test_server()
resp, _ = self.client.delete_server(server['id'])
self.assertEqual('204', resp['status'])
self.servers_client.wait_for_server_termination(server['id'])
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index 3178ead..a599f06 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -39,7 +39,7 @@
cls.client.tenant_name][0]
# Create a server in the demo tenant
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
time.sleep(2)
now = datetime.datetime.now()
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 9bf897e..de8b312 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -72,19 +72,6 @@
pass
@classmethod
- def rebuild_server(cls, **kwargs):
- # Destroy an existing server and creates a new one
- try:
- cls.servers_client.delete_server(cls.server_id)
- cls.servers_client.wait_for_server_termination(cls.server_id)
- except Exception as exc:
- LOG.exception(exc)
- pass
- resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
- cls.server_id = server['id']
- cls.password = server['adminPass']
-
- @classmethod
def clear_images(cls):
for image_id in cls.images:
try:
@@ -105,7 +92,7 @@
super(BaseComputeTest, cls).tearDownClass()
@classmethod
- def create_server(cls, **kwargs):
+ def create_test_server(cls, **kwargs):
"""Wrapper utility that returns a test server."""
name = rand_name(cls.__name__ + "-instance")
if 'name' in kwargs:
@@ -132,25 +119,6 @@
return resp, body
- @classmethod
- def create_image_from_server(cls, server_id, **kwargs):
- """Wrapper utility that returns an image created from the server."""
- name = rand_name(cls.__name__ + "-image")
- if 'name' in kwargs:
- name = kwargs.pop('name')
-
- resp, image = cls.images_client.create_image(
- server_id, name)
- image_id = parse_image_id(resp['location'])
- cls.images.append(image_id)
-
- if 'wait_until' in kwargs:
- cls.images_client.wait_for_image_status(image_id,
- kwargs['wait_until'])
- resp, image = cls.images_client.get_image(image_id)
-
- return resp, image
-
def wait_for(self, condition):
"""Repeatedly calls condition() until a timeout."""
start_time = int(time.time())
@@ -192,6 +160,38 @@
cls.servers_client_v3_auth = cls.os.servers_client_v3_auth
cls.certificates_client = cls.os.certificates_client
+ @classmethod
+ def create_image_from_server(cls, server_id, **kwargs):
+ """Wrapper utility that returns an image created from the server."""
+ name = rand_name(cls.__name__ + "-image")
+ if 'name' in kwargs:
+ name = kwargs.pop('name')
+
+ resp, image = cls.images_client.create_image(
+ server_id, name)
+ image_id = parse_image_id(resp['location'])
+ cls.images.append(image_id)
+
+ if 'wait_until' in kwargs:
+ cls.images_client.wait_for_image_status(image_id,
+ kwargs['wait_until'])
+ resp, image = cls.images_client.get_image(image_id)
+
+ return resp, image
+
+ @classmethod
+ def rebuild_server(cls, **kwargs):
+ # Destroy an existing server and creates a new one
+ try:
+ cls.servers_client.delete_server(cls.server_id)
+ cls.servers_client.wait_for_server_termination(cls.server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
+ resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
+ cls.server_id = server['id']
+ cls.password = server['adminPass']
+
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
"""Base test case class for Compute Admin V2 API tests."""
@@ -216,3 +216,78 @@
interface=cls._interface)
else:
cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+
+class BaseV3ComputeTest(BaseComputeTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV3ComputeTest, cls).setUpClass()
+ if not cls.config.compute_feature_enabled.api_v3:
+ skip_msg = ("%s skipped as nova v3 api is not available" %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ cls.servers_client = cls.os.servers_v3_client
+ cls.images_client = cls.os.image_client
+ cls.services_client = cls.os.services_v3_client
+
+ @classmethod
+ def create_image_from_server(cls, server_id, **kwargs):
+ """Wrapper utility that returns an image created from the server."""
+ name = rand_name(cls.__name__ + "-image")
+ if 'name' in kwargs:
+ name = kwargs.pop('name')
+
+ resp, image = cls.servers_client.create_image(
+ server_id, name)
+ image_id = parse_image_id(resp['location'])
+ cls.images.append(image_id)
+
+ if 'wait_until' in kwargs:
+ cls.images_client.wait_for_image_status(image_id,
+ kwargs['wait_until'])
+ resp, image = cls.images_client.get_image_meta(image_id)
+
+ return resp, image
+
+ @classmethod
+ def rebuild_server(cls, **kwargs):
+ # Destroy an existing server and creates a new one
+ try:
+ cls.servers_client.delete_server(cls.server_id)
+ cls.servers_client.wait_for_server_termination(cls.server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
+ resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
+ cls.server_id = server['id']
+ cls.password = server['admin_pass']
+
+
+class BaseV3ComputeAdminTest(BaseV3ComputeTest):
+ """Base test case class for all Compute Admin API V3 tests."""
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV3ComputeAdminTest, cls).setUpClass()
+ admin_username = cls.config.compute_admin.username
+ admin_password = cls.config.compute_admin.password
+ admin_tenant = cls.config.compute_admin.tenant_name
+ if not (admin_username and admin_password and admin_tenant):
+ msg = ("Missing Compute Admin API credentials "
+ "in configuration.")
+ raise cls.skipException(msg)
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_admin_creds()
+ admin_username, admin_tenant_name, admin_password = creds
+ os_adm = clients.Manager(username=admin_username,
+ password=admin_password,
+ tenant_name=admin_tenant_name,
+ interface=cls._interface)
+ else:
+ os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+ cls.os_adm = os_adm
+ cls.severs_admin_client = cls.os_adm.servers_v3_client
+ cls.services_admin_client = cls.os_adm.services_v3_client
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index ff7188b..21a2256 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -35,7 +35,7 @@
cls.servers_client = cls.servers_client
# Server creation
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
resp, body = cls.servers_client.get_server(server['id'])
# Floating IP creation
@@ -64,10 +64,10 @@
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
# should be successful
+ resp, body = self.client.create_floating_ip()
+ self.assertEqual(200, resp.status)
+ floating_ip_id_allocated = body['id']
try:
- resp, body = self.client.create_floating_ip()
- self.assertEqual(200, resp.status)
- floating_ip_id_allocated = body['id']
resp, floating_ip_details = \
self.client.get_floating_ip_details(floating_ip_id_allocated)
# Checking if the details of allocated IP is in list of floating IP
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index df857bf..e497062 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -34,7 +34,7 @@
cls.servers_client = cls.servers_client
cls.client = cls.images_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
# Snapshot the server once to save time
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 383ea1d..bb6ead9 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -66,7 +66,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_from_deleted_server(self):
# An image should not be created if the server instance is removed
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
# Delete server before trying to create server
self.servers_client.delete_server(server['id'])
@@ -91,7 +91,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_from_stopped_server(self):
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.stop(server['id'])
self.servers_client.wait_for_server_status(server['id'],
'SHUTOFF')
@@ -106,7 +106,7 @@
@attr(type='gate')
def test_delete_saving_image(self):
snapshot_name = rand_name('test-snap-')
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
resp, image = self.create_image_from_server(server['id'],
name=snapshot_name,
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index bec5ea4..d0bed57 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -63,7 +63,7 @@
raise cls.skipException(skip_msg)
try:
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
except Exception:
cls.tearDownClass()
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 8d4e47b..00a172a 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -38,8 +38,8 @@
cls.image_ids = []
try:
- resp, cls.server1 = cls.create_server()
- resp, cls.server2 = cls.create_server(wait_until='ACTIVE')
+ resp, cls.server1 = cls.create_test_server()
+ resp, cls.server2 = cls.create_test_server(wait_until='ACTIVE')
# NOTE(sdague) this is faster than doing the sync wait_util on both
cls.servers_client.wait_for_server_status(cls.server1['id'],
'ACTIVE')
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 9dc164d..d61acfb 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import config
from tempest import exceptions
from tempest.test import attr
-from tempest.test import skip_because
class SecurityGroupRulesTestJSON(base.BaseV2ComputeTest):
@@ -30,6 +30,7 @@
def setUpClass(cls):
super(SecurityGroupRulesTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
+ cls.neutron_available = cls.config.service_available.neutron
@attr(type='gate')
def test_security_group_rules_create(self):
@@ -93,14 +94,14 @@
self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
+ @attr(type=['negative', 'smoke'])
def test_security_group_rules_create_with_invalid_id(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid Parent group id
# Adding rules to the invalid Security Group id
parent_group_id = data_utils.rand_int_id(start=999)
+ if self.neutron_available:
+ parent_group_id = str(uuid.uuid4())
ip_protocol = 'tcp'
from_port = 22
to_port = 22
@@ -185,15 +186,16 @@
self.client.create_security_group_rule,
secgroup_id, ip_protocol, from_port, to_port)
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
+ @attr(type=['negative', 'smoke'])
def test_security_group_rules_delete_with_invalid_id(self):
# Negative test: Deletion of Security Group rule should be FAIL
# with invalid rule id
+ group_rule_id = data_utils.rand_int_id(start=999)
+ if self.neutron_available:
+ group_rule_id = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound,
self.client.delete_security_group_rule,
- data_utils.rand_int_id(start=999))
+ group_rule_id)
@attr(type='gate')
def test_security_group_rules_list(self):
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 6e08700..7cb96af 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -16,6 +16,7 @@
# under the License.
import testtools
+import uuid
from tempest.api.compute import base
from tempest.common.utils import data_utils
@@ -32,6 +33,7 @@
def setUpClass(cls):
super(SecurityGroupsTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
+ cls.neutron_available = cls.config.service_available.neutron
def _delete_security_group(self, securitygroup_id):
resp, _ = self.client.delete_security_group(securitygroup_id)
@@ -108,9 +110,7 @@
"The fetched Security Group is different "
"from the created Group")
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
+ @attr(type=['negative', 'smoke'])
def test_security_group_get_nonexistant_group(self):
# Negative test:Should not be able to GET the details
# of non-existent Security Group
@@ -121,6 +121,8 @@
# Creating a non-existent Security Group id
while True:
non_exist_id = data_utils.rand_int_id(start=999)
+ if self.neutron_available:
+ non_exist_id = str(uuid.uuid4())
if non_exist_id not in security_group_id:
break
self.assertRaises(exceptions.NotFound, self.client.get_security_group,
@@ -198,9 +200,7 @@
self.client.delete_security_group,
default_security_group_id)
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
+ @attr(type=['negative', 'smoke'])
def test_delete_nonexistant_security_group(self):
# Negative test:Deletion of a non-existent Security Group should Fail
security_group_id = []
@@ -210,6 +210,8 @@
# Creating non-existent Security Group
while True:
non_exist_id = data_utils.rand_int_id(start=999)
+ if self.neutron_available:
+ non_exist_id = str(uuid.uuid4())
if non_exist_id not in security_group_id:
break
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index c554dc2..a177cea 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -40,8 +40,7 @@
self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip)
def _create_server_get_interfaces(self):
- resp, server = self.create_server()
- self.os.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
resp, ifs = self.client.list_interfaces(server['id'])
resp, body = self.client.wait_for_interface_status(
server['id'], ifs[0]['port_id'], 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index adbc048..58606eb 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -44,12 +44,12 @@
personality = [{'path': '/test.txt',
'contents': base64.b64encode(file_contents)}]
cls.client = cls.servers_client
- cli_resp = cls.create_server(name=cls.name,
- meta=cls.meta,
- accessIPv4=cls.accessIPv4,
- accessIPv6=cls.accessIPv6,
- personality=personality,
- disk_config=cls.disk_config)
+ cli_resp = cls.create_test_server(name=cls.name,
+ meta=cls.meta,
+ accessIPv4=cls.accessIPv4,
+ accessIPv6=cls.accessIPv6,
+ personality=personality,
+ disk_config=cls.disk_config)
cls.resp, cls.server_initial = cli_resp
cls.password = cls.server_initial['adminPass']
cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 76a7117..d3eaaa1 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -36,8 +36,8 @@
@attr(type='gate')
def test_rebuild_server_with_manual_disk_config(self):
# A server should be rebuilt using the manual disk config option
- resp, server = self.create_server(disk_config='AUTO',
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(disk_config='AUTO',
+ wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
# Verify the specified attributes are set correctly
@@ -58,8 +58,8 @@
@attr(type='gate')
def test_rebuild_server_with_auto_disk_config(self):
# A server should be rebuilt using the auto disk config option
- resp, server = self.create_server(disk_config='MANUAL',
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(disk_config='MANUAL',
+ wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
# Verify the specified attributes are set correctly
@@ -81,9 +81,10 @@
@attr(type='gate')
def test_resize_server_from_manual_to_auto(self):
# A server should be resized from manual to auto disk config
- resp, server = self.create_server(disk_config='MANUAL',
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(disk_config='MANUAL',
+ wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
+
# Resize with auto option
self.client.resize(server['id'], self.flavor_ref_alt,
disk_config='AUTO')
@@ -98,8 +99,8 @@
@attr(type='gate')
def test_resize_server_from_auto_to_manual(self):
# A server should be resized from auto to manual disk config
- resp, server = self.create_server(disk_config='AUTO',
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(disk_config='AUTO',
+ wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
# Resize with manual option
@@ -115,8 +116,8 @@
@attr(type='gate')
def test_update_server_from_auto_to_manual(self):
# A server should be updated from auto to manual disk config
- resp, server = self.create_server(disk_config='AUTO',
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(disk_config='AUTO',
+ wait_until='ACTIVE')
self.addCleanup(self.client.delete_server, server['id'])
# Verify the disk_config attribute is set correctly
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index 61be50a..5019003 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -27,7 +27,7 @@
def setUpClass(cls):
super(InstanceActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.request_id = resp['x-compute-request-id']
cls.server_id = server['id']
@@ -39,7 +39,7 @@
resp, body = self.client.list_instance_actions(self.server_id)
self.assertEqual(200, resp.status)
- self.assertTrue(len(body) == 2)
+ self.assertTrue(len(body) == 2, str(body))
self.assertTrue(any([i for i in body if i['action'] == 'create']))
self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 778b8ec..65bdd19 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -58,22 +58,18 @@
cls.image_ref_alt)
cls.s1_name = rand_name(cls.__name__ + '-instance')
- resp, cls.s1 = cls.create_server(name=cls.s1_name,
- image_id=cls.image_ref,
- flavor=cls.flavor_ref,
- wait_until='ACTIVE')
+ resp, cls.s1 = cls.create_test_server(name=cls.s1_name,
+ wait_until='ACTIVE')
cls.s2_name = rand_name(cls.__name__ + '-instance')
- resp, cls.s2 = cls.create_server(name=cls.s2_name,
- image_id=cls.image_ref_alt,
- flavor=cls.flavor_ref,
- wait_until='ACTIVE')
+ resp, cls.s2 = cls.create_test_server(name=cls.s2_name,
+ image_id=cls.image_ref_alt,
+ wait_until='ACTIVE')
cls.s3_name = rand_name(cls.__name__ + '-instance')
- resp, cls.s3 = cls.create_server(name=cls.s3_name,
- image_id=cls.image_ref,
- flavor=cls.flavor_ref_alt,
- wait_until='ACTIVE')
+ resp, cls.s3 = cls.create_test_server(name=cls.s3_name,
+ flavor=cls.flavor_ref_alt,
+ wait_until='ACTIVE')
cls.fixed_network_name = cls.config.compute.fixed_network_name
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 088d3ae..a06e209 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -91,10 +91,10 @@
cls.deleted_fixtures = []
cls.start_time = datetime.datetime.utcnow()
for x in xrange(2):
- resp, srv = cls.create_server()
+ resp, srv = cls.create_test_server()
cls.existing_fixtures.append(srv)
- resp, srv = cls.create_server()
+ resp, srv = cls.create_test_server()
cls.client.delete_server(srv['id'])
# We ignore errors on termination because the server may
# be put into ERROR status on a quick spawn, then delete,
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index d582894..d85db2f 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -34,7 +34,7 @@
created servers into the servers list to be cleaned up after all.
"""
kwargs['name'] = kwargs.get('name', self._generate_name())
- resp, body = self.create_server(**kwargs)
+ resp, body = self.create_test_server(**kwargs)
return resp, body
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index a594f6c..7ca8a52 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -28,7 +28,7 @@
super(ServerAddressesTest, cls).setUpClass()
cls.client = cls.servers_client
- resp, cls.server = cls.create_server(wait_until='ACTIVE')
+ resp, cls.server = cls.create_test_server(wait_until='ACTIVE')
@attr(type=['negative', 'gate'])
def test_list_server_addresses_invalid_server_id(self):
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 4e45e4b..ee0f4a9 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -32,7 +32,7 @@
resp, tenants = cls.admin_client.list_tenants()
cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
cls.client.tenant_name][0]
- resp, server = cls.create_server(meta={}, wait_until='ACTIVE')
+ resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE')
cls.server_id = server['id']
@@ -76,7 +76,7 @@
key = "k" * sz
meta = {key: 'data1'}
self.assertRaises(exceptions.OverLimit,
- self.create_server,
+ self.create_test_server,
meta=meta)
# no teardown - all creates should fail
@@ -143,7 +143,7 @@
# Blank key should trigger an error.
meta = {'': 'data1'}
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
meta=meta)
# GET on a non-existent server should not succeed
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index ba5c0df..c6d2e44 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -43,7 +43,7 @@
path = 'etc/test' + str(i) + '.txt'
personality.append({'path': path,
'contents': base64.b64encode(file_contents)})
- self.assertRaises(exceptions.OverLimit, self.create_server,
+ self.assertRaises(exceptions.OverLimit, self.create_test_server,
personality=personality)
@attr(type='gate')
@@ -60,8 +60,7 @@
'path': path,
'contents': base64.b64encode(file_contents),
})
- resp, server = self.create_server(personality=person)
- self.addCleanup(self.client.delete_server, server['id'])
+ resp, server = self.create_test_server(personality=person)
self.assertEqual('202', resp['status'])
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index f72d36e..837ab48 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -17,7 +17,6 @@
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_name
-import tempest.config
from tempest import exceptions
from tempest.test import attr
@@ -25,8 +24,6 @@
class ServerRescueTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
- run_ssh = tempest.config.TempestConfig().compute.run_ssh
-
@classmethod
def setUpClass(cls):
super(ServerRescueTestJSON, cls).setUpClass()
@@ -62,12 +59,8 @@
cls.volume_to_detach['id'], 'available')
# Server for positive tests
- resp, server = cls.create_server(image_id=cls.image_ref,
- flavor=cls.flavor_ref,
- wait_until='BUILD')
- resp, resc_server = cls.create_server(image_id=cls.image_ref,
- flavor=cls.flavor_ref,
- wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='BUILD')
+ resp, resc_server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
cls.password = server['adminPass']
cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 5f68201..8712234 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -36,7 +36,7 @@
def test_create_server_with_admin_password(self):
# If an admin password is provided on server creation, the server's
# root password should be set to that password.
- resp, server = self.create_server(adminPass='testpassword')
+ resp, server = self.create_test_server(adminPass='testpassword')
# Verify the password is set correctly in the response
self.assertEqual('testpassword', server['adminPass'])
@@ -47,11 +47,11 @@
# TODO(sdague): clear out try, we do cleanup one layer up
server_name = rand_name('server')
- resp, server = self.create_server(name=server_name,
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
id1 = server['id']
- resp, server = self.create_server(name=server_name,
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
id2 = server['id']
self.assertNotEqual(id1, id2, "Did not create a new server")
resp, server = self.client.get_server(id1)
@@ -67,7 +67,7 @@
key_name = rand_name('key')
resp, keypair = self.keypairs_client.create_keypair(key_name)
resp, body = self.keypairs_client.list_keypairs()
- resp, server = self.create_server(key_name=key_name)
+ resp, server = self.create_test_server(key_name=key_name)
self.assertEqual('202', resp['status'])
self.client.wait_for_server_status(server['id'], 'ACTIVE')
resp, server = self.client.get_server(server['id'])
@@ -76,7 +76,7 @@
@attr(type='gate')
def test_update_server_name(self):
# The server name should be changed to the the provided value
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
# Update the server with a new name
resp, server = self.client.update_server(server['id'],
@@ -91,7 +91,7 @@
@attr(type='gate')
def test_update_access_server_address(self):
# The server's access addresses should reflect the provided values
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
# Update the IPv4 and IPv6 access addresses
resp, body = self.client.update_server(server['id'],
@@ -108,21 +108,21 @@
@attr(type='gate')
def test_delete_server_while_in_building_state(self):
# Delete a server while it's VM state is Building
- resp, server = self.create_server(wait_until='BUILD')
+ resp, server = self.create_test_server(wait_until='BUILD')
resp, _ = self.client.delete_server(server['id'])
self.assertEqual('204', resp['status'])
@attr(type='gate')
def test_delete_active_server(self):
# Delete a server while it's VM state is Active
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
resp, _ = self.client.delete_server(server['id'])
self.assertEqual('204', resp['status'])
@attr(type='gate')
def test_create_server_with_ipv6_addr_only(self):
# Create a server without an IPv4 address(only IPv6 address).
- resp, server = self.create_server(accessIPv6='2001:2001::3')
+ resp, server = self.create_test_server(accessIPv6='2001:2001::3')
self.assertEqual('202', resp['status'])
self.client.wait_for_server_status(server['id'], 'ACTIVE')
resp, server = self.client.get_server(server['id'])
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 0f753a0..7eb127d 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -43,7 +43,7 @@
cls.img_client = cls.images_client
cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@attr(type=['negative', 'gate'])
@@ -51,7 +51,7 @@
# Create a server with name parameter empty
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
name='')
@attr(type=['negative', 'gate'])
@@ -63,7 +63,7 @@
'contents': file_contents}]
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
personality=person)
@attr(type=['negative', 'gate'])
@@ -71,7 +71,7 @@
# Create a server with an unknown image
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
image_id=-1)
@attr(type=['negative', 'gate'])
@@ -79,7 +79,7 @@
# Create a server with an unknown flavor
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
flavor=-1,)
@attr(type=['negative', 'gate'])
@@ -88,7 +88,7 @@
IPv4 = '1.1.1.1.1.1'
self.assertRaises(exceptions.BadRequest,
- self.create_server, accessIPv4=IPv4)
+ self.create_test_server, accessIPv4=IPv4)
@attr(type=['negative', 'gate'])
def test_invalid_ip_v6_address(self):
@@ -97,7 +97,14 @@
IPv6 = 'notvalid'
self.assertRaises(exceptions.BadRequest,
- self.create_server, accessIPv6=IPv6)
+ self.create_test_server, accessIPv6=IPv6)
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_nonexistent_server(self):
+ nonexistent_server = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.resize,
+ nonexistent_server, self.flavor_ref)
@attr(type=['negative', 'gate'])
def test_resize_server_with_non_existent_flavor(self):
@@ -133,7 +140,7 @@
@attr(type=['negative', 'gate'])
def test_rebuild_reboot_deleted_server(self):
# Rebuild and Reboot a deleted server
- _, server = self.create_server()
+ _, server = self.create_test_server()
self.client.delete_server(server['id'])
self.client.wait_for_server_termination(server['id'])
@@ -168,7 +175,7 @@
server_name = 12345
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
name=server_name)
@attr(type=['negative', 'gate'])
@@ -177,7 +184,7 @@
server_name = 'a' * 256
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
name=server_name)
@attr(type=['negative', 'gate'])
@@ -187,7 +194,7 @@
networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}]
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
networks=networks)
@attr(type=['negative', 'gate'])
@@ -196,7 +203,7 @@
key_name = rand_name('key')
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
key_name=key_name)
@attr(type=['negative', 'gate'])
@@ -205,7 +212,7 @@
metadata = {'a': 'b' * 260}
self.assertRaises(exceptions.OverLimit,
- self.create_server,
+ self.create_test_server,
meta=metadata)
@attr(type=['negative', 'gate'])
@@ -280,7 +287,7 @@
security_groups = [{'name': 'does_not_exist'}]
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
security_groups=security_groups)
@attr(type=['negative', 'gate'])
@@ -359,6 +366,36 @@
self.client.get_console_output,
nonexistent_server, 10)
+ @attr(type=['negative', 'gate'])
+ def test_force_delete_nonexistent_server_id(self):
+ non_existent_server_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.force_delete_server,
+ non_existent_server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_force_delete_server_invalid_state(self):
+ # we can only force-delete a server in 'soft-delete' state
+ self.assertRaises(exceptions.Conflict,
+ self.client.force_delete_server,
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_restore_nonexistent_server_id(self):
+ non_existent_server_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.restore_soft_deleted_server,
+ non_existent_server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_restore_server_invalid_state(self):
+ # we can only restore-delete a server in 'soft-delete' state
+ self.assertRaises(exceptions.Conflict,
+ self.client.restore_soft_deleted_server,
+ self.server_id)
+
class ServersNegativeTestXML(ServersNegativeTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index a00e8ed..d7309a6 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -34,7 +34,7 @@
def setUpClass(cls):
super(VirtualInterfacesTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@skip_because(bug="1183436",
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index a7d9310..cefbb63 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -59,7 +59,7 @@
cls.alt_security_client = cls.alt_manager.security_groups_client
cls.alt_security_client._set_auth()
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
resp, cls.server = cls.client.get_server(server['id'])
name = rand_name('image')
diff --git a/tempest/api/compute/test_live_block_migration.py b/tempest/api/compute/test_live_block_migration.py
index 7f68ab5..a7b6cd2 100644
--- a/tempest/api/compute/test_live_block_migration.py
+++ b/tempest/api/compute/test_live_block_migration.py
@@ -84,7 +84,7 @@
if 'ACTIVE' == self._get_server_status(server_id):
return server_id
else:
- _, server = self.create_server(wait_until="ACTIVE")
+ _, server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
self.password = server['adminPass']
self.password = 'password'
diff --git a/tempest/api/compute/v3/__init__.py b/tempest/api/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/__init__.py
diff --git a/tempest/api/compute/v3/admin/__init__.py b/tempest/api/compute/v3/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/admin/__init__.py
diff --git a/tempest/api/compute/v3/admin/test_services.py b/tempest/api/compute/v3/admin/test_services.py
new file mode 100644
index 0000000..67f9947
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_services.py
@@ -0,0 +1,135 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServicesAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Services API. List and Enable/Disable require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServicesAdminV3TestJSON, cls).setUpClass()
+ cls.client = cls.services_admin_client
+ cls.non_admin_client = cls.services_client
+
+ @attr(type='gate')
+ def test_list_services(self):
+ resp, services = self.client.list_services()
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(services))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_services_with_non_admin_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_services)
+
+ @attr(type='gate')
+ def test_get_service_by_service_binary_name(self):
+ binary_name = 'nova-compute'
+ params = {'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(services))
+ for service in services:
+ self.assertEqual(binary_name, service['binary'])
+
+ @attr(type='gate')
+ def test_get_service_by_host_name(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ services_on_host = [service for service in services if
+ service['host'] == host_name]
+ params = {'host': host_name}
+ resp, services = self.client.list_services(params)
+
+ # we could have a periodic job checkin between the 2 service
+ # lookups, so only compare binary lists.
+ s1 = map(lambda x: x['binary'], services)
+ s2 = map(lambda x: x['binary'], services_on_host)
+
+ # sort the lists before comparing, to take out dependency
+ # on order.
+ self.assertEqual(sorted(s1), sorted(s2))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_by_invalid_params(self):
+ # return all services if send the request with invalid parameter
+ resp, services = self.client.list_services()
+ params = {'xxx': 'nova-compute'}
+ resp, services_xxx = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(services), len(services_xxx))
+
+ @attr(type='gate')
+ def test_get_service_by_service_and_host_name(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ binary_name = services[0]['binary']
+ params = {'host': host_name, 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(1, len(services))
+ self.assertEqual(host_name, services[0]['host'])
+ self.assertEqual(binary_name, services[0]['binary'])
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_by_invalid_service_and_valid_host(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ params = {'host': host_name, 'binary': 'xxx'}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(services))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_with_valid_service_and_invalid_host(self):
+ resp, services = self.client.list_services()
+ binary_name = services[0]['binary']
+ params = {'host': 'xxx', 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(services))
+
+ @attr(type='gate')
+ def test_service_enable_disable(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ binary_name = services[0]['binary']
+
+ resp, service = self.client.disable_service(host_name, binary_name)
+ self.assertEqual(200, resp.status)
+ params = {'host': host_name, 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual('disabled', services[0]['status'])
+
+ resp, service = self.client.enable_service(host_name, binary_name)
+ self.assertEqual(200, resp.status)
+ resp, services = self.client.list_services(params)
+ self.assertEqual('enabled', services[0]['status'])
+
+
+class ServicesAdminV3TestXML(ServicesAdminV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/images/__init__.py b/tempest/api/compute/v3/images/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/images/__init__.py
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
new file mode 100644
index 0000000..ac2deb4
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -0,0 +1,128 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils.data_utils import parse_image_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesV3TestJSON, cls).setUpClass()
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ cls.client = cls.images_client
+ cls.servers_client = cls.servers_client
+
+ if compute.MULTI_USER:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.alt_manager = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ else:
+ # Use the alt_XXX credentials in the config file
+ cls.alt_manager = clients.AltManager()
+ cls.alt_client = cls.alt_manager.images_client
+
+ def __create_image__(self, server_id, name, meta=None):
+ resp, body = self.servers_client.create_image(server_id, name, meta)
+ image_id = parse_image_id(resp['location'])
+ self.addCleanup(self.client.delete_image, image_id)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+ return resp, body
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_from_deleted_server(self):
+ # An image should not be created if the server instance is removed
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Delete server before trying to create server
+ self.servers_client.delete_server(server['id'])
+ self.servers_client.wait_for_server_termination(server['id'])
+ # Create a new image after server is deleted
+ name = rand_name('image')
+ meta = {'image_type': 'test'}
+ self.assertRaises(exceptions.NotFound,
+ self.__create_image__,
+ server['id'], name, meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_from_invalid_server(self):
+ # An image should not be created with invalid server id
+ # Create a new image with invalid server id
+ name = rand_name('image')
+ meta = {'image_type': 'test'}
+ resp = {}
+ resp['status'] = None
+ self.assertRaises(exceptions.NotFound, self.__create_image__,
+ '!@#$%^&*()', name, meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_from_stopped_server(self):
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.servers_client.stop(server['id'])
+ self.servers_client.wait_for_server_status(server['id'],
+ 'SHUTOFF')
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ snapshot_name = rand_name('test-snap-')
+ resp, image = self.create_image_from_server(server['id'],
+ name=snapshot_name,
+ wait_until='active')
+ self.addCleanup(self.client.delete_image, image['id'])
+ self.assertEqual(snapshot_name, image['name'])
+
+ @attr(type='gate')
+ def test_delete_queued_image(self):
+ snapshot_name = rand_name('test-snap-')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ resp, image = self.create_image_from_server(server['id'],
+ name=snapshot_name,
+ wait_until='queued')
+ resp, body = self.client.delete_image(image['id'])
+ self.assertEqual('200', resp['status'])
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_uuid_35_characters_or_less(self):
+ # Return an error if Image ID passed is 35 characters or less
+ snapshot_name = rand_name('test-snap-')
+ test_uuid = ('a' * 35)
+ self.assertRaises(exceptions.NotFound,
+ self.servers_client.create_image,
+ test_uuid, snapshot_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_uuid_37_characters_or_more(self):
+ # Return an error if Image ID passed is 37 characters or more
+ snapshot_name = rand_name('test-snap-')
+ test_uuid = ('a' * 37)
+ self.assertRaises(exceptions.NotFound,
+ self.servers_client.create_image,
+ test_uuid, snapshot_name)
+
+
+class ImagesV3TestXML(ImagesV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/__init__.py b/tempest/api/compute/v3/servers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/servers/__init__.py
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
new file mode 100644
index 0000000..fb4214a
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -0,0 +1,278 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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 base64
+import time
+
+import testtools
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.linux.remote_client import RemoteClient
+import tempest.config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class ServerActionsV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+ resize_available = tempest.config.TempestConfig().\
+ compute_feature_enabled.resize
+ run_ssh = tempest.config.TempestConfig().compute.run_ssh
+
+ def setUp(self):
+ # NOTE(afazekas): Normally we use the same server with all test cases,
+ # but if it has an issue, we build a new one
+ super(ServerActionsV3TestJSON, self).setUp()
+ # Check if the server is in a clean state after test
+ try:
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ except Exception:
+ # Rebuild server if something happened to it during a test
+ self.rebuild_server()
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerActionsV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ cls.rebuild_server()
+
+ @testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
+ 'Change password not available.')
+ @attr(type='gate')
+ def test_change_server_password(self):
+ # The server's password should be set to the provided password
+ new_password = 'Newpass1234'
+ resp, body = self.client.change_password(self.server_id, new_password)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ if self.run_ssh:
+ # Verify that the user can authenticate with the new password
+ resp, server = self.client.get_server(self.server_id)
+ linux_client = RemoteClient(server, self.ssh_user, new_password)
+ self.assertTrue(linux_client.can_authenticate())
+
+ @attr(type='smoke')
+ def test_reboot_server_hard(self):
+ # The server should be power cycled
+ if self.run_ssh:
+ # Get the time the server was last rebooted,
+ resp, server = self.client.get_server(self.server_id)
+ linux_client = RemoteClient(server, self.ssh_user, self.password)
+ boot_time = linux_client.get_boot_time()
+
+ resp, body = self.client.reboot(self.server_id, 'HARD')
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ if self.run_ssh:
+ # Log in and verify the boot time has changed
+ linux_client = RemoteClient(server, self.ssh_user, self.password)
+ new_boot_time = linux_client.get_boot_time()
+ self.assertGreater(new_boot_time, boot_time)
+
+ @skip_because(bug="1014647")
+ @attr(type='smoke')
+ def test_reboot_server_soft(self):
+ # The server should be signaled to reboot gracefully
+ if self.run_ssh:
+ # Get the time the server was last rebooted,
+ resp, server = self.client.get_server(self.server_id)
+ linux_client = RemoteClient(server, self.ssh_user, self.password)
+ boot_time = linux_client.get_boot_time()
+
+ resp, body = self.client.reboot(self.server_id, 'SOFT')
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ if self.run_ssh:
+ # Log in and verify the boot time has changed
+ linux_client = RemoteClient(server, self.ssh_user, self.password)
+ new_boot_time = linux_client.get_boot_time()
+ self.assertGreater(new_boot_time, boot_time)
+
+ @attr(type='smoke')
+ def test_rebuild_server(self):
+ # The server should be rebuilt using the provided image and data
+ meta = {'rebuild': 'server'}
+ new_name = rand_name('server')
+ file_contents = 'Test server rebuild.'
+ personality = [{'path': 'rebuild.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ password = 'rebuildPassw0rd'
+ resp, rebuilt_server = self.client.rebuild(self.server_id,
+ self.image_ref_alt,
+ name=new_name,
+ metadata=meta,
+ personality=personality,
+ admin_pass=password)
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(self.server_id, rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+ self.assertEqual(self.flavor_ref, int(rebuilt_server['flavor']['id']))
+
+ # Verify the server properties after the rebuild completes
+ self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+ self.assertEqual(new_name, rebuilt_server['name'])
+
+ if self.run_ssh:
+ # Verify that the user can authenticate with the provided password
+ linux_client = RemoteClient(server, self.ssh_user, password)
+ self.assertTrue(linux_client.can_authenticate())
+
+ def _detect_server_image_flavor(self, server_id):
+ # Detects the current server image flavor ref.
+ resp, server = self.client.get_server(self.server_id)
+ current_flavor = server['flavor']['id']
+ new_flavor_ref = self.flavor_ref_alt \
+ if int(current_flavor) == self.flavor_ref else self.flavor_ref
+ return int(current_flavor), int(new_flavor_ref)
+
+ @testtools.skipIf(not resize_available, 'Resize not available.')
+ @attr(type='smoke')
+ def test_resize_server_confirm(self):
+ # The server's RAM and disk space should be modified to that of
+ # the provided flavor
+
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+ self.client.confirm_resize(self.server_id)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ resp, server = self.client.get_server(self.server_id)
+ self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
+
+ @testtools.skipIf(not resize_available, 'Resize not available.')
+ @attr(type='gate')
+ def test_resize_server_revert(self):
+ # The server's RAM and disk space should return to its original
+ # values after a resize is reverted
+
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+ self.client.revert_resize(self.server_id)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ # Need to poll for the id change until lp#924371 is fixed
+ resp, server = self.client.get_server(self.server_id)
+ start = int(time.time())
+
+ while int(server['flavor']['id']) != previous_flavor_ref:
+ time.sleep(self.build_interval)
+ resp, server = self.client.get_server(self.server_id)
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = 'Server %s failed to revert resize within the \
+ required time (%s s).' % (self.server_id, self.build_timeout)
+ raise exceptions.TimeoutException(message)
+
+ @attr(type='gate')
+ def test_get_console_output(self):
+ # Positive test:Should be able to GET the console output
+ # for a given server_id and number of lines
+ def get_output():
+ resp, output = self.servers_client.get_console_output(
+ self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+ self.wait_for(get_output)
+
+ @skip_because(bug="1014683")
+ @attr(type='gate')
+ def test_get_console_output_server_id_in_reboot_status(self):
+ # Positive test:Should be able to GET the console output
+ # for a given server_id in reboot status
+ resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'REBOOT')
+ resp, output = self.servers_client.get_console_output(self.server_id,
+ 10)
+ self.assertEqual(200, resp.status)
+ self.assertIsNotNone(output)
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
+ @attr(type='gate')
+ def test_pause_unpause_server(self):
+ resp, server = self.client.pause_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'PAUSED')
+ resp, server = self.client.unpause_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
+ def test_suspend_resume_server(self):
+ resp, server = self.client.suspend_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
+ resp, server = self.client.resume_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
+ def test_stop_start_server(self):
+ resp, server = self.servers_client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ resp, server = self.servers_client.start(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
+ def test_lock_unlock_server(self):
+ # Lock the server,try server stop(exceptions throw),unlock it and retry
+ resp, server = self.servers_client.lock_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ resp, server = self.servers_client.get_server(self.server_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(server['status'], 'ACTIVE')
+ # Locked server is not allowed to be stopped by non-admin user
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.stop, self.server_id)
+ resp, server = self.servers_client.unlock_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ resp, server = self.servers_client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ resp, server = self.servers_client.start(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+
+class ServerActionsV3TestXML(ServerActionsV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/test_extensions.py b/tempest/api/compute/v3/test_extensions.py
new file mode 100644
index 0000000..8f1e446
--- /dev/null
+++ b/tempest/api/compute/v3/test_extensions.py
@@ -0,0 +1,35 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.compute import base
+from tempest.test import attr
+
+
+class ExtensionsTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @attr(type='gate')
+ def test_list_extensions(self):
+ # List of all extensions
+ resp, extensions = self.extensions_client.list_extensions()
+ self.assertIn("extensions", extensions)
+ self.assertEqual(200, resp.status)
+
+
+class ExtensionsTestXML(ExtensionsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index a993077..660de95 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -53,8 +53,9 @@
def _create_and_attach(self):
# Start a server and wait for it to become ready
- resp, server = self.create_server(wait_until='ACTIVE',
- adminPass=self.image_ssh_password)
+ admin_pass = self.image_ssh_password
+ resp, server = self.create_test_server(wait_until='ACTIVE',
+ adminPass=admin_pass)
self.server = server
# Record addresses so that we can ssh later
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index ab0cb00..fcd895e 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import cStringIO as StringIO
+
from tempest import clients
from tempest.common import isolated_creds
from tempest.common.utils.data_utils import rand_name
@@ -86,6 +88,36 @@
raise cls.skipException(msg)
+class BaseV1ImageMembersTest(BaseV1ImageTest):
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV1ImageMembersTest, cls).setUpClass()
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.os_alt = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id']
+ else:
+ cls.os_alt = clients.AltManager()
+ identity_client = cls._get_identity_admin_client()
+ cls.alt_tenant_id = identity_client.get_tenant_by_name(
+ cls.os_alt.tenant_name)['id']
+
+ cls.alt_img_cli = cls.os_alt.image_client
+
+ def _create_image(self):
+ image_file = StringIO.StringIO('*' * 1024)
+ resp, image = self.create_image(container_format='bare',
+ disk_format='raw',
+ is_public=False,
+ data=image_file)
+ self.assertEqual(201, resp.status)
+ image_id = image['id']
+ return image_id
+
+
class BaseV2ImageTest(BaseImageTest):
@classmethod
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index 9ea9a3d..437527d 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -14,44 +14,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import cStringIO as StringIO
from tempest.api.image import base
-from tempest import clients
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
-class ImageMembersTests(base.BaseV1ImageTest):
-
- @classmethod
- def setUpClass(cls):
- super(ImageMembersTests, cls).setUpClass()
- if cls.config.compute.allow_tenant_isolation:
- creds = cls.isolated_creds.get_alt_creds()
- username, tenant_name, password = creds
- cls.os_alt = clients.Manager(username=username,
- password=password,
- tenant_name=tenant_name)
- else:
- cls.os_alt = clients.AltManager()
-
- alt_tenant_name = cls.os_alt.tenant_name
- identity_client = cls._get_identity_admin_client()
- _, tenants = identity_client.list_tenants()
- cls.alt_tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
- alt_tenant_name][0]
-
- def _create_image(self):
- image_file = StringIO.StringIO('*' * 1024)
- resp, image = self.create_image(container_format='bare',
- disk_format='raw',
- is_public=False,
- data=image_file)
- self.assertEqual(201, resp.status)
- image_id = image['id']
- return image_id
+class ImageMembersTest(base.BaseV1ImageMembersTest):
@attr(type='gate')
def test_add_image_member(self):
@@ -63,6 +31,9 @@
members = body['members']
members = map(lambda x: x['member_id'], members)
self.assertIn(self.alt_tenant_id, members)
+ # get image as alt user
+ resp, body = self.alt_img_cli.get_image(image)
+ self.assertEqual(200, resp.status)
@attr(type='gate')
def test_get_shared_images(self):
@@ -90,25 +61,3 @@
self.assertEqual(200, resp.status)
members = body['members']
self.assertEqual(0, len(members), str(members))
-
- @attr(type=['negative', 'gate'])
- def test_add_member_with_non_existing_image(self):
- # Add member with non existing image.
- non_exist_image = rand_name('image_')
- self.assertRaises(exceptions.NotFound, self.client.add_member,
- self.alt_tenant_id, non_exist_image)
-
- @attr(type=['negative', 'gate'])
- def test_delete_member_with_non_existing_image(self):
- # Delete member with non existing image.
- non_exist_image = rand_name('image_')
- self.assertRaises(exceptions.NotFound, self.client.delete_member,
- self.alt_tenant_id, non_exist_image)
-
- @attr(type=['negative', 'gate'])
- def test_delete_member_with_non_existing_tenant(self):
- # Delete member with non existing tenant.
- image_id = self._create_image()
- non_exist_tenant = rand_name('tenant_')
- self.assertRaises(exceptions.NotFound, self.client.delete_member,
- non_exist_tenant, image_id)
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
new file mode 100644
index 0000000..83f7a0f
--- /dev/null
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -0,0 +1,54 @@
+# 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.
+
+
+from tempest.api.image import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImageMembersNegativeTest(base.BaseV1ImageMembersTest):
+
+ @attr(type=['negative', 'gate'])
+ def test_add_member_with_non_existing_image(self):
+ # Add member with non existing image.
+ non_exist_image = data_utils.rand_uuid()
+ self.assertRaises(exceptions.NotFound, self.client.add_member,
+ self.alt_tenant_id, non_exist_image)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_member_with_non_existing_image(self):
+ # Delete member with non existing image.
+ non_exist_image = data_utils.rand_uuid()
+ self.assertRaises(exceptions.NotFound, self.client.delete_member,
+ self.alt_tenant_id, non_exist_image)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_member_with_non_existing_tenant(self):
+ # Delete member with non existing tenant.
+ image_id = self._create_image()
+ non_exist_tenant = data_utils.rand_uuid_hex()
+ self.assertRaises(exceptions.NotFound, self.client.delete_member,
+ non_exist_tenant, image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_image_without_membership(self):
+ # Image is hidden from another tenants.
+ image_id = self._create_image()
+ self.assertRaises(exceptions.NotFound,
+ self.alt_img_cli.get_image,
+ image_id)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 90ffeae..558e2ec 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -18,7 +18,6 @@
import cStringIO as StringIO
from tempest.api.image import base
-from tempest import exceptions
from tempest.test import attr
@@ -26,17 +25,6 @@
"""Here we test the registration and creation of images."""
@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='gate')
- def test_register_with_invalid_disk_format(self):
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- 'test', 'bare', 'wrong')
-
- @attr(type='gate')
def test_register_then_upload(self):
# Register, then upload an image
properties = {'prop1': 'val1'}
@@ -108,6 +96,8 @@
self.assertEqual(40, body.get('min_ram'))
for key, val in properties.items():
self.assertEqual(val, body.get('properties')[key])
+ resp, body = self.client.delete_image(body['id'])
+ self.assertEqual('200', resp['status'])
class ListImagesTest(base.BaseV1ImageTest):
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
new file mode 100644
index 0000000..1bcf120
--- /dev/null
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -0,0 +1,72 @@
+# 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.
+
+from tempest.api.image import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest):
+ """Here are negative tests for the deletion and creation of images."""
+
+ @attr(type=['negative', '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', 'gate'])
+ def test_register_with_invalid_disk_format(self):
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ 'test', 'bare', 'wrong')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_with_invalid_image_id(self):
+ # An image should not be deleted with invalid image id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ '!@$%^&*()')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existent_image(self):
+ # Return an error while trying to delete a non-existent image
+
+ non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ non_existent_image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_blank_id(self):
+ # Return an error while trying to delete an image with blank Id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_non_hex_string_id(self):
+ # Return an error while trying to delete an image with non hex id
+ image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_negative_image_id(self):
+ # Return an error while trying to delete an image with negative id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_id_is_over_35_character_limit(self):
+ # Return an error while trying to delete image with id over limit
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 512d065..2f20cc9 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -106,8 +106,8 @@
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_subnet_id,
router['id'], subnet['id'])
- self.assertTrue('subnet_id' in interface.keys())
- self.assertTrue('port_id' in interface.keys())
+ self.assertIn('subnet_id', interface.keys())
+ self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
@@ -126,8 +126,8 @@
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_port_id,
router['id'], port_body['port']['id'])
- self.assertTrue('subnet_id' in interface.keys())
- self.assertTrue('port_id' in interface.keys())
+ self.assertIn('subnet_id', interface.keys())
+ self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 407c3ec..3389235 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -96,7 +96,7 @@
self.container_name, object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
actual_meta_key = 'x-object-meta-' + meta_key
- self.assertTrue(actual_meta_key in resp)
+ self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='smoke')
@@ -220,7 +220,7 @@
object_name)
self.assertEqual(body, data)
actual_meta_key = 'x-object-meta-' + meta_key
- self.assertTrue(actual_meta_key in resp)
+ self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index c455566..7e9d242 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -99,31 +99,14 @@
'from the created Volume')
@attr(type='smoke')
- def test_volume_type_create_delete(self):
- # Create/Delete volume type.
- name = rand_name("volume-type-")
- extra_specs = {"storage_protocol": "iSCSI",
- "vendor_name": "Open Source"}
- resp, body = self.client.create_volume_type(
- name,
- extra_specs=extra_specs)
- self.assertEqual(200, resp.status)
- self.assertIn('id', body)
- self.addCleanup(self._delete_volume_type, body['id'])
- self.assertIn('name', body)
- self.assertEqual(body['name'], name,
- "The created volume_type name is not equal "
- "to the requested name")
- self.assertTrue(body['id'] is not None,
- "Field volume_type id is empty or not found.")
-
- @attr(type='smoke')
- def test_volume_type_create_get(self):
+ def test_volume_type_create_get_delete(self):
# Create/get volume type.
body = {}
name = rand_name("volume-type-")
- extra_specs = {"storage_protocol": "iSCSI",
- "vendor_name": "Open Source"}
+ proto = self.config.volume.storage_protocol
+ vendor = self.config.volume.vendor_name
+ extra_specs = {"storage_protocol": proto,
+ "vendor_name": vendor}
resp, body = self.client.create_volume_type(
name,
extra_specs=extra_specs)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index d6dd7db..0892eae 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -47,8 +47,7 @@
self.volume_type['id'])
self.assertEqual(200, resp.status)
self.assertIsInstance(body, dict)
- self.assertTrue('spec1' in body, "Incorrect volume type extra"
- " spec returned")
+ self.assertIn('spec1', body)
@attr(type='gate')
def test_volume_type_extra_specs_update(self):
@@ -66,8 +65,7 @@
extra_spec.keys()[0],
extra_spec)
self.assertEqual(200, resp.status)
- self.assertTrue('spec2' in body,
- "Volume type extra spec incorrectly updated")
+ self.assertIn('spec2', body)
self.assertEqual(extra_spec['spec2'], body['spec2'],
"Volume type extra spec incorrectly updated")
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index f12d4bb..30c2c74 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -123,6 +123,23 @@
self.assertEqual(200, resp.status)
self.assertEqual(int(volume['size']), extend_size)
+ @attr(type='gate')
+ def test_reserve_unreserve_volume(self):
+ # Mark volume as reserved.
+ resp, body = self.client.reserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # To get the volume info
+ resp, body = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertIn('attaching', body['status'])
+ # Unmark volume as reserved.
+ resp, body = self.client.unreserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # To get the volume info
+ resp, body = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertIn('available', body['status'])
+
class VolumesActionsTestXML(VolumesActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 9bab9a0..538d5be 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -212,6 +212,31 @@
self.assertRaises(exceptions.NotFound, self.client.extend_volume,
None, extend_size)
+ @attr(type=['negative', 'gate'])
+ def test_reserve_volume_with_nonexistent_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.reserve_volume,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_unreserve_volume_with_nonexistent_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.unreserve_volume,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_reserve_volume_with_negative_volume_status(self):
+ # Mark volume as reserved.
+ resp, body = self.client.reserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # Mark volume which is marked as reserved before
+ self.assertRaises(exceptions.BadRequest,
+ self.client.reserve_volume,
+ self.volume['id'])
+ # Unmark volume as reserved.
+ resp, body = self.client.unreserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+
class VolumesNegativeTestXML(VolumesNegativeTest):
_interface = 'xml'
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index bd1b44f..ec8b3a1 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -128,7 +128,8 @@
if not fail_ok and proc.returncode != 0:
raise CommandFailed(proc.returncode,
cmd,
- result)
+ result,
+ stderr=result_err)
finally:
LOG.debug('output of %s:\n%s' % (cmd_str, result))
if not merge_stderr and result_err:
@@ -149,6 +150,7 @@
class CommandFailed(subprocess.CalledProcessError):
# adds output attribute for python2.6
- def __init__(self, returncode, cmd, output):
+ def __init__(self, returncode, cmd, output, stderr=""):
super(CommandFailed, self).__init__(returncode, cmd)
self.output = output
+ self.stderr = stderr
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index 9bd07d0..047b17d 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -76,6 +76,25 @@
def test_neutron_meter_label_rule_list(self):
self.neutron('meter-label-rule-list')
+ def _test_neutron_lbaas_command(self, command):
+ try:
+ self.neutron(command)
+ except tempest.cli.CommandFailed as e:
+ if '404 Not Found' not in e.stderr:
+ self.fail('%s: Unexpected failure.' % command)
+
+ def test_neutron_lb_healthmonitor_list(self):
+ self._test_neutron_lbaas_command('lb-healthmonitor-list')
+
+ def test_neutron_lb_member_list(self):
+ self._test_neutron_lbaas_command('lb-member-list')
+
+ def test_neutron_lb_pool_list(self):
+ self._test_neutron_lbaas_command('lb-pool-list')
+
+ def test_neutron_lb_vip_list(self):
+ self._test_neutron_lbaas_command('lb-vip-list')
+
def test_neutron_net_external_list(self):
self.neutron('net-external-list')
diff --git a/tempest/clients.py b/tempest/clients.py
index 97afd9a..4c39bb1 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -48,6 +48,13 @@
TenantUsagesClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.servers_client import \
+ ServersV3ClientJSON
+from tempest.services.compute.v3.json.services_client import \
+ ServicesV3ClientJSON
+from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
+from tempest.services.compute.v3.xml.services_client import \
+ ServicesV3ClientXML
from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
from tempest.services.compute.xml.availability_zone_client import \
AvailabilityZoneClientXML
@@ -172,6 +179,7 @@
if interface == 'xml':
self.certificates_client = CertificatesClientXML(*client_args)
self.servers_client = ServersClientXML(*client_args)
+ self.servers_v3_client = ServersV3ClientXML(*client_args)
self.limits_client = LimitsClientXML(*client_args)
self.images_client = ImagesClientXML(*client_args)
self.keypairs_client = KeyPairsClientXML(*client_args)
@@ -194,6 +202,7 @@
self.fixed_ips_client = FixedIPsClientXML(*client_args)
self.availability_zone_client = AvailabilityZoneClientXML(
*client_args)
+ self.services_v3_client = ServicesV3ClientXML(*client_args)
self.service_client = ServiceClientXML(*client_args)
self.aggregates_client = AggregatesClientXML(*client_args)
self.services_client = ServicesClientXML(*client_args)
@@ -211,6 +220,7 @@
elif interface == 'json':
self.certificates_client = CertificatesClientJSON(*client_args)
self.servers_client = ServersClientJSON(*client_args)
+ self.servers_v3_client = ServersV3ClientJSON(*client_args)
self.limits_client = LimitsClientJSON(*client_args)
self.images_client = ImagesClientJSON(*client_args)
self.keypairs_client = KeyPairsClientJSON(*client_args)
@@ -233,6 +243,7 @@
self.fixed_ips_client = FixedIPsClientJSON(*client_args)
self.availability_zone_client = AvailabilityZoneClientJSON(
*client_args)
+ self.services_v3_client = ServicesV3ClientJSON(*client_args)
self.service_client = ServiceClientJSON(*client_args)
self.aggregates_client = AggregatesClientJSON(*client_args)
self.services_client = ServicesClientJSON(*client_args)
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index 3ab8fe0..4f93e1c 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -19,10 +19,19 @@
import random
import re
import urllib
+import uuid
from tempest import exceptions
+def rand_uuid():
+ return str(uuid.uuid4())
+
+
+def rand_uuid_hex():
+ return uuid.uuid4().hex
+
+
def rand_name(name='test'):
return name + "-tempest-" + str(random.randint(1, 0x7fffffff))
diff --git a/tempest/config.py b/tempest/config.py
index 925c4bd..76461fb 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -186,6 +186,9 @@
"of identity.region is used instead. If no such region "
"is found in the service catalog, the first found one is "
"used."),
+ cfg.StrOpt('catalog_v3_type',
+ default='computev3',
+ help="Catalog type of the Compute v3 service."),
cfg.StrOpt('path_to_private_key',
default=None,
help="Path to a private key file for SSH access to remote "
@@ -200,6 +203,9 @@
title="Enabled Compute Service Features")
ComputeFeaturesGroup = [
+ cfg.BoolOpt('api_v3',
+ default=False,
+ help="If false, skip all nova v3 tests."),
cfg.BoolOpt('disk_config',
default=True,
help="If false, skip disk config tests"),
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 67406b0..02fc231 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -82,7 +82,7 @@
class ImageKilledException(TempestException):
- message = "Image %(image_id)s 'killed' while waiting for %(status)s"
+ message = "Image %(image_id)s 'killed' while waiting for '%(status)s'"
class AddImageException(TempestException):
diff --git a/tempest/openstack/common/config/__init__.py b/tempest/openstack/common/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/common/config/__init__.py
diff --git a/tempest/openstack/common/config/generator.py b/tempest/openstack/common/config/generator.py
new file mode 100644
index 0000000..373f9a6
--- /dev/null
+++ b/tempest/openstack/common/config/generator.py
@@ -0,0 +1,268 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 SINA 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.
+#
+
+"""Extracts OpenStack config option info from module(s)."""
+
+from __future__ import print_function
+
+import imp
+import os
+import re
+import socket
+import sys
+import textwrap
+
+from oslo.config import cfg
+
+from tempest.openstack.common import gettextutils
+from tempest.openstack.common import importutils
+
+gettextutils.install('tempest')
+
+STROPT = "StrOpt"
+BOOLOPT = "BoolOpt"
+INTOPT = "IntOpt"
+FLOATOPT = "FloatOpt"
+LISTOPT = "ListOpt"
+MULTISTROPT = "MultiStrOpt"
+
+OPT_TYPES = {
+ STROPT: 'string value',
+ BOOLOPT: 'boolean value',
+ INTOPT: 'integer value',
+ FLOATOPT: 'floating point value',
+ LISTOPT: 'list value',
+ MULTISTROPT: 'multi valued',
+}
+
+OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
+ FLOATOPT, LISTOPT,
+ MULTISTROPT]))
+
+PY_EXT = ".py"
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ "../../../../"))
+WORDWRAP_WIDTH = 60
+
+
+def generate(srcfiles):
+ mods_by_pkg = dict()
+ for filepath in srcfiles:
+ pkg_name = filepath.split(os.sep)[1]
+ mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
+ os.path.basename(filepath).split('.')[0]])
+ mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
+ # NOTE(lzyeval): place top level modules before packages
+ pkg_names = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys())
+ pkg_names.sort()
+ ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys())
+ ext_names.sort()
+ pkg_names.extend(ext_names)
+
+ # opts_by_group is a mapping of group name to an options list
+ # The options list is a list of (module, options) tuples
+ opts_by_group = {'DEFAULT': []}
+
+ for module_name in os.getenv(
+ "OSLO_CONFIG_GENERATOR_EXTRA_MODULES", "").split(','):
+ module = _import_module(module_name)
+ if module:
+ for group, opts in _list_opts(module):
+ opts_by_group.setdefault(group, []).append((module_name, opts))
+
+ for pkg_name in pkg_names:
+ mods = mods_by_pkg.get(pkg_name)
+ mods.sort()
+ for mod_str in mods:
+ if mod_str.endswith('.__init__'):
+ mod_str = mod_str[:mod_str.rfind(".")]
+
+ mod_obj = _import_module(mod_str)
+ if not mod_obj:
+ raise RuntimeError("Unable to import module %s" % mod_str)
+
+ for group, opts in _list_opts(mod_obj):
+ opts_by_group.setdefault(group, []).append((mod_str, opts))
+
+ print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
+ for group, opts in opts_by_group.items():
+ print_group_opts(group, opts)
+
+
+def _import_module(mod_str):
+ try:
+ if mod_str.startswith('bin.'):
+ imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
+ return sys.modules[mod_str[4:]]
+ else:
+ return importutils.import_module(mod_str)
+ except ImportError as ie:
+ sys.stderr.write("%s\n" % str(ie))
+ return None
+ except Exception:
+ return None
+
+
+def _is_in_group(opt, group):
+ "Check if opt is in group."
+ for key, value in group._opts.items():
+ if value['opt'] == opt:
+ return True
+ return False
+
+
+def _guess_groups(opt, mod_obj):
+ # is it in the DEFAULT group?
+ if _is_in_group(opt, cfg.CONF):
+ return 'DEFAULT'
+
+ # what other groups is it in?
+ for key, value in cfg.CONF.items():
+ if isinstance(value, cfg.CONF.GroupAttr):
+ if _is_in_group(opt, value._group):
+ return value._group.name
+
+ raise RuntimeError(
+ "Unable to find group for option %s, "
+ "maybe it's defined twice in the same group?"
+ % opt.name
+ )
+
+
+def _list_opts(obj):
+ def is_opt(o):
+ return (isinstance(o, cfg.Opt) and
+ not isinstance(o, cfg.SubCommandOpt))
+
+ opts = list()
+ for attr_str in dir(obj):
+ attr_obj = getattr(obj, attr_str)
+ if is_opt(attr_obj):
+ opts.append(attr_obj)
+ elif (isinstance(attr_obj, list) and
+ all(map(lambda x: is_opt(x), attr_obj))):
+ opts.extend(attr_obj)
+
+ ret = {}
+ for opt in opts:
+ ret.setdefault(_guess_groups(opt, obj), []).append(opt)
+ return ret.items()
+
+
+def print_group_opts(group, opts_by_module):
+ print("[%s]" % group)
+ print('')
+ for mod, opts in opts_by_module:
+ print('#')
+ print('# Options defined in %s' % mod)
+ print('#')
+ print('')
+ for opt in opts:
+ _print_opt(opt)
+ print('')
+
+
+def _get_my_ip():
+ try:
+ csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ csock.connect(('8.8.8.8', 80))
+ (addr, port) = csock.getsockname()
+ csock.close()
+ return addr
+ except socket.error:
+ return None
+
+
+def _sanitize_default(name, value):
+ """Set up a reasonably sensible default for pybasedir, my_ip and host."""
+ if value.startswith(sys.prefix):
+ # NOTE(jd) Don't use os.path.join, because it is likely to think the
+ # second part is an absolute pathname and therefore drop the first
+ # part.
+ value = os.path.normpath("/usr/" + value[len(sys.prefix):])
+ elif value.startswith(BASEDIR):
+ return value.replace(BASEDIR, '/usr/lib/python/site-packages')
+ elif BASEDIR in value:
+ return value.replace(BASEDIR, '')
+ elif value == _get_my_ip():
+ return '10.0.0.1'
+ elif value == socket.gethostname() and 'host' in name:
+ return 'tempest'
+ elif value.strip() != value:
+ return '"%s"' % value
+ return value
+
+
+def _print_opt(opt):
+ opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
+ if not opt_help:
+ sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
+ opt_help = ""
+ opt_type = None
+ try:
+ opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
+ except (ValueError, AttributeError) as err:
+ sys.stderr.write("%s\n" % str(err))
+ sys.exit(1)
+ opt_help += ' (' + OPT_TYPES[opt_type] + ')'
+ print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)))
+ if opt.deprecated_opts:
+ for deprecated_opt in opt.deprecated_opts:
+ if deprecated_opt.name:
+ deprecated_group = (deprecated_opt.group if
+ deprecated_opt.group else "DEFAULT")
+ print('# Deprecated group/name - [%s]/%s' %
+ (deprecated_group,
+ deprecated_opt.name))
+ try:
+ if opt_default is None:
+ print('#%s=<None>' % opt_name)
+ elif opt_type == STROPT:
+ assert(isinstance(opt_default, basestring))
+ print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
+ opt_default)))
+ elif opt_type == BOOLOPT:
+ assert(isinstance(opt_default, bool))
+ print('#%s=%s' % (opt_name, str(opt_default).lower()))
+ elif opt_type == INTOPT:
+ assert(isinstance(opt_default, int) and
+ not isinstance(opt_default, bool))
+ print('#%s=%s' % (opt_name, opt_default))
+ elif opt_type == FLOATOPT:
+ assert(isinstance(opt_default, float))
+ print('#%s=%s' % (opt_name, opt_default))
+ elif opt_type == LISTOPT:
+ assert(isinstance(opt_default, list))
+ print('#%s=%s' % (opt_name, ','.join(opt_default)))
+ elif opt_type == MULTISTROPT:
+ assert(isinstance(opt_default, list))
+ if not opt_default:
+ opt_default = ['']
+ for default in opt_default:
+ print('#%s=%s' % (opt_name, default))
+ print('')
+ except Exception:
+ sys.stderr.write('Error in option "%s"\n' % opt_name)
+ sys.exit(1)
+
+
+def main():
+ generate(sys.argv[1:])
+
+if __name__ == '__main__':
+ main()
diff --git a/tempest/openstack/common/excutils.py b/tempest/openstack/common/excutils.py
index db37660..c7bce72 100644
--- a/tempest/openstack/common/excutils.py
+++ b/tempest/openstack/common/excutils.py
@@ -79,7 +79,7 @@
try:
return infunc(*args, **kwargs)
except Exception as exc:
- this_exc_message = unicode(exc)
+ this_exc_message = six.u(str(exc))
if this_exc_message == last_exc_message:
exc_count += 1
else:
diff --git a/tempest/openstack/common/fileutils.py b/tempest/openstack/common/fileutils.py
index 6cf68ba..15530af 100644
--- a/tempest/openstack/common/fileutils.py
+++ b/tempest/openstack/common/fileutils.py
@@ -19,6 +19,7 @@
import contextlib
import errno
import os
+import tempfile
from tempest.openstack.common import excutils
from tempest.openstack.common.gettextutils import _ # noqa
@@ -109,3 +110,30 @@
state at all (for unit tests)
"""
return file(*args, **kwargs)
+
+
+def write_to_tempfile(content, path=None, suffix='', prefix='tmp'):
+ """Create temporary file or use existing file.
+
+ This util is needed for creating temporary file with
+ specified content, suffix and prefix. If path is not None,
+ it will be used for writing content. If the path doesn't
+ exist it'll be created.
+
+ :param content: content for temporary file.
+ :param path: same as parameter 'dir' for mkstemp
+ :param suffix: same as parameter 'suffix' for mkstemp
+ :param prefix: same as parameter 'prefix' for mkstemp
+
+ For example: it can be used in database tests for creating
+ configuration files.
+ """
+ if path:
+ ensure_tree(path)
+
+ (fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix)
+ try:
+ os.write(fd, content)
+ finally:
+ os.close(fd)
+ return path
diff --git a/tempest/openstack/common/gettextutils.py b/tempest/openstack/common/gettextutils.py
index cbf570a..2939ed9 100644
--- a/tempest/openstack/common/gettextutils.py
+++ b/tempest/openstack/common/gettextutils.py
@@ -60,6 +60,8 @@
if USE_LAZY:
return Message(msg, 'tempest')
else:
+ if six.PY3:
+ return _t.gettext(msg)
return _t.ugettext(msg)
@@ -105,13 +107,17 @@
"""
return Message(msg, domain)
- import __builtin__
- __builtin__.__dict__['_'] = _lazy_gettext
+ from six import moves
+ moves.builtins.__dict__['_'] = _lazy_gettext
else:
localedir = '%s_LOCALEDIR' % domain.upper()
- gettext.install(domain,
- localedir=os.environ.get(localedir),
- unicode=True)
+ if six.PY3:
+ gettext.install(domain,
+ localedir=os.environ.get(localedir))
+ else:
+ gettext.install(domain,
+ localedir=os.environ.get(localedir),
+ unicode=True)
class Message(_userString.UserString, object):
@@ -121,8 +127,8 @@
self._msg = msg
self._left_extra_msg = ''
self._right_extra_msg = ''
+ self._locale = None
self.params = None
- self.locale = None
self.domain = domain
@property
@@ -142,8 +148,13 @@
localedir=localedir,
fallback=True)
+ if six.PY3:
+ ugettext = lang.gettext
+ else:
+ ugettext = lang.ugettext
+
full_msg = (self._left_extra_msg +
- lang.ugettext(self._msg) +
+ ugettext(self._msg) +
self._right_extra_msg)
if self.params is not None:
@@ -151,6 +162,33 @@
return six.text_type(full_msg)
+ @property
+ def locale(self):
+ return self._locale
+
+ @locale.setter
+ def locale(self, value):
+ self._locale = value
+ if not self.params:
+ return
+
+ # This Message object may have been constructed with one or more
+ # Message objects as substitution parameters, given as a single
+ # Message, or a tuple or Map containing some, so when setting the
+ # locale for this Message we need to set it for those Messages too.
+ if isinstance(self.params, Message):
+ self.params.locale = value
+ return
+ if isinstance(self.params, tuple):
+ for param in self.params:
+ if isinstance(param, Message):
+ param.locale = value
+ return
+ if isinstance(self.params, dict):
+ for param in self.params.values():
+ if isinstance(param, Message):
+ param.locale = value
+
def _save_dictionary_parameter(self, dict_param):
full_msg = self.data
# look for %(blah) fields in string;
@@ -169,7 +207,7 @@
params[key] = copy.deepcopy(dict_param[key])
except TypeError:
# cast uncopyable thing to unicode string
- params[key] = unicode(dict_param[key])
+ params[key] = six.text_type(dict_param[key])
return params
@@ -188,7 +226,7 @@
try:
self.params = copy.deepcopy(other)
except TypeError:
- self.params = unicode(other)
+ self.params = six.text_type(other)
return self
@@ -197,11 +235,13 @@
return self.data
def __str__(self):
+ if six.PY3:
+ return self.__unicode__()
return self.data.encode('utf-8')
def __getstate__(self):
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
- 'domain', 'params', 'locale']
+ 'domain', 'params', '_locale']
new_dict = self.__dict__.fromkeys(to_copy)
for attr in to_copy:
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
@@ -289,13 +329,21 @@
def get_localized_message(message, user_locale):
- """Gets a localized version of the given message in the given locale."""
+ """Gets a localized version of the given message in the given locale.
+
+ If the message is not a Message object the message is returned as-is.
+ If the locale is None the message is translated to the default locale.
+
+ :returns: the translated message in unicode, or the original message if
+ it could not be translated
+ """
+ translated = message
if isinstance(message, Message):
- if user_locale:
- message.locale = user_locale
- return unicode(message)
- else:
- return message
+ original_locale = message.locale
+ message.locale = user_locale
+ translated = six.text_type(message)
+ message.locale = original_locale
+ return translated
class LocaleHandler(logging.Handler):
diff --git a/tempest/openstack/common/jsonutils.py b/tempest/openstack/common/jsonutils.py
index c568a06..b589545 100644
--- a/tempest/openstack/common/jsonutils.py
+++ b/tempest/openstack/common/jsonutils.py
@@ -46,6 +46,7 @@
import six
+from tempest.openstack.common import gettextutils
from tempest.openstack.common import importutils
from tempest.openstack.common import timeutils
@@ -135,6 +136,8 @@
if convert_datetime and isinstance(value, datetime.datetime):
return timeutils.strtime(value)
+ elif isinstance(value, gettextutils.Message):
+ return value.data
elif hasattr(value, 'iteritems'):
return recursive(dict(value.iteritems()), level=level + 1)
elif hasattr(value, '__iter__'):
diff --git a/tempest/openstack/common/lockutils.py b/tempest/openstack/common/lockutils.py
index a55fd94..8ea8766 100644
--- a/tempest/openstack/common/lockutils.py
+++ b/tempest/openstack/common/lockutils.py
@@ -24,7 +24,6 @@
import time
import weakref
-import fixtures
from oslo.config import cfg
from tempest.openstack.common import fileutils
@@ -242,13 +241,14 @@
def wrap(f):
@functools.wraps(f)
def inner(*args, **kwargs):
- with lock(name, lock_file_prefix, external, lock_path):
- LOG.debug(_('Got semaphore / lock "%(function)s"'),
+ try:
+ with lock(name, lock_file_prefix, external, lock_path):
+ LOG.debug(_('Got semaphore / lock "%(function)s"'),
+ {'function': f.__name__})
+ return f(*args, **kwargs)
+ finally:
+ LOG.debug(_('Semaphore / lock released "%(function)s"'),
{'function': f.__name__})
- return f(*args, **kwargs)
-
- LOG.debug(_('Semaphore / lock released "%(function)s"'),
- {'function': f.__name__})
return inner
return wrap
@@ -276,36 +276,3 @@
"""
return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)
-
-
-class LockFixture(fixtures.Fixture):
- """External locking fixture.
-
- This fixture is basically an alternative to the synchronized decorator with
- the external flag so that tearDowns and addCleanups will be included in
- the lock context for locking between tests. The fixture is recommended to
- be the first line in a test method, like so::
-
- def test_method(self):
- self.useFixture(LockFixture)
- ...
-
- or the first line in setUp if all the test methods in the class are
- required to be serialized. Something like::
-
- class TestCase(testtools.testcase):
- def setUp(self):
- self.useFixture(LockFixture)
- super(TestCase, self).setUp()
- ...
-
- This is because addCleanups are put on a LIFO queue that gets run after the
- test method exits. (either by completing or raising an exception)
- """
- def __init__(self, name, lock_file_prefix=None):
- self.mgr = lock(name, lock_file_prefix, True)
-
- def setUp(self):
- super(LockFixture, self).setUp()
- self.addCleanup(self.mgr.__exit__, None, None, None)
- self.mgr.__enter__()
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 4133c30..5cf8ed6 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -39,6 +39,7 @@
import traceback
from oslo.config import cfg
+import six
from six import moves
from tempest.openstack.common.gettextutils import _ # noqa
@@ -131,7 +132,6 @@
'boto=WARN',
'suds=INFO',
'keystone=INFO',
- 'eventlet.wsgi.server=WARN'
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
@@ -207,6 +207,8 @@
binary = binary or _get_binary_name()
return '%s.log' % (os.path.join(logdir, binary),)
+ return None
+
class BaseLoggerAdapter(logging.LoggerAdapter):
@@ -249,6 +251,13 @@
self.warn(stdmsg, *args, **kwargs)
def process(self, msg, kwargs):
+ # NOTE(mrodden): catch any Message/other object and
+ # coerce to unicode before they can get
+ # to the python logging and possibly
+ # cause string encoding trouble
+ if not isinstance(msg, six.string_types):
+ msg = six.text_type(msg)
+
if 'extra' not in kwargs:
kwargs['extra'] = {}
extra = kwargs['extra']
@@ -260,14 +269,14 @@
extra.update(_dictify_context(context))
instance = kwargs.pop('instance', None)
+ instance_uuid = (extra.get('instance_uuid', None) or
+ kwargs.pop('instance_uuid', None))
instance_extra = ''
if instance:
instance_extra = CONF.instance_format % instance
- else:
- instance_uuid = kwargs.pop('instance_uuid', None)
- if instance_uuid:
- instance_extra = (CONF.instance_uuid_format
- % {'uuid': instance_uuid})
+ elif instance_uuid:
+ instance_extra = (CONF.instance_uuid_format
+ % {'uuid': instance_uuid})
extra.update({'instance': instance_extra})
extra.update({"project": self.project})
diff --git a/tempest/openstack/common/timeutils.py b/tempest/openstack/common/timeutils.py
index 60f02bc..98d877d 100644
--- a/tempest/openstack/common/timeutils.py
+++ b/tempest/openstack/common/timeutils.py
@@ -117,12 +117,15 @@
utcnow.override_time = None
-def set_time_override(override_time=datetime.datetime.utcnow()):
+def set_time_override(override_time=None):
"""Overrides utils.utcnow.
Make it return a constant time or a list thereof, one at a time.
+
+ :param override_time: datetime instance or list thereof. If not
+ given, defaults to the current UTC time.
"""
- utcnow.override_time = override_time
+ utcnow.override_time = override_time or datetime.datetime.utcnow()
def advance_time_delta(timedelta):
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index ce12a3b..835ba99 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -10,10 +10,13 @@
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.
+Any scenario test should have a real-life use case. An example would be:
+
+ - "As operator I want to start with a blank environment":
+ 1. upload a glance image
+ 2. deploy a vm from it
+ 3. ssh to the guest
+ 4. create a snapshot of the vm
Why are these tests in tempest?
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 8ccc899..d92a0c8 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -88,6 +88,7 @@
auth_url = self.config.identity.uri
dscv = self.config.identity.disable_ssl_certificate_validation
+ region = self.config.identity.region
client_args = (username, password, tenant_name, auth_url)
@@ -96,13 +97,16 @@
return novaclient.client.Client(self.NOVACLIENT_VERSION,
*client_args,
service_type=service_type,
+ region_name=region,
no_cache=True,
insecure=dscv,
http_log_debug=True)
def _get_image_client(self):
token = self.identity_client.auth_token
+ region = self.config.identity.region
endpoint = self.identity_client.service_catalog.url_for(
+ attr='region', filter_value=region,
service_type='image', endpoint_type='publicURL')
dscv = self.config.identity.disable_ssl_certificate_validation
return glanceclient.Client('1', endpoint=endpoint, token=token,
@@ -110,11 +114,13 @@
def _get_volume_client(self, username, password, tenant_name):
auth_url = self.config.identity.uri
+ region = self.config.identity.region
return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
username,
password,
tenant_name,
auth_url,
+ region_name=region,
http_log_debug=True)
def _get_orchestration_client(self, username=None, password=None,
@@ -129,9 +135,12 @@
self._validate_credentials(username, password, tenant_name)
keystone = self._get_identity_client(username, password, tenant_name)
+ region = self.config.identity.region
token = keystone.auth_token
try:
endpoint = keystone.service_catalog.url_for(
+ attr='region',
+ filter_value=region,
service_type='orchestration',
endpoint_type='publicURL')
except keystoneclient.exceptions.EndpointNotFound:
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 305a77b..588e5cd 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -122,6 +122,13 @@
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
+
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
post_body = {
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 07bb6ce..55a4a1b 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -390,3 +390,11 @@
(str(server_id), str(request_id)))
body = json.loads(body)
return resp, body['instanceAction']
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'forceDelete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/compute/v3/__init__.py b/tempest/services/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/__init__.py
diff --git a/tempest/services/compute/v3/json/__init__.py b/tempest/services/compute/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/json/__init__.py
diff --git a/tempest/services/compute/v3/json/extensions_client.py b/tempest/services/compute/v3/json/extensions_client.py
new file mode 100644
index 0000000..ce46a9b
--- /dev/null
+++ b/tempest/services/compute/v3/json/extensions_client.py
@@ -0,0 +1,39 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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 tempest.common.rest_client import RestClient
+
+
+class ExtensionsClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ExtensionsClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def is_enabled(self, extension):
+ _, extensions = self.list_extensions()
+ exts = extensions['extensions']
+ return any([e for e in exts if e['name'] == extension])
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
new file mode 100644
index 0000000..a005edb
--- /dev/null
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -0,0 +1,396 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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.rest_client import RestClient
+from tempest.common import waiters
+from tempest import exceptions
+
+
+class ServersV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url,
+ tenant_name=None, auth_version='v2'):
+ super(ServersV3ClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name,
+ auth_version=auth_version)
+ self.service = self.config.compute.catalog_v3_type
+
+ def create_server(self, name, image_ref, flavor_ref, **kwargs):
+ """
+ Creates an instance of a server.
+ name (Required): The name of the server.
+ image_ref (Required): Reference to the image used to build the server.
+ flavor_ref (Required): The flavor used to build the server.
+ Following optional keyword arguments are accepted:
+ admin_pass: Sets the initial root password.
+ key_name: Key name of keypair that was created earlier.
+ meta: A dictionary of values to be used as metadata.
+ personality: A list of dictionaries for files to be injected into
+ the server.
+ security_groups: A list of security group dicts.
+ networks: A list of network dicts with UUID and fixed_ip.
+ user_data: User data for instance.
+ availability_zone: Availability zone in which to launch instance.
+ access_ip_v4: The IPv4 access address for the server.
+ access_ip_v6: The IPv6 access address for the server.
+ min_count: Count of minimum number of instances to launch.
+ max_count: Count of maximum number of instances to launch.
+ disk_config: Determines if user or admin controls disk configuration.
+ return_reservation_id: Enable/Disable the return of reservation id
+ """
+ post_body = {
+ 'name': name,
+ 'image_ref': image_ref,
+ 'flavor_ref': flavor_ref
+ }
+
+ for option in ['personality', 'admin_pass', 'key_name',
+ 'security_groups', 'networks',
+ ('os-user-data:user_data', 'user_data'),
+ ('os-availability-zone:availability_zone',
+ 'availability_zone'),
+ 'access_ip_v4', 'access_ip_v6',
+ ('os-multiple-create:min_count', 'min_count'),
+ ('os-multiple-create:max_count', 'max_count'),
+ ('metadata', 'meta'),
+ ('os-disk-config:disk_config', 'disk_config'),
+ ('os-multiple-create:return_reservation_id',
+ 'return_reservation_id')]:
+ if isinstance(option, tuple):
+ post_param = option[0]
+ key = option[1]
+ else:
+ post_param = option
+ key = option
+ value = kwargs.get(key)
+ if value is not None:
+ post_body[post_param] = value
+ post_body = json.dumps({'server': post_body})
+ resp, body = self.post('servers', post_body, self.headers)
+
+ body = json.loads(body)
+ # NOTE(maurosr): this deals with the case of multiple server create
+ # with return reservation id set True
+ if 'reservation_id' in body:
+ return resp, body
+ return resp, body['server']
+
+ def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+ access_ip_v6=None, disk_config=None):
+ """
+ Updates the properties of an existing server.
+ server_id: The id of an existing server.
+ name: The name of the server.
+ personality: A list of files to be injected into the server.
+ access_ip_v4: The IPv4 access address for the server.
+ access_ip_v6: The IPv6 access address for the server.
+ """
+
+ post_body = {}
+
+ if meta is not None:
+ post_body['metadata'] = meta
+
+ if name is not None:
+ post_body['name'] = name
+
+ if access_ip_v4 is not None:
+ post_body['access_ip_v4'] = access_ip_v4
+
+ if access_ip_v6 is not None:
+ post_body['access_ip_v6'] = access_ip_v6
+
+ if disk_config is not None:
+ post_body['OS-DCF:diskConfig'] = disk_config
+
+ post_body = json.dumps({'server': post_body})
+ resp, body = self.put("servers/%s" % str(server_id),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['server']
+
+ def get_server(self, server_id):
+ """Returns the details of an existing server."""
+ resp, body = self.get("servers/%s" % str(server_id))
+ body = json.loads(body)
+ return resp, body['server']
+
+ def delete_server(self, server_id):
+ """Deletes the given server."""
+ return self.delete("servers/%s" % str(server_id))
+
+ def list_servers(self, params=None):
+ """Lists all servers for a user."""
+
+ url = 'servers'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def list_servers_with_detail(self, params=None):
+ """Lists all servers in detail for a user."""
+
+ url = 'servers/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def wait_for_server_status(self, server_id, status):
+ """Waits for a server to reach a given status."""
+ return waiters.wait_for_server_status(self, server_id, status)
+
+ def wait_for_server_termination(self, server_id, ignore_error=False):
+ """Waits for server to reach termination."""
+ start_time = int(time.time())
+ while True:
+ try:
+ resp, body = self.get_server(server_id)
+ except exceptions.NotFound:
+ return
+
+ server_status = body['status']
+ if server_status == 'ERROR' and not ignore_error:
+ raise exceptions.BuildErrorException(server_id=server_id)
+
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+
+ time.sleep(self.build_interval)
+
+ def list_addresses(self, server_id):
+ """Lists all addresses for a server."""
+ resp, body = self.get("servers/%s/ips" % str(server_id))
+ body = json.loads(body)
+ return resp, body['addresses']
+
+ def list_addresses_by_network(self, server_id, network_id):
+ """Lists all addresses of a specific network type for a server."""
+ resp, body = self.get("servers/%s/ips/%s" %
+ (str(server_id), network_id))
+ body = json.loads(body)
+ return resp, body
+
+ def action(self, server_id, action_name, response_key, **kwargs):
+ post_body = json.dumps({action_name: kwargs})
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body, self.headers)
+ if response_key is not None:
+ body = json.loads(body)[response_key]
+ return resp, body
+
+ def change_password(self, server_id, admin_password):
+ """Changes the root password for the server."""
+ return self.action(server_id, 'change_password', None,
+ admin_password=admin_password)
+
+ def reboot(self, server_id, reboot_type):
+ """Reboots a server."""
+ return self.action(server_id, 'reboot', None, type=reboot_type)
+
+ def rebuild(self, server_id, image_ref, **kwargs):
+ """Rebuilds a server with a new image."""
+ kwargs['image_ref'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ return self.action(server_id, 'rebuild', 'server', **kwargs)
+
+ def resize(self, server_id, flavor_ref, **kwargs):
+ """Changes the flavor of a server."""
+ kwargs['flavor_ref'] = flavor_ref
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ return self.action(server_id, 'resize', None, **kwargs)
+
+ def confirm_resize(self, server_id, **kwargs):
+ """Confirms the flavor change for a server."""
+ return self.action(server_id, 'confirm_resize', None, **kwargs)
+
+ def revert_resize(self, server_id, **kwargs):
+ """Reverts a server back to its original flavor."""
+ return self.action(server_id, 'revert_resize', None, **kwargs)
+
+ def create_image(self, server_id, name, meta=None):
+ """Creates an image of the original server."""
+
+ post_body = {
+ 'create_image': {
+ 'name': name,
+ }
+ }
+
+ if meta is not None:
+ post_body['create_image']['metadata'] = meta
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body, self.headers)
+ return resp, body
+
+ def list_server_metadata(self, server_id):
+ resp, body = self.get("servers/%s/metadata" % str(server_id))
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ if no_metadata_field:
+ post_body = ""
+ else:
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('servers/%s/metadata' % str(server_id),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def update_server_metadata(self, server_id, meta):
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.post('servers/%s/metadata' % str(server_id),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def get_server_metadata_item(self, server_id, key):
+ resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def set_server_metadata_item(self, server_id, key, meta):
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def delete_server_metadata_item(self, server_id, key):
+ resp, body = self.delete("servers/%s/metadata/%s" %
+ (str(server_id), key))
+ return resp, body
+
+ def stop(self, server_id, **kwargs):
+ return self.action(server_id, 'stop', None, **kwargs)
+
+ def start(self, server_id, **kwargs):
+ return self.action(server_id, 'start', None, **kwargs)
+
+ def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+ """Attaches a volume to a server instance."""
+ return self.action(server_id, 'attach', None, volume_id=volume_id,
+ device=device)
+
+ def detach_volume(self, server_id, volume_id):
+ """Detaches a volume from a server instance."""
+ return self.action(server_id, 'detach', None, volume_id=volume_id)
+
+ def add_security_group(self, server_id, name):
+ """Adds a security group to the server."""
+ return self.action(server_id, 'add_security_group', None, name=name)
+
+ def remove_security_group(self, server_id, name):
+ """Removes a security group from the server."""
+ return self.action(server_id, 'remove_security_group', None, name=name)
+
+ def live_migrate_server(self, server_id, dest_host, use_block_migration):
+ """This should be called with administrator privileges ."""
+
+ migrate_params = {
+ "disk_over_commit": False,
+ "block_migration": use_block_migration,
+ "host": dest_host
+ }
+
+ req_body = json.dumps({'migrate_live': migrate_params})
+
+ resp, body = self.post("servers/%s/action" % str(server_id),
+ req_body, self.headers)
+ return resp, body
+
+ def migrate_server(self, server_id, **kwargs):
+ """Migrates a server to a new host."""
+ return self.action(server_id, 'migrate', None, **kwargs)
+
+ def lock_server(self, server_id, **kwargs):
+ """Locks the given server."""
+ return self.action(server_id, 'lock', None, **kwargs)
+
+ def unlock_server(self, server_id, **kwargs):
+ """UNlocks the given server."""
+ return self.action(server_id, 'unlock', None, **kwargs)
+
+ def suspend_server(self, server_id, **kwargs):
+ """Suspends the provded server."""
+ return self.action(server_id, 'suspend', None, **kwargs)
+
+ def resume_server(self, server_id, **kwargs):
+ """Un-suspends the provded server."""
+ return self.action(server_id, 'resume', None, **kwargs)
+
+ def pause_server(self, server_id, **kwargs):
+ """Pauses the provded server."""
+ return self.action(server_id, 'pause', None, **kwargs)
+
+ def unpause_server(self, server_id, **kwargs):
+ """Un-pauses the provded server."""
+ return self.action(server_id, 'unpause', None, **kwargs)
+
+ def reset_state(self, server_id, state='error'):
+ """Resets the state of a server to active/error."""
+ return self.action(server_id, 'reset_state', None, state=state)
+
+ def get_console_output(self, server_id, length):
+ return self.action(server_id, 'get_console_output', 'output',
+ length=length)
+
+ def rescue_server(self, server_id, adminPass=None):
+ """Rescue the provided server."""
+ return self.action(server_id, 'rescue', None, admin_pass=adminPass)
+
+ def unrescue_server(self, server_id):
+ """Unrescue the provided server."""
+ return self.action(server_id, 'unrescue', None)
+
+ def get_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/os-server-diagnostics" %
+ str(server_id))
+ return resp, json.loads(body)
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" %
+ str(server_id))
+ body = json.loads(body)
+ return resp, body['instance_actions']
+
+ def get_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (str(server_id), str(request_id)))
+ body = json.loads(body)
+ return resp, body['instance_action']
diff --git a/tempest/services/compute/v3/json/services_client.py b/tempest/services/compute/v3/json/services_client.py
new file mode 100644
index 0000000..41564e5
--- /dev/null
+++ b/tempest/services/compute/v3/json/services_client.py
@@ -0,0 +1,71 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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 urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class ServicesV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServicesV3ClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def list_services(self, params=None):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['services']
+
+ def enable_service(self, host_name, binary):
+ """
+ Enable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = json.dumps({
+ 'service': {
+ 'binary': binary,
+ 'host': host_name
+ }
+ })
+ resp, body = self.put('os-services/enable', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['service']
+
+ def disable_service(self, host_name, binary):
+ """
+ Disable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = json.dumps({
+ 'service': {
+ 'binary': binary,
+ 'host': host_name
+ }
+ })
+ resp, body = self.put('os-services/disable', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['service']
diff --git a/tempest/services/compute/v3/xml/__init__.py b/tempest/services/compute/v3/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/xml/__init__.py
diff --git a/tempest/services/compute/v3/xml/extensions_client.py b/tempest/services/compute/v3/xml/extensions_client.py
new file mode 100644
index 0000000..1395b5a
--- /dev/null
+++ b/tempest/services/compute/v3/xml/extensions_client.py
@@ -0,0 +1,45 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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 lxml import etree
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class ExtensionsClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ExtensionsClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_array(self, node):
+ array = []
+ for child in node:
+ array.append(xml_to_json(child))
+ return array
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url, self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, {'extensions': body}
+
+ def is_enabled(self, extension):
+ _, extensions = self.list_extensions()
+ exts = extensions['extensions']
+ return any([e for e in exts if e['name'] == extension])
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
new file mode 100644
index 0000000..6f38b6a
--- /dev/null
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -0,0 +1,623 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 IBM Corp.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 time
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.common import waiters
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_V3
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _translate_ip_xml_json(ip):
+ """
+ Convert the address version to int.
+ """
+ ip = dict(ip)
+ version = ip.get('version')
+ if version:
+ ip['version'] = int(version)
+ if ip.get('type'):
+ ip['type'] = ip.get('type')
+ if ip.get('mac_addr'):
+ ip['mac_addr'] = ip.get('mac_addr')
+ return ip
+
+
+def _translate_network_xml_to_json(network):
+ return [_translate_ip_xml_json(ip.attrib)
+ for ip in network.findall('{%s}ip' % XMLNS_V3)]
+
+
+def _translate_addresses_xml_to_json(xml_addresses):
+ return dict((network.attrib['id'], _translate_network_xml_to_json(network))
+ for network in xml_addresses.findall('{%s}network' % XMLNS_V3))
+
+
+def _translate_server_xml_to_json(xml_dom):
+ """Convert server XML to server JSON.
+
+ The addresses collection does not convert well by the dumb xml_to_json.
+ This method does some pre and post-processing to deal with that.
+
+ Translate XML addresses subtree to JSON.
+
+ Having xml_doc similar to
+ <api:server xmlns:api="http://docs.openstack.org/compute/api/v3">
+ <api:addresses>
+ <api:network id="foo_novanetwork">
+ <api:ip version="4" addr="192.168.0.4"/>
+ </api:network>
+ <api:network id="bar_novanetwork">
+ <api:ip version="4" addr="10.1.0.4"/>
+ <api:ip version="6" addr="2001:0:0:1:2:3:4:5"/>
+ </api:network>
+ </api:addresses>
+ </api:server>
+
+ the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce
+ something like
+
+ {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4},
+ {'addr': '2001:0:0:1:2:3:4:5',
+ 'version': 6}],
+ 'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
+ """
+ nsmap = {'api': XMLNS_V3}
+ addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
+ if addresses:
+ if len(addresses) > 1:
+ raise ValueError('Expected only single `addresses` element.')
+ json_addresses = _translate_addresses_xml_to_json(addresses[0])
+ json = xml_to_json(xml_dom)
+ json['addresses'] = json_addresses
+ else:
+ json = xml_to_json(xml_dom)
+ disk_config = ('{http://docs.openstack.org'
+ '/compute/ext/disk_config/api/v3}disk_config')
+ terminated_at = ('{http://docs.openstack.org/'
+ 'compute/ext/os-server-usage/api/v3}terminated_at')
+ launched_at = ('{http://docs.openstack.org'
+ '/compute/ext/os-server-usage/api/v3}launched_at')
+ power_state = ('{http://docs.openstack.org'
+ '/compute/ext/extended_status/api/v3}power_state')
+ availability_zone = ('{http://docs.openstack.org'
+ '/compute/ext/extended_availability_zone/api/v3}'
+ 'availability_zone')
+ vm_state = ('{http://docs.openstack.org'
+ '/compute/ext/extended_status/api/v3}vm_state')
+ task_state = ('{http://docs.openstack.org'
+ '/compute/ext/extended_status/api/v3}task_state')
+ if disk_config in json:
+ json['os-disk-config:disk_config'] = json.pop(disk_config)
+ if terminated_at in json:
+ json['os-server-usage:terminated_at'] = json.pop(terminated_at)
+ if launched_at in json:
+ json['os-server-usage:launched_at'] = json.pop(launched_at)
+ if power_state in json:
+ json['os-extended-status:power_state'] = json.pop(power_state)
+ if availability_zone in json:
+ json['os-extended-availability-zone:availability_zone'] = json.pop(
+ availability_zone)
+ if vm_state in json:
+ json['os-extended-status:vm_state'] = json.pop(vm_state)
+ if task_state in json:
+ json['os-extended-status:task_state'] = json.pop(task_state)
+ return json
+
+
+class ServersV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url,
+ tenant_name=None, auth_version='v2'):
+ super(ServersV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name,
+ auth_version=auth_version)
+ self.service = self.config.compute.catalog_v3_type
+
+ def _parse_key_value(self, node):
+ """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+ data = {}
+ for node in node.getchildren():
+ data[node.get('key')] = node.text
+ return data
+
+ def _parse_links(self, node, json):
+ del json['link']
+ json['links'] = []
+ for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
+ json['links'].append(xml_to_json(linknode))
+
+ def _parse_server(self, body):
+ json = _translate_server_xml_to_json(body)
+
+ if 'metadata' in json and json['metadata']:
+ # NOTE(danms): if there was metadata, we need to re-parse
+ # that as a special type
+ metadata_tag = body.find('{%s}metadata' % XMLNS_V3)
+ json["metadata"] = self._parse_key_value(metadata_tag)
+ if 'link' in json:
+ self._parse_links(body, json)
+ for sub in ['image', 'flavor']:
+ if sub in json and 'link' in json[sub]:
+ self._parse_links(body, json[sub])
+ return json
+
+ def _parse_xml_virtual_interfaces(self, xml_dom):
+ """
+ Return server's virtual interfaces XML as JSON.
+ """
+ data = {"virtual_interfaces": []}
+ for iface in xml_dom.getchildren():
+ data["virtual_interfaces"].append(
+ {"id": iface.get("id"),
+ "mac_address": iface.get("mac_address")})
+ return data
+
+ def get_server(self, server_id):
+ """Returns the details of an existing server."""
+ resp, body = self.get("servers/%s" % str(server_id), self.headers)
+ server = self._parse_server(etree.fromstring(body))
+ return resp, server
+
+ def lock_server(self, server_id, **kwargs):
+ """Locks the given server."""
+ return self.action(server_id, 'lock', None, **kwargs)
+
+ def unlock_server(self, server_id, **kwargs):
+ """Unlocks the given server."""
+ return self.action(server_id, 'unlock', None, **kwargs)
+
+ def suspend_server(self, server_id, **kwargs):
+ """Suspends the provided server."""
+ return self.action(server_id, 'suspend', None, **kwargs)
+
+ def resume_server(self, server_id, **kwargs):
+ """Un-suspends the provided server."""
+ return self.action(server_id, 'resume', None, **kwargs)
+
+ def pause_server(self, server_id, **kwargs):
+ """Pauses the provided server."""
+ return self.action(server_id, 'pause', None, **kwargs)
+
+ def unpause_server(self, server_id, **kwargs):
+ """Un-pauses the provided server."""
+ return self.action(server_id, 'unpause', None, **kwargs)
+
+ def reset_state(self, server_id, state='error'):
+ """Resets the state of a server to active/error."""
+ return self.action(server_id, 'reset_state', None, state=state)
+
+ def delete_server(self, server_id):
+ """Deletes the given server."""
+ return self.delete("servers/%s" % str(server_id))
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ array.append(xml_to_json(child))
+ return array
+
+ def list_servers(self, params=None):
+ url = 'servers'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ servers = self._parse_array(etree.fromstring(body))
+ return resp, {"servers": servers}
+
+ def list_servers_with_detail(self, params=None):
+ url = 'servers/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ servers = self._parse_array(etree.fromstring(body))
+ return resp, {"servers": servers}
+
+ def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+ access_ip_v6=None, disk_config=None):
+ doc = Document()
+ server = Element("server")
+ doc.append(server)
+
+ if name is not None:
+ server.add_attr("name", name)
+ if access_ip_v4 is not None:
+ server.add_attr("access_ip_v4", access_ip_v4)
+ if access_ip_v6 is not None:
+ server.add_attr("access_ip_v6", access_ip_v6)
+ if meta is not None:
+ metadata = Element("metadata")
+ server.append(metadata)
+ for k, v in meta:
+ meta = Element("meta", key=k)
+ meta.append(Text(v))
+ metadata.append(meta)
+
+ resp, body = self.put('servers/%s' % str(server_id),
+ str(doc), self.headers)
+ return resp, xml_to_json(etree.fromstring(body))
+
+ def create_server(self, name, image_ref, flavor_ref, **kwargs):
+ """
+ Creates an instance of a server.
+ name (Required): The name of the server.
+ image_ref (Required): Reference to the image used to build the server.
+ flavor_ref (Required): The flavor used to build the server.
+ Following optional keyword arguments are accepted:
+ admin_password: Sets the initial root password.
+ key_name: Key name of keypair that was created earlier.
+ meta: A dictionary of values to be used as metadata.
+ personality: A list of dictionaries for files to be injected into
+ the server.
+ security_groups: A list of security group dicts.
+ networks: A list of network dicts with UUID and fixed_ip.
+ user_data: User data for instance.
+ availability_zone: Availability zone in which to launch instance.
+ access_ip_v4: The IPv4 access address for the server.
+ access_ip_v6: The IPv6 access address for the server.
+ min_count: Count of minimum number of instances to launch.
+ max_count: Count of maximum number of instances to launch.
+ disk_config: Determines if user or admin controls disk configuration.
+ return_reservation_id: Enable/Disable the return of reservation id.
+ """
+ server = Element("server",
+ imageRef=image_ref,
+ xmlns=XMLNS_V3,
+ flavor_ref=flavor_ref,
+ image_ref=image_ref,
+ name=name)
+ attrs = ["admin_pass", "access_ip_v4", "access_ip_v6", "key_name",
+ ("os-user-data:user_data",
+ 'user_data',
+ 'xmlns:os-user-data',
+ "http://docs.openstack.org/compute/ext/userdata/api/v3"),
+ ("os-availability-zone:availability_zone",
+ 'availability_zone',
+ 'xmlns:os-availability-zone',
+ "http://docs.openstack.org/compute/ext/"
+ "availabilityzone/api/v3"),
+ ("os-multiple-create:min_count",
+ 'min_count',
+ 'xmlns:os-multiple-create',
+ "http://docs.openstack.org/compute/ext/"
+ "multiplecreate/api/v3"),
+ ("os-multiple-create:max_count",
+ 'max_count',
+ 'xmlns:os-multiple-create',
+ "http://docs.openstack.org/compute/ext/"
+ "multiplecreate/api/v3"),
+ ("os-multiple-create:return_reservation_id",
+ "return_reservation_id",
+ 'xmlns:os-multiple-create',
+ "http://docs.openstack.org/compute/ext/"
+ "multiplecreate/api/v3"),
+ ("os-disk-config:disk_config",
+ "disk_config",
+ "xmlns:os-disk-config",
+ "http://docs.openstack.org/"
+ "compute/ext/disk_config/api/v3")]
+
+ for attr in attrs:
+ if isinstance(attr, tuple):
+ post_param = attr[0]
+ key = attr[1]
+ value = kwargs.get(key)
+ if value is not None:
+ server.add_attr(attr[2], attr[3])
+ server.add_attr(post_param, value)
+ else:
+ post_param = attr
+ key = attr
+ value = kwargs.get(key)
+ if value is not None:
+ server.add_attr(post_param, value)
+
+ if 'security_groups' in kwargs:
+ secgroups = Element("security_groups")
+ server.append(secgroups)
+ for secgroup in kwargs['security_groups']:
+ s = Element("security_group", name=secgroup['name'])
+ secgroups.append(s)
+
+ if 'networks' in kwargs:
+ networks = Element("networks")
+ server.append(networks)
+ for network in kwargs['networks']:
+ s = Element("network", uuid=network['uuid'],
+ fixed_ip=network['fixed_ip'])
+ networks.append(s)
+
+ if 'meta' in kwargs:
+ metadata = Element("metadata")
+ server.append(metadata)
+ for k, v in kwargs['meta'].items():
+ meta = Element("meta", key=k)
+ meta.append(Text(v))
+ metadata.append(meta)
+
+ if 'personality' in kwargs:
+ personality = Element('personality')
+ server.append(personality)
+ for k in kwargs['personality']:
+ temp = Element('file', path=k['path'])
+ temp.append(Text(k['contents']))
+ personality.append(temp)
+
+ resp, body = self.post('servers', str(Document(server)), self.headers)
+ server = self._parse_server(etree.fromstring(body))
+ return resp, server
+
+ def wait_for_server_status(self, server_id, status):
+ """Waits for a server to reach a given status."""
+ return waiters.wait_for_server_status(self, server_id, status)
+
+ def wait_for_server_termination(self, server_id, ignore_error=False):
+ """Waits for server to reach termination."""
+ start_time = int(time.time())
+ while True:
+ try:
+ resp, body = self.get_server(server_id)
+ except exceptions.NotFound:
+ return
+
+ server_status = body['status']
+ if server_status == 'ERROR' and not ignore_error:
+ raise exceptions.BuildErrorException
+
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+
+ time.sleep(self.build_interval)
+
+ def _parse_network(self, node):
+ addrs = []
+ for child in node.getchildren():
+ addrs.append({'version': int(child.get('version')),
+ 'addr': child.get('addr')})
+ return {node.get('id'): addrs}
+
+ def list_addresses(self, server_id):
+ """Lists all addresses for a server."""
+ resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
+
+ networks = {}
+ xml_list = etree.fromstring(body)
+ for child in xml_list.getchildren():
+ network = self._parse_network(child)
+ networks.update(**network)
+
+ return resp, networks
+
+ def list_addresses_by_network(self, server_id, network_id):
+ """Lists all addresses of a specific network type for a server."""
+ resp, body = self.get("servers/%s/ips/%s" % (str(server_id),
+ network_id),
+ self.headers)
+ network = self._parse_network(etree.fromstring(body))
+
+ return resp, network
+
+ def action(self, server_id, action_name, response_key, **kwargs):
+ if 'xmlns' not in kwargs:
+ kwargs['xmlns'] = XMLNS_V3
+ doc = Document((Element(action_name, **kwargs)))
+ resp, body = self.post("servers/%s/action" % server_id,
+ str(doc), self.headers)
+ if response_key is not None:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def change_password(self, server_id, password):
+ return self.action(server_id, "change_password", None,
+ admin_pass=password)
+
+ def reboot(self, server_id, reboot_type):
+ return self.action(server_id, "reboot", None, type=reboot_type)
+
+ def rebuild(self, server_id, image_ref, **kwargs):
+ kwargs['image_ref'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+ "compute/ext/disk_config/api/v3"
+ kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+ if 'xmlns' not in kwargs:
+ kwargs['xmlns'] = XMLNS_V3
+
+ attrs = kwargs.copy()
+ if 'metadata' in attrs:
+ del attrs['metadata']
+ rebuild = Element("rebuild",
+ **attrs)
+
+ if 'metadata' in kwargs:
+ metadata = Element("metadata")
+ rebuild.append(metadata)
+ for k, v in kwargs['metadata'].items():
+ meta = Element("meta", key=k)
+ meta.append(Text(v))
+ metadata.append(meta)
+
+ resp, body = self.post('servers/%s/action' % server_id,
+ str(Document(rebuild)), self.headers)
+ server = self._parse_server(etree.fromstring(body))
+ return resp, server
+
+ def resize(self, server_id, flavor_ref, **kwargs):
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+ "compute/ext/disk_config/api/v3"
+ kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+ kwargs['flavor_ref'] = flavor_ref
+ return self.action(server_id, 'resize', None, **kwargs)
+
+ def confirm_resize(self, server_id, **kwargs):
+ return self.action(server_id, 'confirm_resize', None, **kwargs)
+
+ def revert_resize(self, server_id, **kwargs):
+ return self.action(server_id, 'revert_resize', None, **kwargs)
+
+ def stop(self, server_id, **kwargs):
+ return self.action(server_id, 'stop', None, **kwargs)
+
+ def start(self, server_id, **kwargs):
+ return self.action(server_id, 'start', None, **kwargs)
+
+ def create_image(self, server_id, name, meta=None):
+ """Creates an image of the original server."""
+ post_body = Element('create_image', name=name)
+
+ if meta:
+ metadata = Element('metadata')
+ post_body.append(metadata)
+ for k, v in meta.items():
+ data = Element('meta', key=k)
+ data.append(Text(v))
+ metadata.append(data)
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ str(Document(post_body)), self.headers)
+ return resp, body
+
+ def add_security_group(self, server_id, name):
+ return self.action(server_id, 'add_security_group', None, name=name)
+
+ def remove_security_group(self, server_id, name):
+ return self.action(server_id, 'remove_security_group', None, name=name)
+
+ def live_migrate_server(self, server_id, dest_host, use_block_migration):
+ """This should be called with administrator privileges ."""
+
+ req_body = Element("migrate_live",
+ xmlns=XMLNS_V3,
+ disk_over_commit=False,
+ block_migration=use_block_migration,
+ host=dest_host)
+
+ resp, body = self.post("servers/%s/action" % str(server_id),
+ str(Document(req_body)), self.headers)
+ return resp, body
+
+ def list_server_metadata(self, server_id):
+ resp, body = self.get("servers/%s/metadata" % str(server_id),
+ self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
+
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ doc = Document()
+ if not no_metadata_field:
+ metadata = Element("metadata")
+ doc.append(metadata)
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ metadata.append(meta_element)
+ resp, body = self.put('servers/%s/metadata' % str(server_id),
+ str(doc), self.headers)
+ return resp, xml_to_json(etree.fromstring(body))
+
+ def update_server_metadata(self, server_id, meta):
+ doc = Document()
+ metadata = Element("metadata")
+ doc.append(metadata)
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ metadata.append(meta_element)
+ resp, body = self.post("/servers/%s/metadata" % str(server_id),
+ str(doc), headers=self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def get_server_metadata_item(self, server_id, key):
+ resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key),
+ headers=self.headers)
+ return resp, dict([(etree.fromstring(body).attrib['key'],
+ xml_to_json(etree.fromstring(body)))])
+
+ def set_server_metadata_item(self, server_id, key, meta):
+ doc = Document()
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ doc.append(meta_element)
+ resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+ str(doc), self.headers)
+ return resp, xml_to_json(etree.fromstring(body))
+
+ def delete_server_metadata_item(self, server_id, key):
+ resp, body = self.delete("servers/%s/metadata/%s" %
+ (str(server_id), key))
+ return resp, body
+
+ def get_console_output(self, server_id, length):
+ return self.action(server_id, 'get_console_output', 'output',
+ length=length)
+
+ def rescue_server(self, server_id, admin_pass=None):
+ """Rescue the provided server."""
+ return self.action(server_id, 'rescue', None, admin_pass=admin_pass)
+
+ def unrescue_server(self, server_id):
+ """Unrescue the provided server."""
+ return self.action(server_id, 'unrescue', None)
+
+ def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+ return self.action(server_id, "attach", None, volume_id=volume_id,
+ device=device)
+
+ def detach_volume(self, server_id, volume_id):
+ return self.action(server_id, "detach", None, volume_id=volume_id)
+
+ def get_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/os-server-diagnostics" % server_id,
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" % server_id,
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def get_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (server_id, request_id), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/services_client.py b/tempest/services/compute/v3/xml/services_client.py
new file mode 100644
index 0000000..855641b
--- /dev/null
+++ b/tempest/services/compute/v3/xml/services_client.py
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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 urllib
+
+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
+
+
+class ServicesV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServicesV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def list_services(self, params=None):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
+
+ def enable_service(self, host_name, binary):
+ """
+ Enable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = Element("service")
+ post_body.add_attr('binary', binary)
+ post_body.add_attr('host', host_name)
+
+ resp, body = self.put('os-services/enable', str(Document(post_body)),
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def disable_service(self, host_name, binary):
+ """
+ Disable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = Element("service")
+ post_body.add_attr('binary', binary)
+ post_body.add_attr('host', host_name)
+
+ resp, body = self.put('os-services/disable', str(Document(post_body)),
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index ad79ed6..860dd5b 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -18,6 +18,7 @@
import collections
XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
+XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1"
# NOTE(danms): This is just a silly implementation to help make generating
diff --git a/tempest/services/compute/xml/fixed_ips_client.py b/tempest/services/compute/xml/fixed_ips_client.py
index ef023f0..bf2de38 100644
--- a/tempest/services/compute/xml/fixed_ips_client.py
+++ b/tempest/services/compute/xml/fixed_ips_client.py
@@ -15,13 +15,11 @@
# 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 Document
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import Text
-from tempest.services.compute.xml.common import xml_to_json
class FixedIPsClientXML(RestClientXML):
@@ -31,10 +29,6 @@
auth_url, tenant_name)
self.service = self.config.compute.catalog_type
- def _parse_fixed_ip_details(self, body):
- body = xml_to_json(etree.fromstring(body))
- return body
-
def get_fixed_ip_details(self, fixed_ip):
url = "os-fixed-ips/%s" % (fixed_ip)
resp, body = self.get(url, self.headers)
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index 12e24d0..d4c456e 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -183,6 +183,13 @@
def _parse_array_access(self, node):
return [xml_to_json(x) for x in node]
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id),
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
doc = Document()
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 43de4ef..e21bfc4 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -600,3 +600,11 @@
(server_id, request_id), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'forceDelete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 41b9e81..0a7f0e2 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -175,7 +175,7 @@
def delete_image(self, image_id):
url = 'v1/images/%s' % image_id
- self.delete(url)
+ return self.delete(url)
def image_list(self, **kwargs):
url = 'v1/images'
@@ -270,7 +270,7 @@
if value == 'killed':
raise exceptions.ImageKilledException(image_id=image_id,
- status=value)
+ status=status)
if dtime > self.build_timeout:
message = ('Time Limit Exceeded! (%ds)'
'while waiting for %s, '
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index eb87cbe..670492a 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -128,6 +128,22 @@
resp, body = self.post(url, post_body, self.headers)
return resp, body
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = {}
+ post_body = json.dumps({'os-reserve': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = {}
+ post_body = json.dumps({'os-unreserve': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index be292a2..0edf7f3 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -266,3 +266,21 @@
if body:
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = Element("os-reserve")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = Element("os-unreserve")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index ae86f6e..20e58d4 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -26,6 +26,16 @@
To activate logging on your console please make sure that you activate `use_stderr`
in tempest.conf or use the default `logging.conf.sample` file.
+Running default stress test set
+-------------------------------
+
+The stress test framework can automatically discover test inside the tempest
+test suite. All test flag with the `@stresstest` decorator will be executed.
+In order to use this discovery you have to be in the tempest root directory
+and execute the following:
+
+ tempest/stress/run_stress.py -a -d 30
+
Running the sample test
-----------------------
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index 12c1c25..ba83cf4 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -17,24 +17,9 @@
import os
import fixtures
-import mox
-import stubout
import testtools
-
-class MoxStubout(fixtures.Fixture):
- """Deal with code around mox and stubout as a fixture."""
-
- def setUp(self):
- super(MoxStubout, self).setUp()
- # emulate some of the mox stuff, we can't use the metaclass
- # because it screws with our generators
- self.mox = mox.Mox()
- self.stubs = stubout.StubOutForTesting()
- self.addCleanup(self.stubs.UnsetAll)
- self.addCleanup(self.stubs.SmartUnsetAll)
- self.addCleanup(self.mox.UnsetStubs)
- self.addCleanup(self.mox.VerifyAll)
+from tempest.openstack.common.fixture import moxstubout
class TestCase(testtools.TestCase):
@@ -50,6 +35,6 @@
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
- mox_fixture = self.useFixture(MoxStubout())
+ mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.mox = mox_fixture.mox
self.stubs = mox_fixture.stubs
diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh
new file mode 100755
index 0000000..b86e0c2
--- /dev/null
+++ b/tools/config/generate_sample.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+print_hint() {
+ echo "Try \`${0##*/} --help' for more information." >&2
+}
+
+PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:o: \
+ --long help,base-dir:,package-name:,output-dir: -- "$@")
+
+if [ $? != 0 ] ; then print_hint ; exit 1 ; fi
+
+eval set -- "$PARSED_OPTIONS"
+
+while true; do
+ case "$1" in
+ -h|--help)
+ echo "${0##*/} [options]"
+ echo ""
+ echo "options:"
+ echo "-h, --help show brief help"
+ echo "-b, --base-dir=DIR project base directory"
+ echo "-p, --package-name=NAME project package name"
+ echo "-o, --output-dir=DIR file output directory"
+ exit 0
+ ;;
+ -b|--base-dir)
+ shift
+ BASEDIR=`echo $1 | sed -e 's/\/*$//g'`
+ shift
+ ;;
+ -p|--package-name)
+ shift
+ PACKAGENAME=`echo $1`
+ shift
+ ;;
+ -o|--output-dir)
+ shift
+ OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'`
+ shift
+ ;;
+ --)
+ break
+ ;;
+ esac
+done
+
+BASEDIR=${BASEDIR:-`pwd`}
+if ! [ -d $BASEDIR ]
+then
+ echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1
+elif [[ $BASEDIR != /* ]]
+then
+ BASEDIR=$(cd "$BASEDIR" && pwd)
+fi
+
+PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}}
+TARGETDIR=$BASEDIR/$PACKAGENAME
+if ! [ -d $TARGETDIR ]
+then
+ echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1
+fi
+
+OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc}
+# NOTE(bnemec): Some projects put their sample config in etc/,
+# some in etc/$PACKAGENAME/
+if [ -d $OUTPUTDIR/$PACKAGENAME ]
+then
+ OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME
+elif ! [ -d $OUTPUTDIR ]
+then
+ echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2
+ exit 1
+fi
+
+BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'`
+find $TARGETDIR -type f -name "*.pyc" -delete
+FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \
+ -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u)
+
+EXTRA_MODULES_FILE="`dirname $0`/oslo.config.generator.rc"
+if test -r "$EXTRA_MODULES_FILE"
+then
+ source "$EXTRA_MODULES_FILE"
+fi
+
+export EVENTLET_NO_GREENDNS=yes
+
+OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs)
+[ "$OS_VARS" ] && eval "unset \$OS_VARS"
+DEFAULT_MODULEPATH=tempest.openstack.common.config.generator
+MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
+OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
+python -m $MODULEPATH $FILES > $OUTPUTFILE
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 92d66ae..1bab88a 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -121,9 +121,6 @@
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
- def post_process(self):
- self.get_distro().post_process()
-
def parse_args(self, argv):
"""Parses command-line arguments."""
parser = optparse.OptionParser()
@@ -156,14 +153,6 @@
' requires virtualenv, please install it using your'
' favorite package management tool' % self.project)
- def post_process(self):
- """Any distribution-specific post-processing gets done here.
-
- In particular, this is useful for applying patches to code inside
- the venv.
- """
- pass
-
class Fedora(Distro):
"""This covers all Fedora-based distributions.
@@ -175,10 +164,6 @@
return self.run_command_with_code(['rpm', '-q', pkg],
check_exit_code=False)[1] == 0
- def apply_patch(self, originalfile, patchfile):
- self.run_command(['patch', '-N', originalfile, patchfile],
- check_exit_code=False)
-
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
@@ -187,27 +172,3 @@
self.die("Please install 'python-virtualenv'.")
super(Fedora, self).install_virtualenv()
-
- def post_process(self):
- """Workaround for a bug in eventlet.
-
- This currently affects RHEL6.1, but the fix can safely be
- applied to all RHEL and Fedora distributions.
-
- This can be removed when the fix is applied upstream.
-
- Nova: https://bugs.launchpad.net/nova/+bug/884915
- Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
- RHEL: https://bugzilla.redhat.com/958868
- """
-
- if os.path.exists('contrib/redhat-eventlet.patch'):
- # Install "patch" program if it's not there
- if not self.check_pkg('patch'):
- self.die("Please install 'patch'.")
-
- # Apply the eventlet patch
- self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
- 'site-packages',
- 'eventlet/green/subprocess.py'),
- 'contrib/redhat-eventlet.patch')