Merge "Using relative path for personality file in rebuild server test."
diff --git a/HACKING.rst b/HACKING.rst
index 1db1e26..d69f935 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -157,6 +157,52 @@
kwarg2=dict_of_numbers)
+Exception Handling
+------------------
+According to the ``The Zen of Python`` the
+ ``Errors should never pass silently.``
+Tempest usually runs in special environment (jenkins gate jobs), in every
+error or failure situation we should provide as much error related
+information as possible, because we usually do not have the chance to
+investigate the situation after the issue happened.
+
+In every test case the abnormal situations must be very verbosely explained,
+by the exception and the log.
+
+In most cases the very first issue is the most important information.
+
+Try to avoid using ``try`` blocks in the test cases, both the ``except``
+and ``finally`` block could replace the original exception,
+when the additional operations leads to another exception.
+
+Just letting an exception to propagate, is not bad idea in a test case,
+ at all.
+
+Try to avoid using any exception handling construct which can hide the errors
+origin.
+
+If you really need to use a ``try`` block, please ensure the original
+exception at least logged. When the exception is logged you usually need
+to ``raise`` the same or a different exception anyway.
+
+Use the ``self.assert*`` methods provided by the unit test framework
+ the signal failures early.
+
+Avoid using the ``self.fail`` alone, it's stack trace will signal
+ the ``self.fail`` line as the origin of the error.
+
+Avoid constructing complex boolean expressions for assertion.
+The ``self.assertTrue`` or ``self.assertFalse`` will just tell you the
+single boolean, and you will not know anything about the values used in
+the formula. Most other assert method can include more information.
+For example ``self.assertIn`` can include the whole set.
+
+If the test case fails you can see the related logs and the information
+carried by the exception (exception class, backtrack and exception info).
+This and the service logs are your only guide to find the root cause of flaky
+issue.
+
+
Test Skips
----------
If a test is broken because of a bug it is appropriate to skip the test until
diff --git a/README.rst b/README.rst
index 992d19b..da0f5f3 100644
--- a/README.rst
+++ b/README.rst
@@ -40,7 +40,7 @@
$> nosetests tempest
To run one single test ::
- $> nosetests -sv tempest.tests.compute.servers.test_server_actions.py:
+ $> nosetests -sv tempest.api.compute.servers.test_server_actions.py:
ServerActionsTestJSON.test_rebuild_nonexistent_server
Configuration
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 0af8e9b..702aec2 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -181,9 +181,9 @@
# This section contains configuration options used when executing tests
# against the OpenStack Network API.
-# Version of the Quantum API
+# Version of the Neutron API
api_version = v1.1
-# Catalog type of the Quantum Service
+# Catalog type of the Neutron Service
catalog_type = network
# A large private cidr block from which to allocate smaller blocks for
@@ -206,8 +206,8 @@
# for each tenant to have their own router.
public_router_id = {$PUBLIC_ROUTER_ID}
-# Whether or not quantum is expected to be available
-quantum_available = false
+# Whether or not neutron is expected to be available
+neutron_available = false
[volume]
# This section contains the configuration options used when executing tests
@@ -336,6 +336,10 @@
# ssh username for the image file
ssh_user = cirros
+# specifies how many resources to request at once. Used for large operations
+# testing."
+large_ops_number = 0
+
[cli]
# Enable cli tests
enabled = True
diff --git a/requirements.txt b/requirements.txt
index 606d7ae..06aa9f3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,8 +11,8 @@
python-glanceclient>=0.5.0
python-keystoneclient>=0.2.0
python-novaclient>=2.10.0
-python-quantumclient>=2.1
-python-cinderclient>=1.0.4,<2
+python-neutronclient>=2.2.3,<3.0.0
+python-cinderclient>=1.0.4
testresources
keyring
testrepository
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 34f96ba..2eaf3b0 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -56,8 +56,8 @@
CONF = config.TempestConfig()
- @testtools.skipIf(CONF.network.quantum_available, "This feature is not" +
- "implemented by Quantum. See bug: #1194569")
+ @testtools.skipIf(CONF.network.neutron_available, "This feature is not" +
+ "implemented by Neutron. See bug: #1194569")
@attr(type='gate')
def test_list_fixed_ip_details(self):
resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
new file mode 100644
index 0000000..a47e6c9
--- /dev/null
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -0,0 +1,76 @@
+# 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.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class HostsAdminTestJSON(base.BaseComputeAdminTest):
+
+ """
+ Tests hosts API using admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(HostsAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.hosts_client
+ cls.non_admin_client = cls.os.hosts_client
+
+ @attr(type=['positive', 'gate'])
+ def test_list_hosts(self):
+ resp, hosts = self.client.list_hosts()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hosts) >= 2)
+
+ @attr(type='positive')
+ def test_list_hosts_with_zone(self):
+ resp, hosts = self.client.list_hosts()
+ host = hosts[0]
+ zone_name = host['zone']
+ params = {'zone': zone_name}
+ resp, hosts = self.client.list_hosts(params)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hosts) >= 1)
+ self.assertTrue(host in hosts)
+
+ @attr(type='negative')
+ def test_list_hosts_with_non_existent_zone(self):
+ params = {'zone': 'xxx'}
+ resp, hosts = self.client.list_hosts(params)
+ self.assertEqual(0, len(hosts))
+ self.assertEqual(200, resp.status)
+
+ @attr(type='negative')
+ def test_list_hosts_with_a_blank_zone(self):
+ # If send the request with a blank zone, the request will be successful
+ # and it will return all the hosts list
+ params = {'zone': ''}
+ resp, hosts = self.client.list_hosts(params)
+ self.assertNotEqual(0, len(hosts))
+ self.assertEqual(200, resp.status)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_hosts_with_non_admin_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_hosts)
+
+
+class HostsAdminTestXML(HostsAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 4163245..7740cfc 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -40,7 +40,6 @@
def setUpClass(cls):
super(ImagesOneServerTestJSON, cls).setUpClass()
cls.client = cls.images_client
- cls.servers_client = cls.servers_client
try:
resp, cls.server = cls.create_server(wait_until='ACTIVE')
@@ -104,6 +103,10 @@
self.assertRaises(exceptions.NotFound,
self.alt_client.delete_image, image_id)
+ def _get_default_flavor_disk_size(self, flavor_id):
+ resp, flavor = self.flavors_client.get_flavor_details(flavor_id)
+ return flavor['disk']
+
@testtools.skipUnless(compute.CREATE_IMAGE_ENABLED,
'Environment unable to create images.')
@attr(type='smoke')
@@ -123,10 +126,15 @@
self.assertEqual(name, image['name'])
self.assertEqual('test', image['metadata']['image_type'])
- # Verify minRAM and minDisk values are the same as the original image
resp, original_image = self.client.get_image(self.image_ref)
- self.assertEqual(original_image['minRam'], image['minRam'])
- self.assertEqual(original_image['minDisk'], image['minDisk'])
+
+ # Verify minRAM is the same as the original image
+ self.assertEqual(image['minRam'], original_image['minRam'])
+
+ # Verify minDisk is the same as the original image or the flavor size
+ flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref)
+ self.assertIn(str(image['minDisk']),
+ (str(original_image['minDisk']), str(flavor_disk_size)))
# Verify the image was deleted correctly
resp, body = self.client.delete_image(image_id)
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 12c646d..e105121 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -158,8 +158,8 @@
self.client.create_security_group, s_name,
s_description)
- @testtools.skipIf(config.TempestConfig().network.quantum_available,
- "Quantum allows duplicate names for security groups")
+ @testtools.skipIf(config.TempestConfig().network.neutron_available,
+ "Neutron allows duplicate names for security groups")
@attr(type=['negative', 'gate'])
def test_security_group_create_with_duplicate_name(self):
# Negative test:Security Group with duplicate name should not
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 113ac78..de095c5 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -24,8 +24,8 @@
@classmethod
def setUpClass(cls):
- if not cls.config.network.quantum_available:
- raise cls.skipException("Quantum is required")
+ if not cls.config.network.neutron_available:
+ raise cls.skipException("Neutron is required")
super(AttachInterfacesTestJSON, cls).setUpClass()
cls.client = cls.os.interfaces_client
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 98620b1..8b76f7f 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -253,6 +253,24 @@
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')
+
@classmethod
def rebuild_servers(cls):
# Destroy any existing server and creates a new one
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5f53080..5cc8dc6 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -24,12 +24,12 @@
from tempest.test import attr
-class ServersNegativeTest(base.BaseComputeTest):
+class ServersNegativeTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ServersNegativeTest, cls).setUpClass()
+ super(ServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.img_client = cls.images_client
cls.alt_os = clients.AltManager()
@@ -248,5 +248,5 @@
'999erra43')
-class ServersNegativeTestXML(ServersNegativeTest):
+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 9073aeb..35f0fc0 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -37,8 +37,8 @@
resp, server = cls.create_server(wait_until='ACTIVE')
cls.server_id = server['id']
- @testtools.skipIf(CONF.network.quantum_available, "This feature is not " +
- "implemented by Quantum. See bug: #1183436")
+ @testtools.skipIf(CONF.network.neutron_available, "This feature is not " +
+ "implemented by Neutron. See bug: #1183436")
@attr(type='gate')
def test_list_virtual_interfaces(self):
# Positive test:Should be able to GET the virtual interfaces list
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index c029300..0bba250 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
from testtools.matchers._basic import Contains
from tempest.api.identity import base
@@ -77,32 +76,6 @@
self.data.test_user, self.data.test_password,
self.data.tenant['id'], self.data.test_email)
- @testtools.skip("Until Bug #999084 is fixed")
- @attr(type=['negative', 'gate'])
- def test_create_user_with_empty_password(self):
- # User with an empty password should not be created
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user,
- self.alt_user, '', self.data.tenant['id'],
- self.alt_email)
-
- @testtools.skip("Until Bug #999084 is fixed")
- @attr(type=['negative', 'gate'])
- def test_create_user_with_long_password(self):
- # User having password exceeding max length should not be created
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user,
- self.alt_user, 'a' * 65, self.data.tenant['id'],
- self.alt_email)
-
- @testtools.skip("Until Bug #999084 is fixed")
- @attr(type=['negative', 'gate'])
- def test_create_user_with_invalid_email_format(self):
- # Email format should be validated while creating a user
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user,
- self.alt_user, '', self.data.tenant['id'], '12345')
-
@attr(type=['negative', 'gate'])
def test_create_user_for_non_existant_tenant(self):
# Attempt to create a user in a non-existent tenant should fail
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 8d019fe..3d40eb3 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+
from tempest.api.identity import base
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
@@ -49,6 +50,48 @@
missing_doms = [d for d in domain_ids if d not in fetched_ids]
self.assertEqual(0, len(missing_doms))
+ @attr(type='smoke')
+ def test_create_update_delete_domain(self):
+ d_name = rand_name('domain-')
+ d_desc = rand_name('domain-desc-')
+ resp_1, domain = self.v3_client.create_domain(
+ d_name, description=d_desc)
+ self.assertEqual(resp_1['status'], '201')
+ self.addCleanup(self._delete_domain, domain['id'])
+ self.assertIn('id', domain)
+ self.assertIn('description', domain)
+ self.assertIn('name', domain)
+ self.assertIn('enabled', domain)
+ self.assertIn('links', domain)
+ self.assertIsNotNone(domain['id'])
+ self.assertEqual(d_name, domain['name'])
+ self.assertEqual(d_desc, domain['description'])
+ if self._interface == "json":
+ self.assertEqual(True, domain['enabled'])
+ else:
+ self.assertEqual('true', str(domain['enabled']).lower())
+ new_desc = rand_name('new-desc-')
+ new_name = rand_name('new-name-')
+
+ resp_2, updated_domain = self.v3_client.update_domain(
+ domain['id'], name=new_name, description=new_desc)
+ self.assertEqual(resp_2['status'], '200')
+ self.assertIn('id', updated_domain)
+ self.assertIn('description', updated_domain)
+ self.assertIn('name', updated_domain)
+ self.assertIn('enabled', updated_domain)
+ self.assertIn('links', updated_domain)
+ self.assertIsNotNone(updated_domain['id'])
+ self.assertEqual(new_name, updated_domain['name'])
+ self.assertEqual(new_desc, updated_domain['description'])
+ self.assertEqual('true', str(updated_domain['enabled']).lower())
+
+ resp_3, fetched_domain = self.v3_client.get_domain(domain['id'])
+ self.assertEqual(resp_3['status'], '200')
+ self.assertEqual(new_name, fetched_domain['name'])
+ self.assertEqual(new_desc, fetched_domain['description'])
+ self.assertEqual('true', str(fetched_domain['enabled']).lower())
+
class DomainsTestXML(DomainsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
new file mode 100644
index 0000000..2a20493
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# 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.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class UsersTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @attr(type='smoke')
+ def test_tokens(self):
+ # Valid user's token is authenticated
+ # Create a User
+ u_name = rand_name('user-')
+ u_desc = '%s-description' % u_name
+ u_email = '%s@testmail.tm' % u_name
+ u_password = rand_name('pass-')
+ resp, user = self.v3_client.create_user(
+ u_name, description=u_desc, password=u_password,
+ email=u_email)
+ self.assertTrue(resp['status'].startswith('2'))
+ self.addCleanup(self.v3_client.delete_user, user['id'])
+ # Perform Authentication
+ resp, body = self.v3_token.auth(user['id'], u_password)
+ self.assertEqual(resp['status'], '201')
+ subject_token = resp['x-subject-token']
+ # Perform GET Token
+ resp, token_details = self.v3_client.get_token(subject_token)
+ self.assertEqual(resp['status'], '200')
+ self.assertEqual(resp['x-subject-token'], subject_token)
+ self.assertEqual(token_details['user']['id'], user['id'])
+ self.assertEqual(token_details['user']['name'], u_name)
+ # Perform Delete Token
+ resp, _ = self.v3_client.delete_token(subject_token)
+ self.assertRaises(exceptions.Unauthorized, self.v3_client.get_token,
+ subject_token)
+
+
+class UsersTestXML(UsersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index db55509..1237ce4 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -32,6 +32,7 @@
cls.v3_client = os.identity_v3_client
cls.service_client = os.service_client
cls.policy_client = os.policy_client
+ cls.v3_token = os.token_v3_client
if not cls.client.has_admin_extensions():
raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 03e73df..3b7f9dd 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -26,11 +26,11 @@
class BaseNetworkTest(tempest.test.BaseTestCase):
"""
- Base class for the Quantum tests that use the Tempest Quantum REST client
+ Base class for the Neutron tests that use the Tempest Neutron REST client
- Per the Quantum API Guide, API v1.x was removed from the source code tree
+ Per the Neutron API Guide, API v1.x was removed from the source code tree
(docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
- Therefore, v2.x of the Quantum API is assumed. It is also assumed that the
+ Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
following options are defined in the [network] section of etc/tempest.conf:
tenant_network_cidr with a block of cidr's from which smaller blocks
@@ -44,8 +44,8 @@
def setUpClass(cls):
os = clients.Manager()
cls.network_cfg = os.config.network
- if not cls.network_cfg.quantum_available:
- raise cls.skipException("Quantum support is required")
+ if not cls.network_cfg.neutron_available:
+ raise cls.skipException("Neutron support is required")
cls.client = os.network_client
cls.networks = []
cls.subnets = []
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index 22eb1d3..c3fb821 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -32,7 +32,7 @@
class DeletableResource(AttributeDict):
"""
- Support deletion of quantum resources (networks, subnets) via a
+ Support deletion of neutron resources (networks, subnets) via a
delete() method, as is supported by keystone and nova resources.
"""
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 1f45f92..4481853 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -26,8 +26,8 @@
class NetworksTest(base.BaseNetworkTest):
"""
- Tests the following operations in the Quantum API using the REST client for
- Quantum:
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
create a network for a tenant
list tenant's networks
@@ -36,7 +36,7 @@
list tenant's subnets
show a tenant subnet details
- v2.0 of the Quantum API is assumed. It is also assumed that the following
+ 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:
tenant_network_cidr with a block of cidr's from which smaller blocks
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 2f52a65..6136216 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -340,18 +340,19 @@
def test_object_upload_in_segments(self):
# create object
object_name = rand_name(name='LObject')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
+ data = arbitrary_string()
segments = 10
- self.object_client.create_object(self.container_name,
- object_name, data)
- # uploading 10 segments
- for i in range(segments):
+ data_segments = [data + str(i) for i in xrange(segments)]
+ # uploading segments
+ for i in xrange(segments):
resp, _ = self.object_client.create_object_segments(
- self.container_name, object_name, i, data)
- # creating a manifest file (metadata update)
+ self.container_name, object_name, i, data_segments[i])
+ self.assertEqual(resp['status'], '201')
+ # creating a manifest file
metadata = {'X-Object-Manifest': '%s/%s/'
% (self.container_name, object_name)}
+ self.object_client.create_object(self.container_name,
+ object_name, data='')
resp, _ = self.object_client.update_object_metadata(
self.container_name, object_name, metadata, metadata_prefix='')
resp, _ = self.object_client.list_object_metadata(
@@ -363,7 +364,7 @@
# downloading the object
resp, body = self.object_client.get_object(
self.container_name, object_name)
- self.assertEqual(data * segments, body)
+ self.assertEqual(''.join(data_segments), body)
@attr(type='gate')
def test_get_object_if_different(self):
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 0e50ac3..ffa534a 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
+from tempest.common import log as logging
import time
from tempest import clients
@@ -62,15 +62,15 @@
cls.config.identity.uri
)
- def create_stack(self, stack_name, template_data, parameters={}):
- resp, body = self.client.create_stack(
+ @classmethod
+ def create_stack(cls, stack_name, template_data, parameters={}):
+ resp, body = cls.client.create_stack(
stack_name,
template=template_data,
parameters=parameters)
- self.assertEqual('201', resp['status'])
stack_id = resp['location'].split('/')[-1]
stack_identifier = '%s/%s' % (stack_name, stack_id)
- self.stacks.append(stack_identifier)
+ cls.stacks.append(stack_identifier)
return stack_identifier
@classmethod
@@ -88,11 +88,11 @@
except Exception:
pass
- def _create_keypair(self, namestart='keypair-heat-'):
+ @classmethod
+ def _create_keypair(cls, namestart='keypair-heat-'):
kp_name = rand_name(namestart)
- resp, body = self.keypairs_client.create_keypair(kp_name)
- self.assertEqual(body['name'], kp_name)
- self.keypairs.append(kp_name)
+ resp, body = cls.keypairs_client.create_keypair(kp_name)
+ cls.keypairs.append(kp_name)
return body
@classmethod
diff --git a/tempest/api/orchestration/stacks/test_instance_cfn_init.py b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
index e3b8162..4f22158 100644
--- a/tempest/api/orchestration/stacks/test_instance_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
@@ -13,7 +13,7 @@
# under the License.
import json
-import logging
+from tempest.common import log as logging
import testtools
from tempest.api.orchestration import base
@@ -46,6 +46,13 @@
Resources:
CfnUser:
Type: AWS::IAM::User
+ SmokeSecurityGroup:
+ Type: AWS::EC2::SecurityGroup
+ Properties:
+ GroupDescription: Enable only ping and SSH access
+ SecurityGroupIngress:
+ - {CidrIp: 0.0.0.0/0, FromPort: '-1', IpProtocol: icmp, ToPort: '-1'}
+ - {CidrIp: 0.0.0.0/0, FromPort: '22', IpProtocol: tcp, ToPort: '22'}
SmokeKeys:
Type: AWS::IAM::AccessKey
Properties:
@@ -79,6 +86,8 @@
ImageId: {Ref: ImageId}
InstanceType: {Ref: InstanceType}
KeyName: {Ref: KeyName}
+ SecurityGroups:
+ - {Ref: SmokeSecurityGroup}
UserData:
Fn::Base64:
Fn::Join:
@@ -119,23 +128,21 @@
raise cls.skipException("No image available to test")
cls.client = cls.orchestration_client
- def setUp(self):
- super(InstanceCfnInitTestJSON, self).setUp()
stack_name = rand_name('heat')
- if self.orchestration_cfg.keypair_name:
- keypair_name = self.orchestration_cfg.keypair_name
+ if cls.orchestration_cfg.keypair_name:
+ keypair_name = cls.orchestration_cfg.keypair_name
else:
- self.keypair = self._create_keypair()
- keypair_name = self.keypair['name']
+ cls.keypair = cls._create_keypair()
+ keypair_name = cls.keypair['name']
# create the stack
- self.stack_identifier = self.create_stack(
+ cls.stack_identifier = cls.create_stack(
stack_name,
- self.template,
+ cls.template,
parameters={
'KeyName': keypair_name,
- 'InstanceType': self.orchestration_cfg.instance_type,
- 'ImageId': self.orchestration_cfg.image_ref
+ 'InstanceType': cls.orchestration_cfg.instance_type,
+ 'ImageId': cls.orchestration_cfg.image_ref
})
@attr(type='gate')
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 5fed581..15979ed 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
+from tempest.common import log as logging
from tempest.api.orchestration import base
from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index cd5ab34..56a3006 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -27,7 +27,7 @@
def setUpClass(cls):
super(VolumesActionsTest, cls).setUpClass()
cls.client = cls.volumes_client
- cls.servers_client = cls.servers_client
+ cls.image_client = cls.os.image_client
# Create a test shared instance and volume for attach/detach tests
srv_name = rand_name('Instance-')
@@ -93,3 +93,16 @@
finally:
self.client.detach_volume(self.volume['id'])
self.client.wait_for_volume_status(self.volume['id'], 'available')
+
+ @attr(type='gate')
+ def test_volume_upload(self):
+ # NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
+ # it is shared with the other tests. After it is uploaded in Glance,
+ # there is no way to delete it from Cinder, so we delete it from Glance
+ # using the Glance image_client and from Cinder via tearDownClass.
+ image_name = rand_name('Image-')
+ resp, body = self.client.upload_volume(self.volume['id'], image_name)
+ image_id = body["image_id"]
+ self.addCleanup(self.image_client.delete_image, image_id)
+ self.assertEqual(202, resp.status)
+ self.image_client.wait_for_image_status(image_id, 'active')
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 5bbedfd..0e1d6db 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import os
import shlex
import subprocess
@@ -23,6 +22,7 @@
from oslo.config import cfg
import tempest.cli.output_parser
+from tempest.common import log as logging
import tempest.test
@@ -77,6 +77,11 @@
return self.cmd_with_auth(
'glance', action, flags, params, admin, fail_ok)
+ def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
+ """Executes cinder command for the given action."""
+ return self.cmd_with_auth(
+ 'cinder', action, flags, params, admin, fail_ok)
+
def cmd_with_auth(self, cmd, action, flags='', params='',
admin=True, fail_ok=False):
"""Executes given command with auth attributes appended."""
diff --git a/tempest/cli/output_parser.py b/tempest/cli/output_parser.py
index 840839b..3ee3098 100644
--- a/tempest/cli/output_parser.py
+++ b/tempest/cli/output_parser.py
@@ -18,7 +18,8 @@
"""Collection of utilities for parsing CLI clients output."""
-import logging
+from tempest.common import log as logging
+
import re
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
new file mode 100644
index 0000000..e9ce87b
--- /dev/null
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -0,0 +1,117 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import re
+import subprocess
+
+import tempest.cli
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyCinderClientTest(tempest.cli.ClientTestBase):
+ """Basic, read-only tests for Cinder CLI client.
+
+ Checks return values and output of read-only commands.
+ These tests do not presume any content, nor do they create
+ their own. They only verify the structure of output if present.
+ """
+
+ def test_cinder_fake_action(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ self.cinder,
+ 'this-does-not-exist')
+
+ def test_cinder_absolute_limit_list(self):
+ roles = self.parser.listing(self.cinder('absolute-limits'))
+ self.assertTableStruct(roles, ['Name', 'Value'])
+
+ def test_cinder_backup_list(self):
+ self.cinder('backup-list')
+
+ def test_cinder_extra_specs_list(self):
+ self.cinder('extra-specs-list')
+
+ def test_cinder_volumes_list(self):
+ self.cinder('list')
+
+ def test_cinder_quota_class_show(self):
+ """This CLI can accept and string as param."""
+ roles = self.parser.listing(self.cinder('quota-class-show',
+ params='abc'))
+ self.assertTableStruct(roles, ['Property', 'Value'])
+
+ def test_cinder_quota_defaults(self):
+ """This CLI can accept and string as param."""
+ roles = self.parser.listing(self.cinder('quota-defaults',
+ params=self.identity.
+ admin_tenant_name))
+ self.assertTableStruct(roles, ['Property', 'Value'])
+
+ def test_cinder_quota_show(self):
+ """This CLI can accept and string as param."""
+ roles = self.parser.listing(self.cinder('quota-show',
+ params=self.identity.
+ admin_tenant_name))
+ self.assertTableStruct(roles, ['Property', 'Value'])
+
+ def test_cinder_rate_limits(self):
+ self.cinder('rate-limits')
+
+ def test_cinder_snapshot_list(self):
+ self.cinder('snapshot-list')
+
+ def test_cinder_type_list(self):
+ self.cinder('type-list')
+
+ def test_cinder_list_extensions(self):
+ self.cinder('list-extensions')
+ roles = self.parser.listing(self.cinder('list-extensions'))
+ self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated'])
+
+ def test_admin_help(self):
+ help_text = self.cinder('help')
+ lines = help_text.split('\n')
+ self.assertTrue(lines[0].startswith('usage: cinder'))
+
+ commands = []
+ cmds_start = lines.index('Positional arguments:')
+ cmds_end = lines.index('Optional arguments:')
+ command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)')
+ for line in lines[cmds_start:cmds_end]:
+ match = command_pattern.match(line)
+ if match:
+ commands.append(match.group(1))
+ commands = set(commands)
+ wanted_commands = set(('absolute-limits', 'list', 'help',
+ 'quota-show', 'type-list', 'snapshot-list'))
+ self.assertFalse(wanted_commands - commands)
+
+ # Optional arguments:
+
+ def test_cinder_version(self):
+ self.cinder('', flags='--version')
+
+ def test_cinder_debug_list(self):
+ self.cinder('list', flags='--debug')
+
+ def test_cinder_retries_list(self):
+ self.cinder('list', flags='--retries 3')
+
+ def test_cinder_region_list(self):
+ self.cinder('list', flags='--os-region-name ' + self.identity.region)
diff --git a/tempest/cli/simple_read_only/test_compute.py b/tempest/cli/simple_read_only/test_compute.py
index fa64561..561fd00 100644
--- a/tempest/cli/simple_read_only/test_compute.py
+++ b/tempest/cli/simple_read_only/test_compute.py
@@ -15,13 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import subprocess
from oslo.config import cfg
import testtools
import tempest.cli
+from tempest.common import log as logging
CONF = cfg.CONF
diff --git a/tempest/cli/simple_read_only/test_compute_manage.py b/tempest/cli/simple_read_only/test_compute_manage.py
index a788c8b..802a206 100644
--- a/tempest/cli/simple_read_only/test_compute_manage.py
+++ b/tempest/cli/simple_read_only/test_compute_manage.py
@@ -15,10 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import subprocess
import tempest.cli
+from tempest.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index b3b3eb7..fa77e8a 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import re
import subprocess
import tempest.cli
+from tempest.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 45d519b..3bc8b3e 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import re
import subprocess
import tempest.cli
+from tempest.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/tempest/clients.py b/tempest/clients.py
index a5c7b4d..5efce98 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -75,12 +75,14 @@
EndPointClientJSON
from tempest.services.identity.v3.json.identity_client import \
IdentityV3ClientJSON
+from tempest.services.identity.v3.json.identity_client import V3TokenClientJSON
from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
from tempest.services.identity.v3.json.service_client import \
ServiceClientJSON
from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
from tempest.services.identity.v3.xml.identity_client import \
IdentityV3ClientXML
+from tempest.services.identity.v3.xml.identity_client import V3TokenClientXML
from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
from tempest.services.identity.v3.xml.service_client import \
ServiceClientXML
@@ -239,6 +241,11 @@
"xml": HypervisorClientXML,
}
+V3_TOKEN_CLIENT = {
+ "json": V3TokenClientJSON,
+ "xml": V3TokenClientXML,
+}
+
class Manager(object):
@@ -319,6 +326,7 @@
TENANT_USAGES_CLIENT[interface](*client_args)
self.policy_client = POLICY_CLIENT[interface](*client_args)
self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args)
+ self.token_v3_client = V3_TOKEN_CLIENT[interface](*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
@@ -399,5 +407,5 @@
base = super(OrchestrationManager, self)
base.__init__(conf.identity.admin_username,
conf.identity.admin_password,
- conf.identity.admin_tenant_name,
+ conf.identity.tenant_name,
interface=interface)
diff --git a/tempest/config.py b/tempest/config.py
index 8795b33..a831070 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,9 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+from __future__ import print_function
+
import os
import sys
+
from oslo.config import cfg
from tempest.common import log as logging
@@ -277,7 +280,7 @@
NetworkGroup = [
cfg.StrOpt('catalog_type',
default='network',
- help='Catalog type of the Quantum service.'),
+ help='Catalog type of the Neutron service.'),
cfg.StrOpt('tenant_network_cidr',
default="10.100.0.0/16",
help="The cidr block to allocate tenant networks from"),
@@ -296,9 +299,9 @@
default="",
help="Id of the public router that provides external "
"connectivity"),
- cfg.BoolOpt('quantum_available',
+ cfg.BoolOpt('neutron_available',
default=False,
- help="Whether or not quantum is expected to be available"),
+ help="Whether or not neutron is expected to be available"),
]
@@ -517,7 +520,12 @@
help='AKI image file name'),
cfg.StrOpt('ssh_user',
default='cirros',
- help='ssh username for the image file')
+ help='ssh username for the image file'),
+ cfg.IntOpt(
+ 'large_ops_number',
+ default=0,
+ help="specifies how many resources to request at once. Used "
+ "for large operations testing.")
]
@@ -559,7 +567,7 @@
if not os.path.exists(path):
msg = "Config file %(path)s not found" % locals()
- print >> sys.stderr, RuntimeError(msg)
+ print(RuntimeError(msg), file=sys.stderr)
else:
config_files.append(path)
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 5e941da..f9eb968 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -17,7 +17,7 @@
import re
-PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'quantum']
+PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron']
SKIP_DECORATOR_RE = re.compile(r'\s*@testtools.skip\((.*)\)')
SKIP_STR_RE = re.compile(r'.*Bug #\d+.*')
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 366ff43..fe6fbf5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -16,7 +16,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import subprocess
# Default client libs
@@ -24,12 +23,13 @@
import glanceclient
import keystoneclient.v2_0.client
import netaddr
+from neutronclient.common import exceptions as exc
+import neutronclient.v2_0.client
import novaclient.client
-from quantumclient.common import exceptions as exc
-import quantumclient.v2_0.client
from tempest.api.network import common as net_common
+from tempest.common import log as logging
from tempest.common import ssh
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
@@ -166,7 +166,7 @@
auth_url = self.config.identity.uri
dscv = self.config.identity.disable_ssl_certificate_validation
- return quantumclient.v2_0.client.Client(username=username,
+ return neutronclient.v2_0.client.Client(username=username,
password=password,
tenant_name=tenant_name,
auth_url=auth_url,
@@ -236,9 +236,9 @@
@classmethod
def check_preconditions(cls):
- if (cls.config.network.quantum_available):
+ if (cls.config.network.neutron_available):
cls.enabled = True
- #verify that quantum_available is telling the truth
+ #verify that neutron_available is telling the truth
try:
cls.network_client.list_networks()
except exc.EndpointNotFound:
@@ -246,7 +246,7 @@
raise
else:
cls.enabled = False
- msg = 'Quantum not available'
+ msg = 'Neutron not available'
raise cls.skipException(msg)
@classmethod
@@ -358,7 +358,7 @@
try:
result = self.network_client.create_subnet(body=body)
break
- except exc.QuantumClientException as e:
+ except exc.NeutronClientException as e:
is_overlapping_cidr = 'overlaps with another subnet' in str(e)
if not is_overlapping_cidr:
raise
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
new file mode 100644
index 0000000..1f75e2f
--- /dev/null
+++ b/tempest/scenario/test_large_ops.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.common import log as logging
+from tempest.common.utils.data_utils import rand_name
+from tempest.scenario import manager
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestLargeOpsScenario(manager.OfficialClientTest):
+
+ """
+ Test large operations.
+
+ This test below:
+ * Spin up multiple instances in one nova call
+ * as a regular user
+ * TODO: same thing for cinder
+
+ """
+
+ def _wait_for_server_status(self, status):
+ for server in self.servers:
+ self.status_timeout(
+ self.compute_client.servers, server.id, status)
+
+ def _wait_for_volume_status(self, status):
+ volume_id = self.volume.id
+ self.status_timeout(
+ self.volume_client.volumes, volume_id, status)
+
+ def _image_create(self, name, fmt, path, properties={}):
+ name = rand_name('%s-' % name)
+ image_file = open(path, 'rb')
+ self.addCleanup(image_file.close)
+ params = {
+ 'name': name,
+ 'container_format': fmt,
+ 'disk_format': fmt,
+ 'is_public': 'True',
+ }
+ params.update(properties)
+ image = self.image_client.images.create(**params)
+ self.addCleanup(self.image_client.images.delete, image)
+ self.assertEqual("queued", image.status)
+ image.update(data=image_file)
+ return image.id
+
+ def glance_image_create(self):
+ aki_img_path = self.config.scenario.img_dir + "/" + \
+ self.config.scenario.aki_img_file
+ ari_img_path = self.config.scenario.img_dir + "/" + \
+ self.config.scenario.ari_img_file
+ ami_img_path = self.config.scenario.img_dir + "/" + \
+ self.config.scenario.ami_img_file
+ LOG.debug("paths: ami: %s, ari: %s, aki: %s"
+ % (ami_img_path, ari_img_path, aki_img_path))
+ kernel_id = self._image_create('scenario-aki', 'aki', aki_img_path)
+ ramdisk_id = self._image_create('scenario-ari', 'ari', ari_img_path)
+ properties = {
+ 'properties': {'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id}
+ }
+ self.image = self._image_create('scenario-ami', 'ami',
+ path=ami_img_path,
+ properties=properties)
+
+ def nova_boot(self):
+ def delete(servers):
+ [x.delete() for x in servers]
+
+ name = rand_name('scenario-server-')
+ client = self.compute_client
+ flavor_id = self.config.compute.flavor_ref
+ self.servers = client.servers.create(
+ name=name, image=self.image,
+ flavor=flavor_id,
+ min_count=self.config.scenario.large_ops_number)
+ # needed because of bug 1199788
+ self.servers = [x for x in client.servers.list() if name in x.name]
+ self.addCleanup(delete, self.servers)
+ self._wait_for_server_status('ACTIVE')
+
+ def test_large_ops_scenario(self):
+ if self.config.scenario.large_ops_number < 1:
+ return
+ self.glance_image_create()
+ self.nova_boot()
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index a55bbb2..2097f50 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index b94caaa..390e004 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -26,7 +26,7 @@
"""
This smoke test suite assumes that Nova has been configured to
- boot VM's with Quantum-managed networking, and attempts to
+ boot VM's with Neutron-managed networking, and attempts to
verify network connectivity as follows:
* For a freshly-booted VM with an IP address ("port") on a given network:
diff --git a/tempest/scenario/test_network_quotas.py b/tempest/scenario/test_network_quotas.py
index 8c3af73..267aff6 100644
--- a/tempest/scenario/test_network_quotas.py
+++ b/tempest/scenario/test_network_quotas.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from quantumclient.common import exceptions as exc
+from neutronclient.common import exceptions as exc
from tempest.scenario.manager import NetworkScenarioTest
MAX_REASONABLE_ITERATIONS = 51 # more than enough. Default for port is 50.
@@ -48,7 +48,7 @@
self.networks.append(
self._create_network(self.tenant_id,
namestart='network-quotatest-'))
- except exc.QuantumClientException as e:
+ except exc.NeutronClientException as e:
if (e.status_code != 409):
raise
hit_limit = True
@@ -66,7 +66,7 @@
self.subnets.append(
self._create_subnet(self.networks[0],
namestart='subnet-quotatest-'))
- except exc.QuantumClientException as e:
+ except exc.NeutronClientException as e:
if (e.status_code != 409):
raise
hit_limit = True
@@ -84,7 +84,7 @@
self.ports.append(
self._create_port(self.networks[0],
namestart='port-quotatest-'))
- except exc.QuantumClientException as e:
+ except exc.NeutronClientException as e:
if (e.status_code != 409):
raise
hit_limit = True
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 7725421..f21a00b 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index dc3c524..8093d19 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -1,4 +1,21 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
import json
+import urllib
from tempest.common.rest_client import RestClient
@@ -10,10 +27,13 @@
auth_url, tenant_name)
self.service = self.config.compute.catalog_type
- def list_hosts(self):
+ def list_hosts(self, params=None):
"""Lists all hosts."""
url = 'os-hosts'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
resp, body = self.get(url)
body = json.loads(body)
return resp, body['hosts']
diff --git a/tempest/services/compute/xml/hosts_client.py b/tempest/services/compute/xml/hosts_client.py
new file mode 100644
index 0000000..70aeb48
--- /dev/null
+++ b/tempest/services/compute/xml/hosts_client.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+from lxml import etree
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class HostsClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(HostsClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_hosts(self, params=None):
+ """Lists all hosts."""
+
+ url = 'os-hosts'
+ 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
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 1ec4df0..f2cca72 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -163,6 +163,22 @@
server = self._parse_server(etree.fromstring(body))
return resp, server
+ 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 delete_server(self, server_id):
"""Deletes the given server."""
return self.delete("servers/%s" % str(server_id))
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index adbdc83..56a1a72 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -208,3 +208,61 @@
resp, body = self.get('domains/%s' % domain_id)
body = json.loads(body)
return resp, body['domain']
+
+ def get_token(self, resp_token):
+ """Get token details."""
+ headers = {'X-Subject-Token': resp_token}
+ resp, body = self.get("auth/tokens", headers=headers)
+ body = json.loads(body)
+ return resp, body['token']
+
+ def delete_token(self, resp_token):
+ """Deletes token."""
+ headers = {'X-Subject-Token': resp_token}
+ resp, body = self.delete("auth/tokens", headers=headers)
+ return resp, body
+
+
+class V3TokenClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(V3TokenClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ auth_url = config.identity.uri
+
+ if 'tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/tokens'
+
+ self.auth_url = auth_url
+ self.config = config
+
+ def auth(self, user_id, password):
+ creds = {
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'id': user_id,
+ 'password': password
+ }
+ }
+ }
+ }
+ }
+ headers = {'Content-Type': 'application/json'}
+ body = json.dumps(creds)
+ resp, body = self.post("auth/tokens", headers=headers, body=body)
+ return resp, body
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(V3TokenClientJSON, self).request(method, url,
+ headers=headers,
+ body=body)
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 708ee28..571b491 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -22,9 +22,9 @@
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
-
XMLNS = "http://docs.openstack.org/identity/api/v3"
@@ -241,3 +241,65 @@
resp, body = self.get('domains/%s' % domain_id, self.headers)
body = self._parse_body(etree.fromstring(body))
return resp, body
+
+ def get_token(self, resp_token):
+ """GET a Token Details."""
+ headers = {'Content-Type': 'application/xml',
+ 'Accept': 'application/xml',
+ 'X-Subject-Token': resp_token}
+ resp, body = self.get("auth/tokens", headers=headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_token(self, resp_token):
+ """Delete a Given Token."""
+ headers = {'X-Subject-Token': resp_token}
+ resp, body = self.delete("auth/tokens", headers=headers)
+ return resp, body
+
+
+class V3TokenClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(V3TokenClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ auth_url = config.identity.uri
+
+ if 'tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/tokens'
+
+ self.auth_url = auth_url
+ self.config = config
+
+ def auth(self, user_id, password):
+ user = Element('user',
+ id=user_id,
+ password=password)
+ password = Element('password')
+ password.append(user)
+
+ method = Element('method')
+ method.append(Text('password'))
+ methods = Element('methods')
+ methods.append(method)
+ identity = Element('identity')
+ identity.append(methods)
+ identity.append(password)
+ auth = Element('auth')
+ auth.append(identity)
+ headers = {'Content-Type': 'application/xml'}
+ resp, body = self.post("auth/tokens", headers=headers,
+ body=str(Document(auth)))
+ return resp, body
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(V3TokenClientXML, self).request(method, url,
+ headers=headers,
+ body=body)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 4758ddd..c4fe6b1 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -5,10 +5,10 @@
class NetworkClient(RestClient):
"""
- Tempest REST client for Quantum. Uses v2 of the Quantum API, since the
+ Tempest REST client for Neutron. Uses v2 of the Neutron API, since the
V1 API has been removed from the code base.
- Implements the following operations for each one of the basic Quantum
+ Implements the following operations for each one of the basic Neutron
abstractions (networks, sub-networks and ports):
create
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 6b0e7e3..22f3f26 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -46,6 +46,35 @@
def create_stack(self, name, disable_rollback=True, parameters={},
timeout_mins=60, template=None, template_url=None):
+ headers, body = self._prepare_update_create(
+ name,
+ disable_rollback,
+ parameters,
+ timeout_mins,
+ template,
+ template_url)
+ uri = 'stacks'
+ resp, body = self.post(uri, headers=headers, body=body)
+ return resp, body
+
+ def update_stack(self, stack_identifier, name, disable_rollback=True,
+ parameters={}, timeout_mins=60, template=None,
+ template_url=None):
+ headers, body = self._prepare_update_create(
+ name,
+ disable_rollback,
+ parameters,
+ timeout_mins,
+ template,
+ template_url)
+
+ uri = "stacks/%s" % stack_identifier
+ resp, body = self.put(uri, headers=headers, body=body)
+ return resp, body
+
+ def _prepare_update_create(self, name, disable_rollback=True,
+ parameters={}, timeout_mins=60,
+ template=None, template_url=None):
post_body = {
"stack_name": name,
"disable_rollback": disable_rollback,
@@ -58,9 +87,13 @@
if template_url:
post_body['template_url'] = template_url
body = json.dumps(post_body)
- uri = 'stacks'
- resp, body = self.post(uri, headers=self.headers, body=body)
- return resp, body
+
+ # Password must be provided on stack create so that heat
+ # can perform future operations on behalf of the user
+ headers = dict(self.headers)
+ headers['X-Auth-Key'] = self.password
+ headers['X-Auth-User'] = self.user
+ return headers, body
def get_stack(self, stack_identifier):
"""Returns the details of a single stack."""
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 87c0eba..c22b398 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -85,6 +85,17 @@
"""Deletes the Specified Volume."""
return self.delete("volumes/%s" % str(volume_id))
+ def upload_volume(self, volume_id, image_name):
+ """Uploads a volume in Glance."""
+ post_body = {
+ 'image_name': image_name,
+ }
+ post_body = json.dumps({'os-volume_upload_image': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['os-volume_upload_image']
+
def attach_volume(self, volume_id, instance_uuid, mountpoint):
"""Attaches a volume to a given instance on a given mountpoint."""
post_body = {
diff --git a/tempest/test.py b/tempest/test.py
index 6be37be..d7008a7 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -137,7 +137,7 @@
thing = things.get(thing_id)
new_status = thing.status
if new_status == 'ERROR':
- self.fail("%s failed to get to expected status."
+ self.fail("%s failed to get to expected status. "
"In ERROR state."
% thing)
elif new_status == expected_status:
diff --git a/test-requirements.txt b/test-requirements.txt
index 3912695..2185997 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,7 +3,6 @@
pyflakes==0.7.2
flake8==2.0
hacking>=0.5.3,<0.6
-psycopg2
# needed for doc build
sphinx>=1.1.2
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 42a44e8..f428c1e 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -34,12 +34,13 @@
class InstallVenv(object):
- def __init__(self, root, venv, pip_requires, test_requires, py_version,
+ def __init__(self, root, venv, requirements,
+ test_requirements, py_version,
project):
self.root = root
self.venv = venv
- self.pip_requires = pip_requires
- self.test_requires = test_requires
+ self.requirements = requirements
+ self.test_requirements = test_requirements
self.py_version = py_version
self.project = project
@@ -75,11 +76,13 @@
def get_distro(self):
if (os.path.exists('/etc/fedora-release') or
os.path.exists('/etc/redhat-release')):
- return Fedora(self.root, self.venv, self.pip_requires,
- self.test_requires, self.py_version, self.project)
+ return Fedora(
+ self.root, self.venv, self.requirements,
+ self.test_requirements, self.py_version, self.project)
else:
- return Distro(self.root, self.venv, self.pip_requires,
- self.test_requires, self.py_version, self.project)
+ return Distro(
+ self.root, self.venv, self.requirements,
+ self.test_requirements, self.py_version, self.project)
def check_dependencies(self):
self.get_distro().install_virtualenv()
@@ -98,11 +101,6 @@
else:
self.run_command(['virtualenv', '-q', self.venv])
print('done.')
- print('Installing pip in venv...', end=' ')
- if not self.run_command(['tools/with_venv.sh', 'easy_install',
- 'pip>1.0']).strip():
- self.die("Failed to install pip.")
- print('done.')
else:
print("venv already exists...")
pass
@@ -116,20 +114,12 @@
print('Installing dependencies with pip (this can take a while)...')
# First things first, make sure our venv has the latest pip and
- # distribute.
- # NOTE: we keep pip at version 1.1 since the most recent version causes
- # the .venv creation to fail. See:
- # https://bugs.launchpad.net/nova/+bug/1047120
- self.pip_install('pip==1.1')
- self.pip_install('distribute')
+ # setuptools.
+ self.pip_install('pip>=1.3')
+ self.pip_install('setuptools')
- # Install greenlet by hand - just listing it in the requires file does
- # not
- # get it installed in the right order
- self.pip_install('greenlet')
-
- self.pip_install('-r', self.pip_requires)
- self.pip_install('-r', self.test_requires)
+ self.pip_install('-r', self.requirements)
+ self.pip_install('-r', self.test_requirements)
def post_process(self):
self.get_distro().post_process()
diff --git a/tox.ini b/tox.ini
index caa9403..964dbca 100644
--- a/tox.ini
+++ b/tox.ini
@@ -61,6 +61,14 @@
nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
python -m tools/tempest_coverage -c report --html {posargs}
+[testenv:stress]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+commands =
+ python -m tempest/stress/run_stress tempest/stress/etc/sample-test.json -d 60
+ python -m tempest/stress/run_stress tempest/stress/etc/volume-create-delete-test.json -d 60
+
+
[testenv:venv]
commands = {posargs}
deps = -r{toxinidir}/requirements.txt