Merge "Update docs config"
diff --git a/HACKING.rst b/HACKING.rst
index 2fa949d..9878b67 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -123,8 +123,8 @@
earlier a problem is detected the easier it is to debug, especially
where there is complicated setup required.
-Parallel Test Exectution
-------------------------
+Parallel Test Execution
+-----------------------
Tempest by default runs its tests in parallel this creates the possibility for
interesting interactions between tests which can cause unexpected failures.
Tenant isolation provides protection from most of the potential race conditions
@@ -133,7 +133,7 @@
- Resources outside of a tenant scope still have the potential to conflict. This
is a larger concern for the admin tests since most resources and actions that
- require admin privleges are outside of tenants.
+ require admin privileges are outside of tenants.
- Races between methods in the same class are not a problem because
parallelization in tempest is at the test class level, but if there is a json
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index ec14c7e..115a2b5 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -222,7 +222,7 @@
tenant_network_cidr = 10.100.0.0/16
# The mask bits used to partition the tenant block.
-tenant_network_mask_bits = 28
+tenant_network_mask_bits = 24
# If tenant networks are reachable, connectivity checks will be
# performed directly against addresses on those networks.
diff --git a/requirements.txt b/requirements.txt
index b15fb92..3b9ec44 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,7 +17,7 @@
testresources>=0.2.4
keyring>=1.6.1
testrepository>=0.0.17
-oslo.config>=1.1.0
+oslo.config>=1.2.0
eventlet>=0.13.0
six<1.4.0
iso8601>=0.1.4
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index a5dceca..40b005d 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -15,25 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import fixtures
-
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
-from tempest.openstack.common import lockutils
from tempest.test import attr
-class LockFixture(fixtures.Fixture):
- def __init__(self, name):
- self.mgr = lockutils.lock(name, 'tempest-', True)
-
- def setUp(self):
- super(LockFixture, self).setUp()
- self.addCleanup(self.mgr.__exit__, None, None, None)
- self.mgr.__enter__()
-
-
class AggregatesAdminTestJSON(base.BaseComputeAdminTest):
"""
@@ -160,7 +148,7 @@
@attr(type='gate')
def test_aggregate_add_remove_host(self):
# Add an host to the given aggregate and remove.
- self.useFixture(LockFixture('availability_zone'))
+ self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -182,7 +170,7 @@
@attr(type='gate')
def test_aggregate_add_host_list(self):
# Add an host to the given aggregate and list.
- self.useFixture(LockFixture('availability_zone'))
+ self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -200,7 +188,7 @@
@attr(type='gate')
def test_aggregate_add_host_get_details(self):
# Add an host to the given aggregate and get details.
- self.useFixture(LockFixture('availability_zone'))
+ self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -215,7 +203,7 @@
@attr(type='gate')
def test_aggregate_add_host_create_server_with_az(self):
# Add an host to the given aggregate and create a server.
- self.useFixture(LockFixture('availability_zone'))
+ self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = rand_name(self.aggregate_name_prefix)
az_name = rand_name(self.az_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
@@ -262,7 +250,7 @@
@attr(type=['negative', 'gate'])
def test_aggregate_remove_host_as_user(self):
# Regular user is not allowed to remove a host from an aggregate.
- self.useFixture(LockFixture('availability_zone'))
+ self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 19d7973..bf09428 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -15,6 +15,7 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
@@ -42,6 +43,7 @@
@attr(type='gate')
def test_list_hosts_with_zone(self):
+ self.useFixture(fixtures.LockFixture('availability_zone'))
resp, hosts = self.client.list_hosts()
host = hosts[0]
zone_name = host['zone']
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index cb47066..0abf779 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -15,7 +15,9 @@
# under the License.
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
@@ -31,6 +33,12 @@
def setUpClass(cls):
super(ServersAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.servers_client
+ cls.flavors_client = cls.os_adm.flavors_client
+
+ cls.admin_client = cls._get_identity_admin_client()
+ tenant = cls.admin_client.get_tenant_by_name(
+ cls.client.tenant_name)
+ cls.tenant_id = tenant['id']
cls.s1_name = rand_name('server')
resp, server = cls.create_server(name=cls.s1_name,
@@ -39,6 +47,16 @@
resp, server = cls.create_server(name=cls.s2_name,
wait_until='ACTIVE')
+ def _get_unused_flavor_id(self):
+ flavor_id = rand_int_id(start=1000)
+ while True:
+ try:
+ resp, body = self.flavors_client.get_flavor_details(flavor_id)
+ except exceptions.NotFound:
+ break
+ flavor_id = rand_int_id(start=1000)
+ return flavor_id
+
@attr(type='gate')
def test_list_servers_by_admin(self):
# Listing servers by admin user returns empty list by default
@@ -59,6 +77,42 @@
self.assertIn(self.s1_name, servers_name)
self.assertIn(self.s2_name, servers_name)
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_using_overlimit_ram(self):
+ flavor_name = rand_name("flavor-")
+ flavor_id = self._get_unused_flavor_id()
+ resp, quota_set = self.quotas_client.get_default_quota_set(
+ self.tenant_id)
+ ram = int(quota_set['ram']) + 1
+ vcpus = 8
+ disk = 10
+ resp, flavor_ref = self.flavors_client.create_flavor(flavor_name,
+ ram, vcpus, disk,
+ flavor_id)
+ self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
+ self.assertRaises(exceptions.OverLimit,
+ self.client.resize,
+ self.servers[0]['id'],
+ flavor_ref['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_using_overlimit_vcpus(self):
+ flavor_name = rand_name("flavor-")
+ flavor_id = self._get_unused_flavor_id()
+ ram = 512
+ resp, quota_set = self.quotas_client.get_default_quota_set(
+ self.tenant_id)
+ vcpus = int(quota_set['cores']) + 1
+ disk = 10
+ resp, flavor_ref = self.flavors_client.create_flavor(flavor_name,
+ ram, vcpus, disk,
+ flavor_id)
+ self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
+ self.assertRaises(exceptions.OverLimit,
+ self.client.resize,
+ self.servers[0]['id'],
+ flavor_ref['id'])
+
class ServersAdminTestXML(ServersAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index d2f437b..0b527d9 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -22,6 +22,7 @@
from tempest.common import isolated_creds
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.openstack.common import log as logging
import tempest.test
@@ -103,6 +104,9 @@
for image_id in cls.images:
try:
cls.images_client.delete_image(image_id)
+ except exceptions.NotFound:
+ # The image may have already been deleted which is OK.
+ pass
except Exception as exc:
LOG.info('Exception raised deleting image %s', image_id)
LOG.exception(exc)
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 1c72eba..3c76069 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -93,6 +93,14 @@
self.assertRaises(exceptions.NotFound,
self.client.get_floating_ip_details, non_exist_id)
+ @attr(type='gate')
+ def test_list_floating_ip_pools(self):
+ # Positive test:Should return the list of floating IP Pools
+ resp, floating_ip_pools = self.client.list_floating_ip_pools()
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(floating_ip_pools),
+ "Expected floating IP Pools. Got zero.")
+
class FloatingIPDetailsTestXML(FloatingIPDetailsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 77f92c9..0d5a8fa 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -196,6 +196,18 @@
raise exceptions.TimeoutException(message)
@attr(type=['negative', 'gate'])
+ def test_resize_server_using_nonexist_flavor(self):
+ flavor_id = -1
+ self.assertRaises(exceptions.BadRequest,
+ self.client.resize, self.server_id, flavor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_using_null_flavor(self):
+ flavor_id = ""
+ self.assertRaises(exceptions.BadRequest,
+ self.client.resize, self.server_id, flavor_id)
+
+ @attr(type=['negative', 'gate'])
def test_reboot_nonexistent_server_soft(self):
# Negative Test: The server reboot on non existent server should return
# an error
@@ -288,6 +300,26 @@
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.BadRequest,
+ 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 ServerActionsTestXML(ServerActionsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index fcbde50..e9c395e 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -18,6 +18,8 @@
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
@@ -83,3 +85,25 @@
self.assertEqual(200, resp.status)
members = body['members']
self.assertEqual(0, len(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.tenants[0], 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.tenants[0], 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/network/test_extensions.py b/tempest/api/network/test_extensions.py
new file mode 100644
index 0000000..1b27d1b
--- /dev/null
+++ b/tempest/api/network/test_extensions.py
@@ -0,0 +1,79 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack, Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.network import base
+from tempest.test import attr
+
+
+class ExtensionsTestJSON(base.BaseNetworkTest):
+ _interface = 'json'
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ List all available extensions
+
+ v2.0 of the Neutron API is assumed. It is also assumed that the following
+ options are defined in the [network] section of etc/tempest.conf:
+
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(ExtensionsTestJSON, cls).setUpClass()
+
+ @attr(type='smoke')
+ def test_list_show_extensions(self):
+ # List available extensions for the tenant
+ expected_alias = ['security-group', 'l3_agent_scheduler',
+ 'ext-gw-mode', 'binding', 'quotas',
+ 'agent', 'dhcp_agent_scheduler', 'provider',
+ 'router', 'extraroute', 'external-net',
+ 'allowed-address-pairs', 'extra_dhcp_opt']
+ actual_alias = list()
+ resp, extensions = self.client.list_extensions()
+ self.assertEqual('200', resp['status'])
+ list_extensions = extensions['extensions']
+ # Show and verify the details of the available extensions
+ for ext in list_extensions:
+ ext_name = ext['name']
+ ext_alias = ext['alias']
+ actual_alias.append(ext['alias'])
+ resp, ext_details = self.client.show_extension_details(ext_alias)
+ self.assertEqual('200', resp['status'])
+ ext_details = ext_details['extension']
+
+ self.assertIsNotNone(ext_details)
+ self.assertIn('updated', ext_details.keys())
+ self.assertIn('name', ext_details.keys())
+ self.assertIn('description', ext_details.keys())
+ self.assertIn('namespace', ext_details.keys())
+ self.assertIn('links', ext_details.keys())
+ self.assertIn('alias', ext_details.keys())
+ self.assertEqual(ext_details['name'], ext_name)
+ self.assertEqual(ext_details['alias'], ext_alias)
+ self.assertEqual(ext_details, ext)
+ # Verify if expected extensions are present in the actual list
+ # of extensions returned
+ for e in expected_alias:
+ self.assertIn(e, actual_alias)
+
+
+class ExtensionsTestXML(ExtensionsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 7c10138..407c3ec 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -16,7 +16,6 @@
# under the License.
import hashlib
-import time
from tempest.api.object_storage import base
from tempest.common.utils.data_utils import arbitrary_string
@@ -225,44 +224,6 @@
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='gate')
- def test_get_object_using_temp_url(self):
- # access object using temporary URL within expiration time
-
- try:
- # update account metadata
- # flag to check if account metadata got updated
- flag = False
- key = 'Meta'
- metadata = {'Temp-URL-Key': key}
- resp, _ = self.account_client.create_account_metadata(
- metadata=metadata)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
- flag = True
- resp, _ = self.account_client.list_account_metadata()
- self.assertIn('x-account-meta-temp-url-key', resp)
- self.assertEqual(resp['x-account-meta-temp-url-key'], key)
-
- # create object
- object_name = rand_name(name='ObjectTemp')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
- self.object_client.create_object(self.container_name,
- object_name, data)
- expires = int(time.time() + 10)
-
- # trying to get object using temp url with in expiry time
- _, body = self.object_client.get_object_using_temp_url(
- self.container_name, object_name,
- expires, key)
- self.assertEqual(body, data)
- finally:
- if flag:
- resp, _ = self.account_client.delete_account_metadata(
- metadata=metadata)
- resp, _ = self.account_client.list_account_metadata()
- self.assertNotIn('x-account-meta-temp-url-key', resp)
-
- @attr(type='gate')
def test_object_upload_in_segments(self):
# create object
object_name = rand_name(name='LObject')
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
new file mode 100644
index 0000000..0fd5499
--- /dev/null
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -0,0 +1,157 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+#
+# Author: Joe H. Rahme <joe.hakim.rahme@enovance.com>
+#
+# 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 hashlib
+import hmac
+import time
+import urlparse
+
+from tempest.api.object_storage import base
+from tempest.common.utils.data_utils import arbitrary_string
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
+
+
+class ObjectTempUrlTest(base.BaseObjectTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(ObjectTempUrlTest, cls).setUpClass()
+ cls.container_name = rand_name(name='TestContainer')
+ cls.container_client.create_container(cls.container_name)
+ cls.containers = [cls.container_name]
+
+ # update account metadata
+ cls.key = 'Meta'
+ cls.metadata = {'Temp-URL-Key': cls.key}
+ cls.account_client.create_account_metadata(metadata=cls.metadata)
+ cls.account_client_metadata, _ = \
+ cls.account_client.list_account_metadata()
+
+ @classmethod
+ def tearDownClass(cls):
+ resp, _ = cls.account_client.delete_account_metadata(
+ metadata=cls.metadata)
+ resp, _ = cls.account_client.list_account_metadata()
+
+ cls.delete_containers(cls.containers)
+ # delete the user setup created
+ cls.data.teardown_all()
+ super(ObjectTempUrlTest, cls).tearDownClass()
+
+ def setUp(self):
+ super(ObjectTempUrlTest, self).setUp()
+ # make sure the metadata has been set
+ self.assertIn('x-account-meta-temp-url-key',
+ self.account_client_metadata)
+
+ self.assertEqual(
+ self.account_client_metadata['x-account-meta-temp-url-key'],
+ self.key)
+
+ # create object
+ self.object_name = rand_name(name='ObjectTemp')
+ self.data = arbitrary_string(size=len(self.object_name),
+ base_text=self.object_name)
+ self.object_client.create_object(self.container_name,
+ self.object_name, self.data)
+
+ def get_temp_url(self, container, object_name, method, expires,
+ key):
+ """Create the temporary URL."""
+
+ path = "%s/%s/%s" % (
+ urlparse.urlparse(self.object_client.base_url).path,
+ container, object_name)
+
+ hmac_body = '%s\n%s\n%s' % (method, expires, path)
+ sig = hmac.new(key, hmac_body, hashlib.sha1).hexdigest()
+
+ url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container,
+ object_name,
+ sig, expires)
+
+ return url
+
+ @attr(type='gate')
+ def test_get_object_using_temp_url(self):
+ EXPIRATION_TIME = 10000 # high to ensure the test finishes.
+ expires = int(time.time() + EXPIRATION_TIME)
+
+ # get a temp URL for the created object
+ url = self.get_temp_url(self.container_name,
+ self.object_name, "GET",
+ expires, self.key)
+
+ # trying to get object using temp url within expiry time
+ _, body = self.object_client.get_object_using_temp_url(url)
+
+ self.assertEqual(body, self.data)
+
+ # Testing a HEAD on this Temp URL
+ resp, body = self.object_client.head(url)
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+
+ @attr(type='gate')
+ def test_put_object_using_temp_url(self):
+ # make sure the metadata has been set
+ new_data = arbitrary_string(size=len(self.object_name),
+ base_text=rand_name(name="random"))
+
+ EXPIRATION_TIME = 10000 # high to ensure the test finishes.
+ expires = int(time.time() + EXPIRATION_TIME)
+
+ url = self.get_temp_url(self.container_name,
+ self.object_name, "PUT",
+ expires, self.key)
+
+ # trying to put random data in the object using temp url
+ resp, body = self.object_client.put_object_using_temp_url(
+ url, new_data)
+
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+
+ # Testing a HEAD on this Temp URL
+ resp, body = self.object_client.head(url)
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+
+ # Validate that the content of the object has been modified
+ url = self.get_temp_url(self.container_name,
+ self.object_name, "GET",
+ expires, self.key)
+
+ _, body = self.object_client.get_object_using_temp_url(url)
+ self.assertEqual(body, new_data)
+
+ @attr(type=['gate', 'negative'])
+ def test_get_object_after_expiration_time(self):
+ EXPIRATION_TIME = 1
+ expires = int(time.time() + EXPIRATION_TIME)
+
+ # get a temp URL for the created object
+ url = self.get_temp_url(self.container_name,
+ self.object_name, "GET",
+ expires, self.key)
+
+ # temp URL is valid for 1 seconds, let's wait 3
+ time.sleep(EXPIRATION_TIME + 2)
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.object_client.get_object_using_temp_url,
+ url)
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 119e3e0..f2915f7 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -34,6 +34,15 @@
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(volume_id)
+ def _is_true(self, val):
+ # NOTE(jdg): Temporary conversion method to get cinder patch
+ # merged. Then we'll make this strict again and
+ #specifically check "true" or "false"
+ if val in ['true', 'True', True]:
+ return True
+ else:
+ return False
+
def _volume_create_get_update_delete(self, **kwargs):
# Create a volume, Get it's details and Delete the volume
volume = {}
@@ -69,10 +78,14 @@
fetched_volume['metadata'],
'The fetched Volume is different '
'from the created Volume')
+
+ # NOTE(jdg): Revert back to strict true/false checking
+ # after fix for bug #1227837 merges
+ boot_flag = self._is_true(fetched_volume['bootable'])
if 'imageRef' in kwargs:
- self.assertEqual(fetched_volume['bootable'], True)
+ self.assertEqual(boot_flag, True)
if 'imageRef' not in kwargs:
- self.assertEqual(fetched_volume['bootable'], False)
+ self.assertEqual(boot_flag, False)
# Update Volume
new_v_name = rand_name('new-Volume')
@@ -92,10 +105,14 @@
self.assertEqual(new_v_name, updated_volume['display_name'])
self.assertEqual(new_desc, updated_volume['display_description'])
self.assertEqual(metadata, updated_volume['metadata'])
+
+ # NOTE(jdg): Revert back to strict true/false checking
+ # after fix for bug #1227837 merges
+ boot_flag = self._is_true(updated_volume['bootable'])
if 'imageRef' in kwargs:
- self.assertEqual(updated_volume['bootable'], True)
+ self.assertEqual(boot_flag, True)
if 'imageRef' not in kwargs:
- self.assertEqual(updated_volume['bootable'], False)
+ self.assertEqual(boot_flag, False)
@attr(type='gate')
def test_volume_get_metadata_none(self):
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 7eeb1ee..a48cea2 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.volume import base
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
@@ -29,37 +31,24 @@
super(VolumesNegativeTest, cls).setUpClass()
cls.client = cls.volumes_client
+ # Create a test shared instance and volume for attach/detach tests
+ vol_name = rand_name('Volume-')
+
+ cls.volume = cls.create_volume(size=1, display_name=vol_name)
+ cls.client.wait_for_volume_status(cls.volume['id'], 'available')
+ cls.mountpoint = "/dev/vdc"
+
@attr(type='gate')
def test_volume_get_nonexistant_volume_id(self):
- # Should not be able to get a non-existent volume
- # Creating a non-existent volume id
- volume_id_list = []
- resp, volumes = self.client.list_volumes()
- for i in range(len(volumes)):
- volume_id_list.append(volumes[i]['id'])
- while True:
- non_exist_id = rand_name('999')
- if non_exist_id not in volume_id_list:
- break
- # Trying to Get a non-existent volume
+ # Should not be able to get a non-existant volume
self.assertRaises(exceptions.NotFound, self.client.get_volume,
- non_exist_id)
+ str(uuid.uuid4()))
@attr(type='gate')
def test_volume_delete_nonexistant_volume_id(self):
- # Should not be able to delete a non-existent Volume
- # Creating non-existent volume id
- volume_id_list = []
- resp, volumes = self.client.list_volumes()
- for i in range(len(volumes)):
- volume_id_list.append(volumes[i]['id'])
- while True:
- non_exist_id = '12345678-abcd-4321-abcd-123456789098'
- if non_exist_id not in volume_id_list:
- break
- # Try to delete a non-existent volume
+ # Should not be able to delete a non-existant Volume
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
- non_exist_id)
+ str(uuid.uuid4()))
@attr(type='gate')
def test_create_volume_with_invalid_size(self):
@@ -109,6 +98,26 @@
# Should not be able to delete volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
+ @attr(type=['negative', 'gate'])
+ def test_attach_volumes_with_nonexistent_volume_id(self):
+ srv_name = rand_name('Instance-')
+ resp, server = self.servers_client.create_server(srv_name,
+ self.image_ref,
+ self.flavor_ref)
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ self.assertRaises(exceptions.NotFound,
+ self.client.attach_volume,
+ str(uuid.uuid4()),
+ server['id'],
+ self.mountpoint)
+
+ @attr(type=['negative', 'gate'])
+ def test_detach_volumes_with_invalid_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.detach_volume,
+ 'xxx')
+
class VolumesNegativeTestXML(VolumesNegativeTest):
_interface = 'xml'
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index d6b4466..93dec71 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -14,7 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import keystoneclient.v2_0.client
+import netaddr
+
+import keystoneclient.v2_0.client as keystoneclient
+import neutronclient.v2_0.client as neutronclient
from tempest import clients
from tempest.common.utils.data_utils import rand_name
@@ -30,50 +33,64 @@
def __init__(self, name, tempest_client=True, interface='json',
password='pass'):
self.isolated_creds = {}
+ self.isolated_net_resources = {}
+ self.ports = []
self.name = name
self.config = config.TempestConfig()
self.tempest_client = tempest_client
self.interface = interface
self.password = password
- self.admin_client = self._get_identity_admin_client()
+ self.identity_admin_client, self.network_admin_client = (
+ self._get_admin_clients())
- def _get_keystone_client(self):
+ def _get_official_admin_clients(self):
username = self.config.identity.admin_username
password = self.config.identity.admin_password
tenant_name = self.config.identity.admin_tenant_name
auth_url = self.config.identity.uri
dscv = self.config.identity.disable_ssl_certificate_validation
- return keystoneclient.v2_0.client.Client(username=username,
- password=password,
- tenant_name=tenant_name,
- auth_url=auth_url,
- insecure=dscv)
+ identity_client = keystoneclient.Client(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ auth_url=auth_url,
+ insecure=dscv)
+ network_client = neutronclient.Client(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ auth_url=auth_url,
+ insecure=dscv)
+ return identity_client, network_client
- def _get_identity_admin_client(self):
+ def _get_admin_clients(self):
"""
- Returns an instance of the Identity Admin API client
+ Returns a tuple with instances of the following admin clients (in this
+ order):
+ identity
+ network
"""
if self.tempest_client:
os = clients.AdminManager(interface=self.interface)
- admin_client = os.identity_client
+ admin_clients = (os.identity_client,
+ os.network_client,)
else:
- admin_client = self._get_keystone_client()
- return admin_client
+ admin_clients = self._get_official_admin_clients()
+ return admin_clients
def _create_tenant(self, name, description):
if self.tempest_client:
- resp, tenant = self.admin_client.create_tenant(
+ resp, tenant = self.identity_admin_client.create_tenant(
name=name, description=description)
else:
- tenant = self.admin_client.tenants.create(name,
- description=description)
+ tenant = self.identity_admin_client.tenants.create(
+ name,
+ description=description)
return tenant
def _get_tenant_by_name(self, name):
if self.tempest_client:
- resp, tenant = self.admin_client.get_tenant_by_name(name)
+ resp, tenant = self.identity_admin_client.get_tenant_by_name(name)
else:
- tenants = self.admin_client.tenants.list()
+ tenants = self.identity_admin_client.tenants.list()
for ten in tenants:
if ten['name'] == name:
tenant = ten
@@ -82,45 +99,50 @@
def _create_user(self, username, password, tenant, email):
if self.tempest_client:
- resp, user = self.admin_client.create_user(username, password,
- tenant['id'], email)
+ resp, user = self.identity_admin_client.create_user(username,
+ password,
+ tenant['id'],
+ email)
else:
- user = self.admin_client.users.create(username, password, email,
- tenant_id=tenant.id)
+ user = self.identity_admin_client.users.create(username, password,
+ email,
+ tenant_id=tenant.id)
return user
def _get_user(self, tenant, username):
if self.tempest_client:
- resp, user = self.admin_client.get_user_by_username(tenant['id'],
- username)
+ resp, user = self.identity_admin_client.get_user_by_username(
+ tenant['id'],
+ username)
else:
- user = self.admin_client.users.get(username)
+ user = self.identity_admin_client.users.get(username)
return user
def _list_roles(self):
if self.tempest_client:
- resp, roles = self.admin_client.list_roles()
+ resp, roles = self.identity_admin_client.list_roles()
else:
- roles = self.admin_client.roles.list()
+ roles = self.identity_admin_client.roles.list()
return roles
def _assign_user_role(self, tenant, user, role):
if self.tempest_client:
- self.admin_client.assign_user_role(tenant, user, role)
+ self.identity_admin_client.assign_user_role(tenant, user, role)
else:
- self.admin_client.roles.add_user_role(user, role, tenant=tenant)
+ self.identity_admin_client.roles.add_user_role(user,
+ role, tenant=tenant)
def _delete_user(self, user):
if self.tempest_client:
- self.admin_client.delete_user(user)
+ self.identity_admin_client.delete_user(user)
else:
- self.admin_client.users.delete(user)
+ self.identity_admin_client.users.delete(user)
def _delete_tenant(self, tenant):
if self.tempest_client:
- self.admin_client.delete_tenant(tenant)
+ self.identity_admin_client.delete_tenant(tenant)
else:
- self.admin_client.tenants.delete(tenant)
+ self.identity_admin_client.tenants.delete(tenant)
def _create_creds(self, suffix=None, admin=False):
rand_name_root = rand_name(self.name)
@@ -128,7 +150,6 @@
rand_name_root += suffix
tenant_name = rand_name_root + "-tenant"
tenant_desc = tenant_name + "-desc"
- rand_name_root = rand_name(self.name)
tenant = self._create_tenant(name=tenant_name,
description=tenant_desc)
if suffix:
@@ -164,6 +185,92 @@
tenant_name = tenant.name
return username, tenant_name
+ def _get_tenant_id(self, tenant):
+ if self.tempest_client:
+ return tenant.get('id')
+ else:
+ return tenant.id
+
+ def _create_network_resources(self, tenant_id):
+ network = None
+ subnet = None
+ router = None
+ rand_name_root = rand_name(self.name)
+ network_name = rand_name_root + "-network"
+ network = self._create_network(network_name, tenant_id)
+ try:
+ subnet_name = rand_name_root + "-subnet"
+ subnet = self._create_subnet(subnet_name, tenant_id, network['id'])
+ router_name = rand_name_root + "-router"
+ router = self._create_router(router_name, tenant_id)
+ self._add_router_interface(router['id'], subnet['id'])
+ except Exception:
+ if router:
+ self._clear_isolated_router(router['id'], router['name'])
+ if subnet:
+ self._clear_isolated_subnet(subnet['id'], subnet['name'])
+ if network:
+ self._clear_isolated_network(network['id'], network['name'])
+ raise
+ return network, subnet, router
+
+ def _create_network(self, name, tenant_id):
+ if self.tempest_client:
+ resp, resp_body = self.network_admin_client.create_network(
+ name, tenant_id=tenant_id)
+ else:
+ body = {'network': {'tenant_id': tenant_id, 'name': name}}
+ resp_body = self.network_admin_client.create_network(body)
+ return resp_body['network']
+
+ def _create_subnet(self, subnet_name, tenant_id, network_id):
+ if not self.tempest_client:
+ body = {'subnet': {'name': subnet_name, 'tenant_id': tenant_id,
+ 'network_id': network_id, 'ip_version': 4}}
+ base_cidr = netaddr.IPNetwork(self.config.network.tenant_network_cidr)
+ mask_bits = self.config.network.tenant_network_mask_bits
+ for subnet_cidr in base_cidr.subnet(mask_bits):
+ try:
+ if self.tempest_client:
+ resp, resp_body = self.network_admin_client.create_subnet(
+ network_id, str(subnet_cidr), name=subnet_name,
+ tenant_id=tenant_id)
+ else:
+ body['subnet']['cidr'] = str(subnet_cidr)
+ resp_body = self.network_admin_client.create_subnet(body)
+ break
+ except exceptions.BadRequest as e:
+ if 'overlaps with another subnet' not in str(e):
+ raise
+ else:
+ e = exceptions.BuildErrorException()
+ e.message = 'Available CIDR for subnet creation could not be found'
+ raise e
+ return resp_body['subnet']
+
+ def _create_router(self, router_name, tenant_id):
+ external_net_id = dict(
+ network_id=self.config.network.public_network_id)
+ if self.tempest_client:
+ resp, resp_body = self.network_admin_client.create_router(
+ router_name,
+ external_gateway_info=external_net_id,
+ tenant_id=tenant_id)
+ else:
+ body = {'router': {'name': router_name, 'tenant_id': tenant_id,
+ 'external_gateway_info': external_net_id,
+ 'admin_state_up': True}}
+ resp_body = self.network_admin_client.create_router(body)
+ return resp_body['router']
+
+ def _add_router_interface(self, router_id, subnet_id):
+ if self.tempest_client:
+ self.network_admin_client.add_router_interface_with_subnet_id(
+ router_id, subnet_id)
+ else:
+ body = {'subnet_id': subnet_id}
+ self.network_admin_client.add_interface_router(router_id, body)
+
def get_primary_tenant(self):
return self.isolated_creds.get('primary')[1]
@@ -182,6 +289,33 @@
def get_admin_user(self):
return self.isolated_creds.get('admin')[0]
+ def get_primary_network(self):
+ return self.isolated_net_resources.get('primary')[0]
+
+ def get_primary_subnet(self):
+ return self.isolated_net_resources.get('primary')[1]
+
+ def get_primary_router(self):
+ return self.isolated_net_resources.get('primary')[2]
+
+ def get_admin_network(self):
+ return self.isolated_net_resources.get('admin')[0]
+
+ def get_admin_subnet(self):
+ return self.isolated_net_resources.get('admin')[1]
+
+ def get_admin_router(self):
+ return self.isolated_net_resources.get('admin')[2]
+
+ def get_alt_network(self):
+ return self.isolated_net_resources.get('alt')[0]
+
+ def get_alt_subnet(self):
+ return self.isolated_net_resources.get('alt')[1]
+
+ def get_alt_router(self):
+ return self.isolated_net_resources.get('alt')[2]
+
def get_primary_creds(self):
if self.isolated_creds.get('primary'):
user, tenant = self.isolated_creds['primary']
@@ -190,8 +324,15 @@
user, tenant = self._create_creds()
username, tenant_name = self._get_cred_names(user, tenant)
self.isolated_creds['primary'] = (user, tenant)
- LOG.info("Aquired isolated creds:\n user: %s, tenant: %s"
+ LOG.info("Acquired isolated creds:\n user: %s, tenant: %s"
% (username, tenant_name))
+ if self.config.service_available.neutron:
+ network, subnet, router = self._create_network_resources(
+ self._get_tenant_id(tenant))
+ self.isolated_net_resources['primary'] = (
+ network, subnet, router,)
+ LOG.info("Created isolated network resources for : \n"
+ + " user: %s, tenant: %s" % (username, tenant_name))
return username, tenant_name, self.password
def get_admin_creds(self):
@@ -202,9 +343,16 @@
user, tenant = self._create_creds(admin=True)
username, tenant_name = self._get_cred_names(user, tenant)
self.isolated_creds['admin'] = (user, tenant)
- LOG.info("Aquired admin isolated creds:\n user: %s, tenant: %s"
+ LOG.info("Acquired admin isolated creds:\n user: %s, tenant: %s"
% (username, tenant_name))
- return username, tenant_name, self.password
+ if self.config.service_available.neutron:
+ network, subnet, router = self._create_network_resources(
+ self._get_tenant_id(tenant))
+ self.isolated_net_resources['admin'] = (
+ network, subnet, router,)
+ LOG.info("Created isolated network resources for : \n"
+ + " user: %s, tenant: %s" % (username, tenant_name))
+ return username, tenant_name, self.password
def get_alt_creds(self):
if self.isolated_creds.get('alt'):
@@ -214,13 +362,93 @@
user, tenant = self._create_creds()
username, tenant_name = self._get_cred_names(user, tenant)
self.isolated_creds['alt'] = (user, tenant)
- LOG.info("Aquired alt isolated creds:\n user: %s, tenant: %s"
+ LOG.info("Acquired alt isolated creds:\n user: %s, tenant: %s"
% (username, tenant_name))
+ if self.config.service_available.neutron:
+ network, subnet, router = self._create_network_resources(
+ self._get_tenant_id(tenant))
+ self.isolated_net_resources['alt'] = (
+ network, subnet, router,)
+ LOG.info("Created isolated network resources for : \n"
+ + " user: %s, tenant: %s" % (username, tenant_name))
return username, tenant_name, self.password
+ def _clear_isolated_router(self, router_id, router_name):
+ net_client = self.network_admin_client
+ try:
+ net_client.delete_router(router_id)
+ except exceptions.NotFound:
+ LOG.warn('router with name: %s not found for delete' %
+ router_name)
+ pass
+
+ def _clear_isolated_subnet(self, subnet_id, subnet_name):
+ net_client = self.network_admin_client
+ try:
+ net_client.delete_subnet(subnet_id)
+ except exceptions.NotFound:
+ LOG.warn('subnet with name: %s not found for delete' %
+ subnet_name)
+ pass
+
+ def _clear_isolated_network(self, network_id, network_name):
+ net_client = self.network_admin_client
+ try:
+ net_client.delete_network(network_id)
+ except exceptions.NotFound:
+ LOG.warn('network with name: %s not found for delete' %
+ network_name)
+ pass
+
+ def _cleanup_ports(self, network_id):
+ # TODO(mlavalle) This method will be removed once patch
+ # https://review.openstack.org/#/c/46563/ merges in Neutron
+ if not self.ports:
+ if self.tempest_client:
+ resp, resp_body = self.network_admin_client.list_ports()
+ else:
+ resp_body = self.network_admin_client.list_ports()
+ self.ports = resp_body['ports']
+ ports_to_delete = [
+ port for port in self.ports if port['network_id'] == network_id]
+ for port in ports_to_delete:
+ try:
+ LOG.info('Cleaning up port id %s, name %s' %
+ (port['id'], port['name']))
+ self.network_admin_client.delete_port(port['id'])
+ except exceptions.NotFound:
+ LOG.warn('Port id: %s, name %s not found for clean-up' %
+ (port['id'], port['name']))
+
+ def _clear_isolated_net_resources(self):
+ net_client = self.network_admin_client
+ for cred in self.isolated_net_resources:
+ network, subnet, router = self.isolated_net_resources.get(cred)
+ try:
+ if self.tempest_client:
+ net_client.remove_router_interface_with_subnet_id(
+ router['id'], subnet['id'])
+ else:
+ body = {'subnet_id': subnet['id']}
+ net_client.remove_interface_router(router['id'], body)
+ except exceptions.NotFound:
+ LOG.warn('router with name: %s not found for delete' %
+ router['name'])
+ pass
+ self._clear_isolated_router(router['id'], router['name'])
+ # TODO(mlavalle) This method call will be removed once patch
+ # https://review.openstack.org/#/c/46563/ merges in Neutron
+ self._cleanup_ports(network['id'])
+ self._clear_isolated_subnet(subnet['id'], subnet['name'])
+ self._clear_isolated_network(network['id'], network['name'])
+ LOG.info("Cleared isolated network resources: \n"
+ + " network: %s, subnet: %s, router: %s"
+ % (network['name'], subnet['name'], router['name']))
+
def clear_isolated_creds(self):
if not self.isolated_creds:
return
+ self._clear_isolated_net_resources()
for cred in self.isolated_creds:
user, tenant = self.isolated_creds.get(cred)
try:
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index a867a12..4b5127a 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -80,6 +80,21 @@
self.http_obj = http.ClosingHttp(
disable_ssl_certificate_validation=dscv)
+ def __str__(self):
+ STRING_LIMIT = 80
+ str_format = ("config:%s, user:%s, password:%s, "
+ "auth_url:%s, tenant_name:%s, auth_version:%s, "
+ "service:%s, base_url:%s, region:%s, "
+ "endpoint_url:%s, build_interval:%s, build_timeout:%s"
+ "\ntoken:%s..., \nheaders:%s...")
+ return str_format % (self.config, self.user, self.password,
+ self.auth_url, self.tenant_name,
+ self.auth_version, self.service,
+ self.base_url, self.region, self.endpoint_url,
+ self.build_interval, self.build_timeout,
+ str(self.token)[0:STRING_LIMIT],
+ str(self.headers)[0:STRING_LIMIT])
+
def _set_auth(self):
"""
Sets the token and base_url used in requests based on the strategy type
@@ -448,8 +463,8 @@
# NOTE(mtreinish): This is for compatibility with Glance and swift
# APIs. These are the return content types that Glance api v1
# (and occasionally swift) are using.
- TXT_ENC = ['text/plain; charset=UTF-8', 'text/html; charset=UTF-8',
- 'text/plain; charset=utf-8']
+ TXT_ENC = ['text/plain', 'text/plain; charset=UTF-8',
+ 'text/html; charset=UTF-8', 'text/plain; charset=utf-8']
XML_ENC = ['application/xml', 'application/xml; charset=UTF-8']
if ctype in JSON_ENC or ctype in XML_ENC:
diff --git a/tempest/common/tempest_fixtures.py b/tempest/common/tempest_fixtures.py
new file mode 100644
index 0000000..081b271
--- /dev/null
+++ b/tempest/common/tempest_fixtures.py
@@ -0,0 +1,30 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+
+from tempest.openstack.common import lockutils
+
+
+class LockFixture(fixtures.Fixture):
+ def __init__(self, name):
+ self.mgr = lockutils.lock(name, 'tempest-', True)
+
+ def setUp(self):
+ super(LockFixture, self).setUp()
+ self.addCleanup(self.mgr.__exit__, None, None, None)
+ self.mgr.__enter__()
diff --git a/tempest/config.py b/tempest/config.py
index 71705fb..8421c71 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -702,6 +702,7 @@
register_stress_opts(cfg.CONF)
register_scenario_opts(cfg.CONF)
register_service_available_opts(cfg.CONF)
+ register_debug_opts(cfg.CONF)
self.compute = cfg.CONF.compute
self.identity = cfg.CONF.identity
self.images = cfg.CONF.image
@@ -715,6 +716,7 @@
self.stress = cfg.CONF.stress
self.scenario = cfg.CONF.scenario
self.service_available = cfg.CONF.service_available
+ self.debug = cfg.CONF.debug
if not self.compute_admin.username:
self.compute_admin.username = self.identity.admin_username
self.compute_admin.password = self.identity.admin_password
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index f71ea46..c01de83 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -374,8 +374,10 @@
**ruleset)
self.set_resource(sg_rule.id, sg_rule)
- def create_server(self, client, name=None, image=None, flavor=None,
+ def create_server(self, client=None, name=None, image=None, flavor=None,
create_kwargs={}):
+ if client is None:
+ client = self.compute_client
if name is None:
name = rand_name('scenario-server-')
if image is None:
@@ -476,12 +478,17 @@
@classmethod
def setUpClass(cls):
super(NetworkScenarioTest, cls).setUpClass()
- cls.tenant_id = cls.manager._get_identity_client(
- cls.config.identity.username,
- cls.config.identity.password,
- cls.config.identity.tenant_name).tenant_id
+ if cls.config.compute.allow_tenant_isolation:
+ cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
+ else:
+ cls.tenant_id = cls.manager._get_identity_client(
+ cls.config.identity.username,
+ cls.config.identity.password,
+ cls.config.identity.tenant_name).tenant_id
- def _create_security_group(self, client, namestart='secgroup-smoke-'):
+ def _create_security_group(self, client=None, namestart='secgroup-smoke-'):
+ if client is None:
+ client = self.compute_client
# Create security group
sg_name = rand_name(namestart)
sg_desc = sg_name + " description"
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index ce4d1bd..752ff6f 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -87,8 +87,7 @@
def nova_boot(self):
create_kwargs = {'key_name': self.keypair.name}
- self.server = self.create_server(self.compute_client,
- image=self.image,
+ self.server = self.create_server(image=self.image,
create_kwargs=create_kwargs)
def nova_list(self):
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 2aa5de3..6cd9fe8 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -110,10 +110,6 @@
def setUpClass(cls):
super(TestNetworkBasicOps, cls).setUpClass()
cls.check_preconditions()
- cls.tenant_id = cls.manager._get_identity_client(
- cls.config.identity.username,
- cls.config.identity.password,
- cls.config.identity.tenant_name).tenant_id
# TODO(mnewby) Consider looking up entities as needed instead
# of storing them as collections on the class.
cls.keypairs = {}
@@ -168,8 +164,7 @@
name=rand_name('keypair-smoke-'))
def _create_security_groups(self):
- self.security_groups[self.tenant_id] = self._create_security_group(
- self.compute_client)
+ self.security_groups[self.tenant_id] = self._create_security_group()
def _create_networks(self):
network = self._create_network(self.tenant_id)
@@ -214,8 +209,7 @@
'key_name': keypair_name,
'security_groups': security_groups,
}
- server = self.create_server(self.compute_client, name=name,
- create_kwargs=create_kwargs)
+ server = self.create_server(name=name, create_kwargs=create_kwargs)
return server
def _create_servers(self):
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 26bc95e..853b1ba 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -48,7 +48,7 @@
@services('compute')
def test_resize_server_confirm(self):
# We create an instance for use in this test
- instance = self.create_server(self.compute_client)
+ instance = self.create_server()
instance_id = instance.id
resize_flavor = self.config.compute.flavor_ref_alt
LOG.debug("Resizing instance %s from flavor %s to flavor %s",
@@ -66,7 +66,7 @@
@services('compute')
def test_server_sequence_suspend_resume(self):
# We create an instance for use in this test
- instance = self.create_server(self.compute_client)
+ instance = self.create_server()
instance_id = instance.id
LOG.debug("Suspending instance %s. Current status: %s",
instance_id, instance.status)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 03e6e41..c32d49d 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -56,8 +56,7 @@
create_kwargs = {
'key_name': self.keypair.id
}
- instance = self.create_server(self.compute_client,
- create_kwargs=create_kwargs)
+ instance = self.create_server(create_kwargs=create_kwargs)
self.set_resource('instance', instance)
def pause_server(self):
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 8c2cc76..ba347e0 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -34,8 +34,7 @@
create_kwargs = {
'key_name': self.keypair.name
}
- return self.create_server(self.compute_client, image=image_id,
- create_kwargs=create_kwargs)
+ return self.create_server(image=image_id, create_kwargs=create_kwargs)
def _add_keypair(self):
self.keypair = self.create_keypair()
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index ab464e3..4f49d65 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -58,8 +58,7 @@
create_kwargs = {
'key_name': self.keypair.name
}
- return self.create_server(self.compute_client, image=image_id,
- create_kwargs=create_kwargs)
+ return self.create_server(image=image_id, create_kwargs=create_kwargs)
def _add_keypair(self):
self.keypair = self.create_keypair()
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 3572166..d12cd56 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -49,8 +49,7 @@
'block_device_mapping': bd_map,
'key_name': keypair.name
}
- return self.create_server(self.compute_client,
- create_kwargs=create_kwargs)
+ return self.create_server(create_kwargs=create_kwargs)
def _create_snapshot_from_volume(self, vol_id):
volume_snapshots = self.volume_client.volume_snapshots
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 769c538..8349263 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -94,3 +94,13 @@
except exceptions.NotFound:
return True
return False
+
+ def list_floating_ip_pools(self, params=None):
+ """Returns a list of all floating IP Pools."""
+ url = 'os-floating-ip-pools'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['floating_ip_pools']
diff --git a/tempest/services/compute/xml/floating_ips_client.py b/tempest/services/compute/xml/floating_ips_client.py
index 278cc88..2fffaa2 100644
--- a/tempest/services/compute/xml/floating_ips_client.py
+++ b/tempest/services/compute/xml/floating_ips_client.py
@@ -107,3 +107,13 @@
except exceptions.NotFound:
return True
return False
+
+ def list_floating_ip_pools(self, params=None):
+ """Returns a list of all floating IP Pools."""
+ url = 'os-floating-ip-pools'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index bf72bdc..9b688aa 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -193,6 +193,14 @@
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)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 369dd81..4b87b91 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -45,12 +45,9 @@
body = json.loads(body)
return resp, body
- def create_network(self, name):
- post_body = {
- 'network': {
- 'name': name,
- }
- }
+ def create_network(self, name, **kwargs):
+ post_body = {'network': kwargs}
+ post_body['network']['name'] = name
body = json.dumps(post_body)
uri = '%s/networks' % (self.uri_prefix)
resp, body = self.post(uri, headers=self.headers, body=body)
@@ -79,12 +76,11 @@
resp, body = self.delete(uri, self.headers)
return resp, body
- def create_subnet(self, net_uuid, cidr):
- post_body = dict(
- subnet=dict(
- ip_version=4,
- network_id=net_uuid,
- cidr=cidr),)
+ def create_subnet(self, net_uuid, cidr, ip_version=4, **kwargs):
+ post_body = {'subnet': kwargs}
+ post_body['subnet']['ip_version'] = ip_version
+ post_body['subnet']['network_id'] = net_uuid
+ post_body['subnet']['cidr'] = cidr
body = json.dumps(post_body)
uri = '%s/subnets' % (self.uri_prefix)
resp, body = self.post(uri, headers=self.headers, body=body)
@@ -206,16 +202,10 @@
body = json.loads(body)
return resp, body
- def create_router(self, name, **kwargs):
- post_body = {
- 'router': {
- 'name': name,
- }
- }
- post_body['router']['admin_state_up'] = kwargs.get(
- 'admin_state_up', True)
- post_body['router']['external_gateway_info'] = kwargs.get(
- 'external_gateway_info', None)
+ def create_router(self, name, admin_state_up=True, **kwargs):
+ post_body = {'router': kwargs}
+ post_body['router']['name'] = name
+ post_body['router']['admin_state_up'] = admin_state_up
body = json.dumps(post_body)
uri = '%s/routers' % (self.uri_prefix)
resp, body = self.post(uri, headers=self.headers, body=body)
@@ -598,3 +588,15 @@
health_monitor_id)
resp, body = self.delete(uri, headers=self.headers)
return resp, body
+
+ def list_extensions(self):
+ uri = '%s/extensions' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def show_extension_details(self, ext_alias):
+ uri = '%s/extensions/%s' % (self.uri_prefix, ext_alias)
+ resp, body = self.get(uri, headers=self.headers)
+ body = json.loads(body)
+ return resp, body
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a9b5512..1523ed0 100755
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -428,6 +428,19 @@
health_monitor_id)
return self.delete(uri, self.headers)
+ def list_extensions(self):
+ url = '%s/extensions' % (self.uri_prefix)
+ resp, body = self.get(url, self.headers)
+ extensions = self._parse_array(etree.fromstring(body))
+ extensions = {"extensions": extensions}
+ return resp, extensions
+
+ def show_extension_details(self, ext_alias):
+ uri = '%s/extensions/%s' % (self.uri_prefix, str(ext_alias))
+ resp, body = self.get(uri, self.headers)
+ body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ return resp, body
+
def _root_tag_fetcher_and_xml_to_json_parse(xml_returned_body):
body = ET.fromstring(xml_returned_body)
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index d338d45..2fee042 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -15,10 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import hashlib
-import hmac
-import urlparse
-
from tempest.common import http
from tempest.common.rest_client import RestClient
from tempest import exceptions
@@ -125,29 +121,20 @@
resp, body = self.copy(url, headers=headers)
return resp, body
- def get_object_using_temp_url(self, container, object_name, expires, key):
- """Retrieve object's data using temporary URL."""
-
- self._set_auth()
- method = 'GET'
- path = "%s/%s/%s" % (urlparse.urlparse(self.base_url).path, container,
- object_name)
- hmac_body = '%s\n%s\n%s' % (method, expires, path)
- sig = hmac.new(key, hmac_body, hashlib.sha1).hexdigest()
-
- url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container,
- object_name,
- sig, expires)
-
- resp, body = self.get(url)
- return resp, body
-
def create_object_segments(self, container, object_name, segment, data):
"""Creates object segments."""
url = "{0}/{1}/{2}".format(container, object_name, segment)
resp, body = self.put(url, data, self.headers)
return resp, body
+ def get_object_using_temp_url(self, url):
+ """Retrieve object's data using temp URL."""
+ return self.get(url)
+
+ def put_object_using_temp_url(self, url, data):
+ """Put data in an object using temp URL."""
+ return self.put(url, data, None)
+
class ObjectClientCustomizedHeader(RestClient):
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 7c180f6..ae86f6e 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -31,7 +31,7 @@
To test installation, do the following (from the tempest/stress directory):
- ./run_stress.py etc/server-create-destroy-test.json -d 30
+ ./run_stress.py -t etc/server-create-destroy-test.json -d 30
This sample test tries to create a few VMs and kill a few VMs.
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index e518d28..d959543 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -13,6 +13,7 @@
# limitations under the License.
import multiprocessing
+import os
import signal
import time
@@ -87,12 +88,22 @@
"""
Goes through the process list and terminates all child processes.
"""
+ log_check_interval = int(admin_manager.config.stress.log_check_interval)
for process in processes:
if process['process'].is_alive():
try:
process['process'].terminate()
except Exception:
pass
+ time.sleep(log_check_interval)
+ for process in processes:
+ if process['process'].is_alive():
+ try:
+ pid = process['process'].pid
+ LOG.warn("Process %d hangs. Send SIGKILL." % pid)
+ os.kill(pid, signal.SIGKILL)
+ except Exception:
+ pass
process['process'].join()
diff --git a/tempest/stress/stressaction.py b/tempest/stress/stressaction.py
index 45a628d..61e46fa 100644
--- a/tempest/stress/stressaction.py
+++ b/tempest/stress/stressaction.py
@@ -30,7 +30,10 @@
self.stop_on_error = stop_on_error
def _shutdown_handler(self, signal, frame):
- self.tearDown()
+ try:
+ self.tearDown()
+ except Exception:
+ self.logger.exception("Error while tearDown")
sys.exit(0)
@property
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 0999e2c..92d66ae 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -119,8 +119,7 @@
self.pip_install('setuptools')
self.pip_install('pbr')
- self.pip_install('-r', self.requirements)
- self.pip_install('-r', self.test_requirements)
+ self.pip_install('-r', self.requirements, '-r', self.test_requirements)
def post_process(self):
self.get_distro().post_process()
diff --git a/tox.ini b/tox.ini
index 1b8a0fd..abc9e42 100644
--- a/tox.ini
+++ b/tox.ini
@@ -86,7 +86,7 @@
sitepackages = True
setenv = VIRTUAL_ENV={envdir}
commands =
- python -m tempest/stress/run_stress -a -d 3600
+ python -m tempest/stress/run_stress -a -d 3600 -S
[testenv:venv]
commands = {posargs}