Merge "Fixed argument parsing in run script"
diff --git a/README.rst b/README.rst
index 9daf873..ea36619 100644
--- a/README.rst
+++ b/README.rst
@@ -7,7 +7,7 @@
deployment.
Design Principles
-----------
+-----------------
Tempest Design Principles that we strive to live by.
- Tempest should be able to run against any OpenStack cloud, be it a
@@ -127,6 +127,60 @@
of tempest when running with Python 2.6. Additionally, to enable testr to work
with tempest using python 2.6 the discover module from the unittest-ext
project has to be patched to switch the unittest.TestSuite to use
-unittest2.TestSuite instead. See::
+unittest2.TestSuite instead. See:
https://code.google.com/p/unittest-ext/issues/detail?id=79
+
+Branchless Tempest Considerations
+---------------------------------
+
+Starting with the OpenStack Icehouse release Tempest no longer has any stable
+branches. This is to better ensure API consistency between releases because
+the API behavior should not change between releases. This means that the stable
+branches are also gated by the Tempest master branch, which also means that
+proposed commits to Tempest must work against both the master and all the
+currently supported stable branches of the projects. As such there are a few
+special considerations that have to be accounted for when pushing new changes
+to tempest.
+
+1. New Tests for new features
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When adding tests for new features that were not in previous releases of the
+projects the new test has to be properly skipped with a feature flag. Whether
+this is just as simple as using the @test.requires_ext() decorator to check
+if the required extension (or discoverable optional API) is enabled or adding
+a new config option to the appropriate section. If there isn't a method of
+selecting the new **feature** from the config file then there won't be a
+mechanism to disable the test with older stable releases and the new test won't
+be able to merge.
+
+2. Bug fix on core project needing Tempest changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When trying to land a bug fix which changes a tested API you'll have to use the
+following procedure::
+
+ - Propose change to the project, get a +2 on the change even with failing
+ - Propose skip on Tempest which will only be approved after the
+ corresponding change in the project has a +2 on change
+ - Land project change in master and all open stable branches (if required)
+ - Land changed test in Tempest
+
+Otherwise the bug fix won't be able to land in the project.
+
+3. New Tests for existing features
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If a test is being added for a feature that exists in all the current releases
+of the projects then the only concern is that the API behavior is the same
+across all the versions of the project being tested. If the behavior is not
+consistent the test will not be able to merge.
+
+API Stability
+-------------
+
+For new tests being added to Tempest the assumption is that the API being
+tested is considered stable and adheres to the OpenStack API stability
+guidelines. If an API is still considered experimental or in development then
+it should not be tested by Tempest until it is considered stable.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 1c32b9c..25bc900 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1,8 +1,3 @@
-.. Tempest documentation master file, created by
- sphinx-quickstart on Tue May 21 17:43:32 2013.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
=======================
Tempest Testing Project
=======================
@@ -33,14 +28,6 @@
field_guide/thirdparty
field_guide/unit_tests
-------------------
-API and test cases
-------------------
-.. toctree::
- :maxdepth: 1
-
- api/modules
-
==================
Indices and tables
==================
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 37d4d53..8a7ad9c 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -23,47 +23,48 @@
# of default WARNING level). (boolean value)
#verbose=false
-# Log output to standard error (boolean value)
+# Log output to standard error. (boolean value)
#use_stderr=true
-# format string to use for log messages with context (string
+# Format string to use for log messages with context. (string
# value)
#logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
-# format string to use for log messages without context
+# Format string to use for log messages without context.
# (string value)
#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
-# data to append to log format when level is DEBUG (string
+# Data to append to log format when level is DEBUG. (string
# value)
#logging_debug_format_suffix=%(funcName)s %(pathname)s:%(lineno)d
-# prefix each line of exception output with this format
+# Prefix each line of exception output with this format.
# (string value)
#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
-# list of logger=LEVEL pairs (list value)
-#default_log_levels=amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,iso8601=WARN
+# List of logger=LEVEL pairs. (list value)
+#default_log_levels=amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN
-# publish error events (boolean value)
+# Enables or disables publication of error events. (boolean
+# value)
#publish_errors=false
-# make deprecations fatal (boolean value)
+# Enables or disables fatal status of deprecations. (boolean
+# value)
#fatal_deprecations=false
-# If an instance is passed with the log message, format it
-# like this (string value)
+# The format for an instance that is passed with the log
+# message. (string value)
#instance_format="[instance: %(uuid)s] "
-# If an instance UUID is passed with the log message, format
-# it like this (string value)
+# The format for an instance UUID that is passed with the log
+# message. (string value)
#instance_uuid_format="[instance: %(uuid)s] "
-# The name of logging configuration file. It does not disable
-# existing loggers, but just appends specified logging
-# configuration to any other existing logging options. Please
-# see the Python logging module documentation for details on
-# logging configuration files. (string value)
+# The name of a logging configuration file. This file is
+# appended to any existing logging configuration files. For
+# details about logging configuration files, see the Python
+# logging module documentation. (string value)
# Deprecated group/name - [DEFAULT]/log_config
#log_config_append=<None>
@@ -75,7 +76,7 @@
#log_format=<None>
# Format string for %%(asctime)s in log records. Default:
-# %(default)s (string value)
+# %(default)s . (string value)
#log_date_format=%Y-%m-%d %H:%M:%S
# (Optional) Name of log file to output to. If no default is
@@ -84,14 +85,23 @@
#log_file=<None>
# (Optional) The base directory used for relative --log-file
-# paths (string value)
+# paths. (string value)
# Deprecated group/name - [DEFAULT]/logdir
#log_dir=<None>
-# Use syslog for logging. (boolean value)
+# Use syslog for logging. Existing syslog format is DEPRECATED
+# during I, and will change in J to honor RFC5424. (boolean
+# value)
#use_syslog=false
-# syslog facility to receive log lines (string value)
+# (Optional) Enables or disables syslog rfc5424 format for
+# logging. If enabled, prefixes the MSG part of the syslog
+# message with APP-NAME (RFC5424). The format without the APP-
+# NAME is deprecated in I, and will be removed in J. (boolean
+# value)
+#use_syslog_rfc_format=false
+
+# Syslog facility to receive log lines. (string value)
#syslog_log_facility=LOG_USER
@@ -109,6 +119,9 @@
# value)
#driver_enabled=false
+# Driver name which Ironic uses (string value)
+#driver=fake
+
# The endpoint type to use for the baremetal provisioning
# service (string value)
#endpoint_type=publicURL
@@ -119,13 +132,13 @@
# Timeout for association of Nova instance and Ironic node
# (integer value)
-#association_timeout=10
+#association_timeout=30
# Timeout for Ironic power transitions. (integer value)
-#power_timeout=20
+#power_timeout=60
# Timeout for unprovisioning an Ironic node. (integer value)
-#unprovision_timeout=20
+#unprovision_timeout=60
[boto]
@@ -327,6 +340,11 @@
# (integer value)
#shelved_offload_time=0
+# Unallocated floating IP range, which will be used to test
+# the floating IP bulk feature for CRUD operation. (string
+# value)
+#floating_ip_range=10.0.0.0/29
+
# Allows test cases to create/destroy tenants and users. This
# option enables isolated test cases and better parallel
# execution, but also requires that OpenStack Identity API
@@ -366,17 +384,19 @@
#
# If false, skip all nova v3 tests. (boolean value)
-#api_v3=true
+#api_v3=false
# If false, skip disk config tests (boolean value)
#disk_config=true
# A list of enabled compute extensions with a special entry
-# all which indicates every extension is enabled (list value)
+# all which indicates every extension is enabled. Each
+# extension should be specified with alias name (list value)
#api_extensions=all
# A list of enabled v3 extensions with a special entry all
-# which indicates every extension is enabled (list value)
+# which indicates every extension is enabled. Each extension
+# should be specified with alias name (list value)
#api_v3_extensions=all
# Does the test environment support changing the admin
@@ -417,6 +437,10 @@
# as [nova.rdp]->enabled in nova.conf (boolean value)
#rdp_console=false
+# Does the test environment support instance rescue mode?
+# (boolean value)
+#rescue=true
+
[dashboard]
@@ -711,6 +735,10 @@
# (integer value)
#build_interval=1
+# List of dns servers whichs hould be used for subnet creation
+# (list value)
+#dns_servers=8.8.8.8,8.8.4.4
+
[network-feature-enabled]
@@ -725,6 +753,11 @@
# all which indicates every extension is enabled (list value)
#api_extensions=all
+# Allow the execution of IPv6 subnet tests that use the
+# extended IPv6 attributes ipv6_ra_mode and ipv6_address_mode
+# (boolean value)
+#ipv6_subnet_attributes=false
+
[object-storage]
@@ -830,6 +863,28 @@
# queues (integer value)
#max_queues_per_page=20
+# The maximum metadata size for a queue (integer value)
+#max_queue_metadata=65536
+
+# The maximum number of queue message per page when listing
+# (or) posting messages (integer value)
+#max_messages_per_page=20
+
+# The maximum size of a message body (integer value)
+#max_message_size=262144
+
+# The maximum number of messages per claim (integer value)
+#max_messages_per_claim=20
+
+# The maximum ttl for a message (integer value)
+#max_message_ttl=1209600
+
+# The maximum ttl for a claim (integer value)
+#max_claim_ttl=43200
+
+# The maximum grace period for a claim (integer value)
+#max_claim_grace=43200
+
[scenario]
@@ -976,6 +1031,10 @@
# value)
#endpoint_type=publicURL
+# This variable is used as flag to enable notification tests
+# (boolean value)
+#too_slow_to_test=true
+
[volume]
diff --git a/openstack-common.conf b/openstack-common.conf
index 38d58ee..a9a6b0b 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -7,6 +7,7 @@
module=log
module=importlib
module=fixture
+module=versionutils
# The base module to hold the copy of openstack.common
base=tempest
diff --git a/requirements.txt b/requirements.txt
index f907e7d..ab2903a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,7 +8,7 @@
paramiko>=1.13.0
netaddr>=0.7.6
python-glanceclient>=0.9.0
-python-keystoneclient>=0.8.0
+python-keystoneclient>=0.9.0
python-novaclient>=2.17.0
python-neutronclient>=2.3.4,<3
python-cinderclient>=1.0.6
@@ -18,8 +18,8 @@
python-swiftclient>=2.0.2
testresources>=0.2.4
testrepository>=0.0.18
-oslo.config>=1.2.0
-six>=1.6.0
+oslo.config>=1.2.1
+six>=1.7.0
iso8601>=0.1.9
fixtures>=0.3.14
testscenarios>=0.4
diff --git a/setup.cfg b/setup.cfg
index 339da12..5c62710 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,6 +21,7 @@
console_scripts =
verify-tempest-config = tempest.cmd.verify_tempest_config:main
javelin2 = tempest.cmd.javelin:main
+ run-tempest-stress = tempest.cmd.run_stress:main
[build_sphinx]
all_files = 1
diff --git a/tempest/README.rst b/tempest/README.rst
index dbac809..18c7cf3 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -62,13 +62,10 @@
stress
------
-Stress tests are designed to stress an OpenStack environment by
-running a high workload against it and seeing what breaks. Tools may
-be provided to help detect breaks (stack traces in the logs).
-
-TODO: old stress tests deleted, new_stress that david is working on
-moves into here.
-
+Stress tests are designed to stress an OpenStack environment by running a high
+workload against it and seeing what breaks. The stress test framework runs
+several test jobs in parallel and can run any existing test in Tempest as a
+stress job.
thirdparty
----------
diff --git a/tempest/api/baremetal/base.py b/tempest/api/baremetal/base.py
index 021adaf..6f7e438 100644
--- a/tempest/api/baremetal/base.py
+++ b/tempest/api/baremetal/base.py
@@ -27,13 +27,12 @@
def decorator(f):
@functools.wraps(f)
def wrapper(cls, *args, **kwargs):
- result = f(cls, *args, **kwargs)
- body = result[resource]
+ resp, body = f(cls, *args, **kwargs)
if 'uuid' in body:
cls.created_objects[resource].add(body['uuid'])
- return result
+ return resp, body
return wrapper
return decorator
@@ -51,7 +50,7 @@
mgr = clients.AdminManager()
cls.client = mgr.baremetal_client
-
+ cls.power_timeout = CONF.baremetal.power_timeout
cls.created_objects = {'chassis': set(),
'port': set(),
'node': set()}
@@ -81,8 +80,7 @@
"""
description = description or data_utils.rand_name('test-chassis-')
resp, body = cls.client.create_chassis(description=description)
-
- return {'chassis': body, 'response': resp}
+ return resp, body
@classmethod
@creates('node')
@@ -102,7 +100,7 @@
cpu_num=cpu_num, storage=storage,
memory=memory, driver=driver)
- return {'node': body, 'response': resp}
+ return resp, body
@classmethod
@creates('port')
@@ -121,7 +119,7 @@
resp, body = cls.client.create_port(address=address, node_id=node_id,
extra=extra, uuid=uuid)
- return {'port': body, 'response': resp}
+ return resp, body
@classmethod
def delete_chassis(cls, chassis_id):
diff --git a/tempest/api/baremetal/test_api_discovery.py b/tempest/api/baremetal/test_api_discovery.py
index e594b3e..bee10b9 100644
--- a/tempest/api/baremetal/test_api_discovery.py
+++ b/tempest/api/baremetal/test_api_discovery.py
@@ -20,6 +20,7 @@
@test.attr(type='smoke')
def test_api_versions(self):
resp, descr = self.client.get_api_description()
+ self.assertEqual('200', resp['status'])
expected_versions = ('v1',)
versions = [version['id'] for version in descr['versions']]
@@ -30,6 +31,7 @@
@test.attr(type='smoke')
def test_default_version(self):
resp, descr = self.client.get_api_description()
+ self.assertEqual('200', resp['status'])
default_version = descr['default_version']
self.assertEqual(default_version['id'], 'v1')
@@ -37,6 +39,7 @@
@test.attr(type='smoke')
def test_version_1_resources(self):
resp, descr = self.client.get_version_description(version='v1')
+ self.assertEqual('200', resp['status'])
expected_resources = ('nodes', 'chassis',
'ports', 'links', 'media_types')
diff --git a/tempest/api/baremetal/test_chassis.py b/tempest/api/baremetal/test_chassis.py
index 7af1336..4ab86c2 100644
--- a/tempest/api/baremetal/test_chassis.py
+++ b/tempest/api/baremetal/test_chassis.py
@@ -20,57 +20,64 @@
class TestChassis(base.BaseBaremetalTest):
"""Tests for chassis."""
+ @classmethod
+ def setUpClass(cls):
+ super(TestChassis, cls).setUpClass()
+ _, cls.chassis = cls.create_chassis()
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in expected.iteritems():
+ if key not in ('created_at', 'updated_at'):
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
+
@test.attr(type='smoke')
def test_create_chassis(self):
descr = data_utils.rand_name('test-chassis-')
- ch = self.create_chassis(description=descr)['chassis']
-
- self.assertEqual(ch['description'], descr)
+ resp, chassis = self.create_chassis(description=descr)
+ self.assertEqual('201', resp['status'])
+ self.assertEqual(chassis['description'], descr)
@test.attr(type='smoke')
def test_create_chassis_unicode_description(self):
# Use a unicode string for testing:
# 'We ♡ OpenStack in Ukraine'
descr = u'В Україні ♡ OpenStack!'
- ch = self.create_chassis(description=descr)['chassis']
-
- self.assertEqual(ch['description'], descr)
+ resp, chassis = self.create_chassis(description=descr)
+ self.assertEqual('201', resp['status'])
+ self.assertEqual(chassis['description'], descr)
@test.attr(type='smoke')
def test_show_chassis(self):
- descr = data_utils.rand_name('test-chassis-')
- uuid = self.create_chassis(description=descr)['chassis']['uuid']
-
- resp, chassis = self.client.show_chassis(uuid)
-
- self.assertEqual(chassis['uuid'], uuid)
- self.assertEqual(chassis['description'], descr)
+ resp, chassis = self.client.show_chassis(self.chassis['uuid'])
+ self.assertEqual('200', resp['status'])
+ self._assertExpected(self.chassis, chassis)
@test.attr(type="smoke")
def test_list_chassis(self):
- created_ids = [self.create_chassis()['chassis']['uuid']
- for i in range(0, 5)]
-
resp, body = self.client.list_chassis()
- loaded_ids = [ch['uuid'] for ch in body['chassis']]
-
- for i in created_ids:
- self.assertIn(i, loaded_ids)
+ self.assertEqual('200', resp['status'])
+ self.assertIn(self.chassis['uuid'],
+ [i['uuid'] for i in body['chassis']])
@test.attr(type='smoke')
def test_delete_chassis(self):
- uuid = self.create_chassis()['chassis']['uuid']
+ resp, body = self.create_chassis()
+ uuid = body['uuid']
- self.delete_chassis(uuid)
-
+ resp = self.delete_chassis(uuid)
+ self.assertEqual('204', resp['status'])
self.assertRaises(exc.NotFound, self.client.show_chassis, uuid)
@test.attr(type='smoke')
def test_update_chassis(self):
- chassis_id = self.create_chassis()['chassis']['uuid']
+ resp, body = self.create_chassis()
+ uuid = body['uuid']
new_description = data_utils.rand_name('new-description-')
- self.client.update_chassis(chassis_id, description=new_description)
-
- resp, chassis = self.client.show_chassis(chassis_id)
+ resp, body = (self.client.update_chassis(uuid,
+ description=new_description))
+ self.assertEqual('200', resp['status'])
+ resp, chassis = self.client.show_chassis(uuid)
self.assertEqual(chassis['description'], new_description)
diff --git a/tempest/api/baremetal/test_drivers.py b/tempest/api/baremetal/test_drivers.py
index 445ca60..852b6ab 100644
--- a/tempest/api/baremetal/test_drivers.py
+++ b/tempest/api/baremetal/test_drivers.py
@@ -13,14 +13,29 @@
# under the License.
from tempest.api.baremetal import base
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class TestDrivers(base.BaseBaremetalTest):
"""Tests for drivers."""
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(TestDrivers, cls).setUpClass()
+ cls.driver_name = CONF.baremetal.driver
@test.attr(type="smoke")
def test_list_drivers(self):
resp, drivers = self.client.list_drivers()
self.assertEqual('200', resp['status'])
- self.assertIn('fake', [d['name'] for d in drivers['drivers']])
+ self.assertIn(self.driver_name,
+ [d['name'] for d in drivers['drivers']])
+
+ @test.attr(type="smoke")
+ def test_show_driver(self):
+ resp, driver = self.client.show_driver(self.driver_name)
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(self.driver_name, driver['name'])
diff --git a/tempest/api/baremetal/test_nodes.py b/tempest/api/baremetal/test_nodes.py
index 0f585cb..1572840 100644
--- a/tempest/api/baremetal/test_nodes.py
+++ b/tempest/api/baremetal/test_nodes.py
@@ -23,7 +23,15 @@
def setUp(self):
super(TestNodes, self).setUp()
- self.chassis = self.create_chassis()['chassis']
+ _, self.chassis = self.create_chassis()
+ _, self.node = self.create_node(self.chassis['uuid'])
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in six.iteritems(expected):
+ if key not in ('created_at', 'updated_at'):
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
@test.attr(type='smoke')
def test_create_node(self):
@@ -32,45 +40,32 @@
'storage': '10240',
'memory': '1024'}
- node = self.create_node(self.chassis['uuid'], **params)['node']
-
- for key in params:
- self.assertEqual(node['properties'][key], params[key])
+ resp, body = self.create_node(self.chassis['uuid'], **params)
+ self.assertEqual('201', resp['status'])
+ self._assertExpected(params, body['properties'])
@test.attr(type='smoke')
def test_delete_node(self):
- node = self.create_node(self.chassis['uuid'])['node']
- node_id = node['uuid']
+ resp, node = self.create_node(self.chassis['uuid'])
+ self.assertEqual('201', resp['status'])
- resp = self.delete_node(node_id)
+ resp = self.delete_node(node['uuid'])
self.assertEqual(resp['status'], '204')
- self.assertRaises(exc.NotFound, self.client.show_node, node_id)
+ self.assertRaises(exc.NotFound, self.client.show_node, node['uuid'])
@test.attr(type='smoke')
def test_show_node(self):
- params = {'cpu_arch': 'x86_64',
- 'cpu_num': '4',
- 'storage': '100',
- 'memory': '512'}
-
- created_node = self.create_node(self.chassis['uuid'], **params)['node']
- resp, loaded_node = self.client.show_node(created_node['uuid'])
-
- for key, val in created_node.iteritems():
- if key not in ('created_at', 'updated_at'):
- self.assertEqual(loaded_node[key], val)
+ resp, loaded_node = self.client.show_node(self.node['uuid'])
+ self.assertEqual('200', resp['status'])
+ self._assertExpected(self.node, loaded_node)
@test.attr(type='smoke')
def test_list_nodes(self):
- uuids = [self.create_node(self.chassis['uuid'])['node']['uuid']
- for i in range(0, 5)]
-
resp, body = self.client.list_nodes()
- loaded_uuids = [n['uuid'] for n in body['nodes']]
-
- for u in uuids:
- self.assertIn(u, loaded_uuids)
+ self.assertEqual('200', resp['status'])
+ self.assertIn(self.node['uuid'],
+ [i['uuid'] for i in body['nodes']])
@test.attr(type='smoke')
def test_update_node(self):
@@ -79,17 +74,24 @@
'storage': '10',
'memory': '128'}
- node = self.create_node(self.chassis['uuid'], **props)['node']
- node_id = node['uuid']
+ resp, node = self.create_node(self.chassis['uuid'], **props)
+ self.assertEqual('201', resp['status'])
- new_props = {'cpu_arch': 'x86',
- 'cpu_num': '1',
- 'storage': '10000',
- 'memory': '12300'}
+ new_p = {'cpu_arch': 'x86',
+ 'cpu_num': '1',
+ 'storage': '10000',
+ 'memory': '12300'}
- self.client.update_node(node_id, properties=new_props)
- resp, node = self.client.show_node(node_id)
+ resp, body = self.client.update_node(node['uuid'], properties=new_p)
+ self.assertEqual('200', resp['status'])
+ resp, node = self.client.show_node(node['uuid'])
+ self.assertEqual('200', resp['status'])
+ self._assertExpected(new_p, node['properties'])
- for name, value in six.iteritems(new_props):
- if name not in ('created_at', 'updated_at'):
- self.assertEqual(node['properties'][name], value)
+ @test.attr(type='smoke')
+ def test_validate_driver_interface(self):
+ resp, body = self.client.validate_driver_interface(self.node['uuid'])
+ self.assertEqual('200', resp['status'])
+ core_interfaces = ['power', 'deploy']
+ for interface in core_interfaces:
+ self.assertIn(interface, body)
diff --git a/tempest/api/baremetal/test_nodestates.py b/tempest/api/baremetal/test_nodestates.py
index c658d7f..3044bc6 100644
--- a/tempest/api/baremetal/test_nodestates.py
+++ b/tempest/api/baremetal/test_nodestates.py
@@ -13,6 +13,8 @@
# under the License.
from tempest.api.baremetal import base
+from tempest import exceptions
+from tempest.openstack.common import timeutils
from tempest import test
@@ -20,10 +22,25 @@
"""Tests for baremetal NodeStates."""
@classmethod
- def setUpClass(self):
- super(TestNodeStates, self).setUpClass()
- chassis = self.create_chassis()['chassis']
- self.node = self.create_node(chassis['uuid'])['node']
+ def setUpClass(cls):
+ super(TestNodeStates, cls).setUpClass()
+ resp, cls.chassis = cls.create_chassis()
+ resp, cls.node = cls.create_node(cls.chassis['uuid'])
+
+ def _validate_power_state(self, node_uuid, power_state):
+ # Validate that power state is set within timeout
+ if power_state == 'rebooting':
+ power_state = 'power on'
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(
+ start, timeutils.utcnow()) < self.power_timeout:
+ resp, node = self.client.show_node(node_uuid)
+ self.assertEqual(200, resp.status)
+ if node['power_state'] == power_state:
+ return
+ message = ('Failed to set power state within '
+ 'the required time: %s sec.' % self.power_timeout)
+ raise exceptions.TimeoutException(message)
@test.attr(type='smoke')
def test_list_nodestates(self):
@@ -31,3 +48,16 @@
self.assertEqual('200', resp['status'])
for key in nodestates:
self.assertEqual(nodestates[key], self.node[key])
+
+ @test.attr(type='smoke')
+ def test_set_node_power_state(self):
+ resp, node = self.create_node(self.chassis['uuid'])
+ self.assertEqual('201', resp['status'])
+ states = ["power on", "rebooting", "power off"]
+ for state in states:
+ # Set power state
+ resp, _ = self.client.set_node_power_state(node['uuid'],
+ state)
+ self.assertEqual('202', resp['status'])
+ # Check power state after state is set
+ self._validate_power_state(node['uuid'], state)
diff --git a/tempest/api/baremetal/test_ports.py b/tempest/api/baremetal/test_ports.py
index 8b76811..4ac7e29 100644
--- a/tempest/api/baremetal/test_ports.py
+++ b/tempest/api/baremetal/test_ports.py
@@ -22,25 +22,30 @@
def setUp(self):
super(TestPorts, self).setUp()
- chassis = self.create_chassis()['chassis']
- self.node = self.create_node(chassis['uuid'])['node']
+ _, self.chassis = self.create_chassis()
+ _, self.node = self.create_node(self.chassis['uuid'])
+ _, self.port = self.create_port(self.node['uuid'],
+ data_utils.rand_mac_address())
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in expected.iteritems():
+ if key not in ('created_at', 'updated_at'):
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
@test.attr(type='smoke')
def test_create_port(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- result = self.create_port(node_id=node_id, address=address)
-
- port = result['port']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual(201, resp.status)
resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
- self.assertEqual(port['uuid'], body['uuid'])
- self.assertEqual(address, body['address'])
- self.assertEqual({}, body['extra'])
- self.assertEqual(node_id, body['node_uuid'])
+ self._assertExpected(port, body)
@test.attr(type='smoke')
def test_create_port_specifying_uuid(self):
@@ -48,15 +53,13 @@
address = data_utils.rand_mac_address()
uuid = data_utils.rand_uuid()
- self.create_port(node_id=node_id, address=address, uuid=uuid)
+ resp, port = self.create_port(node_id=node_id,
+ address=address, uuid=uuid)
+ self.assertEqual(201, resp.status)
resp, body = self.client.show_port(uuid)
-
self.assertEqual(200, resp.status)
- self.assertEqual(uuid, body['uuid'])
- self.assertEqual(address, body['address'])
- self.assertEqual({}, body['extra'])
- self.assertEqual(node_id, body['node_uuid'])
+ self._assertExpected(port, body)
@test.attr(type='smoke')
def test_create_port_with_extra(self):
@@ -64,76 +67,46 @@
address = data_utils.rand_mac_address()
extra = {'key': 'value'}
- result = self.create_port(node_id=node_id, address=address,
- extra=extra)
- port = result['port']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
resp, body = self.client.show_port(port['uuid'])
-
self.assertEqual(200, resp.status)
- self.assertEqual(port['uuid'], body['uuid'])
- self.assertEqual(address, body['address'])
- self.assertEqual(extra, body['extra'])
- self.assertEqual(node_id, body['node_uuid'])
+ self._assertExpected(port, body)
@test.attr(type='smoke')
def test_delete_port(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual(201, resp.status)
- resp = self.delete_port(port_id)
+ resp = self.delete_port(port['uuid'])
self.assertEqual(204, resp.status)
- self.assertRaises(exc.NotFound, self.client.show_port, port_id)
+ self.assertRaises(exc.NotFound, self.client.show_port, port['uuid'])
@test.attr(type='smoke')
def test_show_port(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key': 'value'}
-
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
-
- resp, port = self.client.show_port(port_id)
-
+ resp, port = self.client.show_port(self.port['uuid'])
self.assertEqual(200, resp.status)
- self.assertEqual(port_id, port['uuid'])
- self.assertEqual(address, port['address'])
- self.assertEqual(extra, port['extra'])
+ self._assertExpected(self.port, port)
@test.attr(type='smoke')
def test_show_port_with_links(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
-
- resp, body = self.client.show_port(port_id)
-
+ resp, port = self.client.show_port(self.port['uuid'])
self.assertEqual(200, resp.status)
- self.assertIn('links', body.keys())
- self.assertEqual(2, len(body['links']))
- self.assertIn(port_id, body['links'][0]['href'])
+ self.assertIn('links', port.keys())
+ self.assertEqual(2, len(port['links']))
+ self.assertIn(port['uuid'], port['links'][0]['href'])
@test.attr(type='smoke')
def test_list_ports(self):
- node_id = self.node['uuid']
-
- uuids = [self.create_port(node_id=node_id,
- address=data_utils.rand_mac_address())
- ['port']['uuid'] for i in xrange(5)]
-
resp, body = self.client.list_ports()
self.assertEqual(200, resp.status)
- loaded_uuids = [p['uuid'] for p in body['ports']]
-
- for uuid in uuids:
- self.assertIn(uuid, loaded_uuids)
-
+ self.assertIn(self.port['uuid'],
+ [i['uuid'] for i in body['ports']])
# Verify self links.
for port in body['ports']:
self.validate_self_link('ports', port['uuid'],
@@ -141,15 +114,8 @@
@test.attr(type='smoke')
def test_list_with_limit(self):
- node_id = self.node['uuid']
-
- for i in xrange(5):
- self.create_port(node_id=node_id,
- address=data_utils.rand_mac_address())
-
resp, body = self.client.list_ports(limit=3)
self.assertEqual(200, resp.status)
- self.assertEqual(3, len(body['ports']))
next_marker = body['ports'][-1]['uuid']
self.assertIn(next_marker, body['next'])
@@ -160,13 +126,13 @@
uuids = [
self.create_port(node_id=node_id,
address=data_utils.rand_mac_address())
- ['port']['uuid'] for i in range(0, 5)]
+ [1]['uuid'] for i in range(0, 5)]
resp, body = self.client.list_ports_detail()
self.assertEqual(200, resp.status)
- ports_dict = {port['uuid']: port for port in body['ports']
- if port['uuid'] in uuids}
+ ports_dict = dict((port['uuid'], port) for port in body['ports']
+ if port['uuid'] in uuids)
for uuid in uuids:
self.assertIn(uuid, ports_dict)
@@ -179,14 +145,28 @@
self.validate_self_link('ports', port['uuid'],
port['links'][0]['href'])
+ def test_list_ports_details_with_address(self):
+ node_id = self.node['uuid']
+ address = data_utils.rand_mac_address()
+ self.create_port(node_id=node_id, address=address)
+ for i in range(0, 5):
+ self.create_port(node_id=node_id,
+ address=data_utils.rand_mac_address())
+
+ resp, body = self.client.list_ports_detail(address=address)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(1, len(body['ports']))
+ self.assertEqual(address, body['ports'][0]['address'])
+
@test.attr(type='smoke')
def test_update_port_replace(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
new_address = data_utils.rand_mac_address()
new_extra = {'key1': 'new-value1', 'key2': 'new-value2',
@@ -205,9 +185,10 @@
'op': 'replace',
'value': new_extra['key3']}]
- self.client.update_port(port_id, patch)
+ resp, _ = self.client.update_port(port['uuid'], patch)
+ self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(new_address, body['address'])
self.assertEqual(new_extra, body['extra'])
@@ -218,23 +199,25 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
# Removing one item from the collection
- resp, _ = self.client.update_port(port_id, [{'path': '/extra/key2',
- 'op': 'remove'}])
+ resp, _ = self.client.update_port(port['uuid'],
+ [{'path': '/extra/key2',
+ 'op': 'remove'}])
self.assertEqual(200, resp.status)
extra.pop('key2')
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(extra, body['extra'])
# Removing the collection
- resp, _ = self.client.update_port(port_id, [{'path': '/extra',
- 'op': 'remove'}])
+ resp, _ = self.client.update_port(port['uuid'], [{'path': '/extra',
+ 'op': 'remove'}])
self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual({}, body['extra'])
@@ -247,8 +230,8 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual(201, resp.status)
extra = {'key1': 'value1', 'key2': 'value2'}
@@ -259,9 +242,10 @@
'op': 'add',
'value': extra['key2']}]
- self.client.update_port(port_id, patch)
+ resp, _ = self.client.update_port(port['uuid'], patch)
+ self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(extra, body['extra'])
@@ -271,8 +255,9 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
new_address = data_utils.rand_mac_address()
new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
@@ -289,9 +274,10 @@
'op': 'add',
'value': new_extra['key3']}]
- self.client.update_port(port_id, patch)
+ resp, _ = self.client.update_port(port['uuid'], patch)
+ self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(new_address, body['address'])
self.assertEqual(new_extra, body['extra'])
diff --git a/tempest/api/baremetal/test_ports_negative.py b/tempest/api/baremetal/test_ports_negative.py
index 4cbe00e..3e77a5f 100644
--- a/tempest/api/baremetal/test_ports_negative.py
+++ b/tempest/api/baremetal/test_ports_negative.py
@@ -22,8 +22,11 @@
def setUp(self):
super(TestPortsNegative, self).setUp()
- chassis = self.create_chassis()['chassis']
- self.node = self.create_node(chassis['uuid'])['node']
+ resp, self.chassis = self.create_chassis()
+ self.assertEqual('201', resp['status'])
+
+ resp, self.node = self.create_node(self.chassis['uuid'])
+ self.assertEqual('201', resp['status'])
@test.attr(type=['negative', 'smoke'])
def test_create_port_malformed_mac(self):
@@ -134,9 +137,13 @@
address = data_utils.rand_mac_address()
extra = {'key': 'value'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
- self.client.delete_port(port_id)
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
+ resp, body = self.client.delete_port(port_id)
+ self.assertEqual('204', resp['status'])
patch = [{'path': '/extra/key',
'op': 'replace',
@@ -162,8 +169,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/extra/key', ' op': 'add',
@@ -174,8 +182,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/extra',
@@ -187,8 +196,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/nonexistent', ' op': 'add',
'value': 'value'}])
@@ -198,8 +209,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
patch = [{'path': '/node_uuid',
'op': 'replace',
@@ -213,9 +225,13 @@
address1 = data_utils.rand_mac_address()
address2 = data_utils.rand_mac_address()
- self.create_port(node_id=node_id, address=address1)
- port_id = self.create_port(node_id=node_id,
- address=address2)['port']['uuid']
+ resp, port1 = self.create_port(node_id=node_id, address=address1)
+ self.assertEqual('201', resp['status'])
+
+ resp, port2 = self.create_port(node_id=node_id, address=address2)
+ self.assertEqual('201', resp['status'])
+ port_id = port2['uuid']
+
patch = [{'path': '/address',
'op': 'replace',
'value': address1}]
@@ -227,8 +243,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
patch = [{'path': '/node_uuid',
'op': 'replace',
@@ -241,8 +258,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
patch = [{'path': '/address',
'op': 'replace',
'value': 'malformed:mac'}]
@@ -256,13 +275,14 @@
address = data_utils.rand_mac_address()
extra = {'key': 'value'}
- port_id = self.create_port(node_id=node_id,
- address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
patch = [{'path': '/extra/key',
'op': 'replace',
'value': 0.123}]
-
self.assertRaises(exc.BadRequest,
self.client.update_port, port_id, patch)
@@ -270,11 +290,11 @@
def test_update_port_replace_whole_extra_with_malformed(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- extra = {'key': 'value'}
- port_id = self.create_port(node_id=node_id,
- address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
patch = [{'path': '/extra',
'op': 'replace',
'value': [1, 2, 3, 4, 'a']}]
@@ -287,8 +307,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
patch = [{'path': '/nonexistent', ' op': 'replace', 'value': 'value'}]
@@ -300,8 +321,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/address', 'op': 'remove'}])
@@ -310,8 +333,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/uuid', 'op': 'remove'}])
@@ -320,8 +345,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/nonexistent', 'op': 'remove'}])
@@ -339,8 +366,10 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
new_address = data_utils.rand_mac_address()
new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index 9555367..3a6de36 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -26,7 +26,7 @@
@classmethod
def setUpClass(cls):
super(AZAdminV3Test, cls).setUpClass()
- cls.client = cls.os_adm.availability_zone_client
+ cls.client = cls.availability_zone_admin_client
@test.attr(type='gate')
def test_get_availability_zone_list(self):
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index b0692b1..939f1a1 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -40,17 +40,20 @@
break
@test.attr(type='gate')
+ @test.services('network')
def test_list_fixed_ip_details(self):
resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
self.assertEqual(fixed_ip['address'], self.ip)
@test.attr(type='gate')
+ @test.services('network')
def test_set_reserve(self):
body = {"reserve": "None"}
resp, body = self.client.reserve_fixed_ip(self.ip, body)
self.assertEqual(resp.status, 202)
@test.attr(type='gate')
+ @test.services('network')
def test_set_unreserve(self):
body = {"unreserve": "None"}
resp, body = self.client.reserve_fixed_ip(self.ip, body)
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index 3fb3829..1caa246 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -41,11 +41,13 @@
break
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_list_fixed_ip_details_with_non_admin_user(self):
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.get_fixed_ip_details, self.ip)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_set_reserve_with_non_admin_user(self):
body = {"reserve": "None"}
self.assertRaises(exceptions.Unauthorized,
@@ -53,6 +55,7 @@
self.ip, body)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_set_unreserve_with_non_admin_user(self):
body = {"unreserve": "None"}
self.assertRaises(exceptions.Unauthorized,
@@ -60,6 +63,7 @@
self.ip, body)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_set_reserve_with_invalid_ip(self):
# NOTE(maurosr): since this exercises the same code snippet, we do it
# only for reserve action
@@ -69,6 +73,7 @@
"my.invalid.ip", body)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_fixed_ip_with_invalid_action(self):
body = {"invalid_action": "None"}
self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 111ac9c..18866e5 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -30,8 +30,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAdminTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
@@ -231,7 +231,7 @@
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
- # Create the flavor
+ # Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
self.ram, self.vcpus,
self.disk,
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 3ba7314..f2554ea 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -28,8 +28,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAccessTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
# Compute admin flavor client
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 73834e9..b636ccd 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -31,8 +31,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 91145ec..56daf96 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -29,8 +29,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index a139c2f..1e5695f 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -30,8 +30,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
index b37d32c..9e4412f 100644
--- a/tempest/api/compute/admin/test_flavors_negative.py
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -32,8 +32,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAdminNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
new file mode 100644
index 0000000..16c2810
--- /dev/null
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -0,0 +1,83 @@
+# Copyright 2014 NEC Technologies India Ltd.
+# 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 netaddr
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class FloatingIPsBulkAdminTestJSON(base.BaseV2ComputeAdminTest):
+ """
+ Tests Floating IPs Bulk APIs Create, List and Delete that
+ require admin privileges.
+ API documentation - http://docs.openstack.org/api/openstack-compute/2/
+ content/ext-os-floating-ips-bulk.html
+ """
+
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(FloatingIPsBulkAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.floating_ips_client
+ cls.ip_range = CONF.compute.floating_ip_range
+ cls.verify_unallocated_floating_ip_range(cls.ip_range)
+
+ @classmethod
+ def verify_unallocated_floating_ip_range(cls, ip_range):
+ # Verify whether configure floating IP range is not already allocated.
+ _, body = cls.client.list_floating_ips_bulk()
+ allocated_ips_list = map(lambda x: x['address'], body)
+ for ip_addr in netaddr.IPNetwork(ip_range).iter_hosts():
+ if str(ip_addr) in allocated_ips_list:
+ msg = ("Configured unallocated floating IP range is already "
+ "allocated. Configure the correct unallocated range "
+ "as 'floating_ip_range'")
+ raise cls.skipException(msg)
+ return
+
+ def _delete_floating_ips_bulk(self, ip_range):
+ try:
+ self.client.delete_floating_ips_bulk(ip_range)
+ except Exception:
+ pass
+
+ @test.attr(type='gate')
+ @test.services('network')
+ def test_create_list_delete_floating_ips_bulk(self):
+ # Create, List and delete the Floating IPs Bulk
+ pool = 'test_pool'
+ # NOTE(GMann): Reserving the IP range but those are not attached
+ # anywhere. Using the below mentioned interface which is not ever
+ # expected to be used. Clean Up has been done for created IP range
+ interface = 'eth0'
+ resp, body = self.client.create_floating_ips_bulk(self.ip_range,
+ pool,
+ interface)
+
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self._delete_floating_ips_bulk, self.ip_range)
+ self.assertEqual(self.ip_range, body['ip_range'])
+ resp, ips_list = self.client.list_floating_ips_bulk()
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(ips_list))
+ for ip in netaddr.IPNetwork(self.ip_range).iter_hosts():
+ self.assertIn(str(ip), map(lambda x: x['address'], ips_list))
+ resp, body = self.client.delete_floating_ips_bulk(self.ip_range)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(self.ip_range, body)
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 48f9ffb..85b26a1 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -86,8 +86,27 @@
# Verify that GET shows the specified hypervisor uptime
hypers = self._list_hypervisors()
- has_valid_uptime = False
+ # Ironic will register each baremetal node as a 'hypervisor',
+ # so the hypervisor list can contain many hypervisors of type
+ # 'ironic'. If they are ALL ironic, skip this test since ironic
+ # doesn't support hypervisor uptime. Otherwise, remove them
+ # from the list of hypervisors to test.
+ ironic_only = True
+ hypers_without_ironic = []
for hyper in hypers:
+ resp, details = (self.client.
+ get_hypervisor_show_details(hypers[0]['id']))
+ self.assertEqual(200, resp.status)
+ if details['hypervisor_type'] != 'ironic':
+ hypers_without_ironic.append(hyper)
+ ironic_only = False
+
+ if ironic_only:
+ raise self.skipException(
+ "Ironic does not support hypervisor uptime")
+
+ has_valid_uptime = False
+ for hyper in hypers_without_ironic:
# because hypervisors might be disabled, this loops looking
# for any good hit.
try:
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 348666d..d27d78b 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -13,14 +13,26 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
+from testtools import matchers
+
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest.openstack.common import log as logging
from tempest import test
+LOG = logging.getLogger(__name__)
+
class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
force_tenant_isolation = True
+ def setUp(self):
+ # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotasAdminTestJSON, self).setUp()
+
@classmethod
def setUpClass(cls):
super(QuotasAdminTestJSON, cls).setUpClass()
@@ -134,3 +146,55 @@
class QuotasAdminTestXML(QuotasAdminTestJSON):
_interface = 'xml'
+
+
+class QuotaClassesAdminTestJSON(base.BaseV2ComputeAdminTest):
+ """Tests the os-quota-class-sets API to update default quotas.
+ """
+
+ def setUp(self):
+ # All test cases in this class need to externally lock on doing
+ # anything with default quota values.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotaClassesAdminTestJSON, self).setUp()
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotaClassesAdminTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.quota_classes_client
+
+ def _restore_default_quotas(self, original_defaults):
+ LOG.debug("restoring quota class defaults")
+ resp, body = self.adm_client.update_quota_class_set(
+ 'default', **original_defaults)
+ self.assertEqual(200, resp.status)
+
+ # NOTE(sdague): this test is problematic as it changes
+ # global state, and possibly needs to be part of a set of
+ # tests that get run all by themselves at the end under a
+ # 'danger' flag.
+ def test_update_default_quotas(self):
+ LOG.debug("get the current 'default' quota class values")
+ resp, body = self.adm_client.get_quota_class_set('default')
+ self.assertEqual(200, resp.status)
+ self.assertIn('id', body)
+ self.assertEqual('default', body.pop('id'))
+ # restore the defaults when the test is done
+ self.addCleanup(self._restore_default_quotas, body.copy())
+ # increment all of the values for updating the default quota class
+ for quota, default in six.iteritems(body):
+ # NOTE(sdague): we need to increment a lot, otherwise
+ # there is a real chance that we go from -1 (unlimitted)
+ # to a very small number which causes issues.
+ body[quota] = default + 100
+ LOG.debug("update limits for the default quota class set")
+ resp, update_body = self.adm_client.update_quota_class_set('default',
+ **body)
+ self.assertEqual(200, resp.status)
+ LOG.debug("assert that the response has all of the changed values")
+ self.assertThat(update_body.items(),
+ matchers.ContainsAll(body.items()))
+
+
+class QuotaClassesAdminTestXML(QuotaClassesAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index e1dc685..f147b9c 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -44,6 +44,7 @@
# TODO(afazekas): Add dedicated tenant to the skiped quota tests
# it can be moved into the setUpClass as well
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_create_server_when_cpu_quota_is_full(self):
# Disallow server creation when tenant's vcpu quota is full
@@ -57,8 +58,9 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
cores=default_vcpu_quota)
- self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ self.assertRaises(exceptions.Unauthorized, self.create_test_server)
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_create_server_when_memory_quota_is_full(self):
# Disallow server creation when tenant's memory quota is full
@@ -72,8 +74,9 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
ram=default_mem_quota)
- self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ self.assertRaises(exceptions.Unauthorized, self.create_test_server)
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_create_server_when_instances_quota_is_full(self):
# Once instances quota limit is reached, disallow server creation
@@ -86,7 +89,7 @@
instances=instances_quota)
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
instances=default_instances_quota)
- self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ self.assertRaises(exceptions.Unauthorized, self.create_test_server)
@test.skip_because(bug="1186354",
condition=CONF.service_available.neutron)
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index f728d68..004ce8f 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -43,6 +43,7 @@
"Skipped because neutron do not support all_tenants"
"search filter.")
@test.attr(type='smoke')
+ @test.services('network')
def test_list_security_groups_list_all_tenants_filter(self):
# Admin can list security groups of all tenants
# List of all security groups created
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 8b3a0b5..cccaf13 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -54,6 +54,9 @@
flavor_id = data_utils.rand_int_id(start=1000)
return flavor_id
+ @test.skip_because(bug="1298131")
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
@test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_ram(self):
flavor_name = data_utils.rand_name("flavor-")
@@ -67,11 +70,14 @@
ram, vcpus, disk,
flavor_id)
self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises(exceptions.Unauthorized,
self.client.resize,
self.servers[0]['id'],
flavor_ref['id'])
+ @test.skip_because(bug="1298131")
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
@test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_vcpus(self):
flavor_name = data_utils.rand_name("flavor-")
@@ -85,7 +91,7 @@
ram, vcpus, disk,
flavor_id)
self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises(exceptions.Unauthorized,
self.client.resize,
self.servers[0]['id'],
flavor_ref['id'])
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
index d69c43c..1031b24 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
@@ -51,7 +51,6 @@
# Get usage for tenant with invalid date
params = {'start': self.end,
'end': self.start}
- resp, tenants = self.identity_client.list_tenants()
self.assertRaises(exceptions.BadRequest,
self.adm_client.get_tenant_usage,
self.client.tenant_id, params)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 7c70aec..70a9604 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -67,6 +67,8 @@
cls.keypairs_client = cls.os.keypairs_client
cls.security_groups_client = cls.os.security_groups_client
cls.quotas_client = cls.os.quotas_client
+ # NOTE(mriedem): os-quota-class-sets is v2 API only
+ cls.quota_classes_client = cls.os.quota_classes_client
cls.limits_client = cls.os.limits_client
cls.volumes_extensions_client = cls.os.volumes_extensions_client
cls.volumes_client = cls.os.volumes_client
@@ -270,7 +272,7 @@
if not policy:
policy = ['affinity']
resp, body = cls.servers_client.create_server_group(name, policy)
- cls.server_groups.append(body)
+ cls.server_groups.append(body['id'])
return resp, body
def wait_for(self, condition):
@@ -390,8 +392,11 @@
msg = ("Missing Compute Admin API credentials "
"in configuration.")
raise cls.skipException(msg)
+ if cls._api_version == 2:
+ cls.availability_zone_admin_client = (
+ cls.os_adm.availability_zone_client)
- if cls._api_version == 3:
+ else:
cls.servers_admin_client = cls.os_adm.servers_v3_client
cls.services_admin_client = cls.os_adm.services_v3_client
cls.availability_zone_admin_client = \
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index bfebb5e..c1c2d05 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -67,8 +67,8 @@
@test.attr(type='gate')
def test_list_flavors_using_marker(self):
# The list of flavors should start from the provided marker
- resp, flavors = self.client.list_flavors()
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
params = {'marker': flavor_id}
resp, flavors = self.client.list_flavors(params)
@@ -78,8 +78,8 @@
@test.attr(type='gate')
def test_list_flavors_detailed_using_marker(self):
# The list of flavors should start from the provided marker
- resp, flavors = self.client.list_flavors_with_detail()
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
params = {'marker': flavor_id}
resp, flavors = self.client.list_flavors_with_detail(params)
@@ -89,44 +89,40 @@
@test.attr(type='gate')
def test_list_flavors_detailed_filter_by_min_disk(self):
# The detailed list of flavors should be filtered by disk space
- resp, flavors = self.client.list_flavors_with_detail()
- flavors = sorted(flavors, key=lambda k: k['disk'])
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
- params = {self._min_disk: flavors[0]['disk'] + 1}
+ params = {self._min_disk: flavor['disk'] + 1}
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
@test.attr(type='gate')
def test_list_flavors_detailed_filter_by_min_ram(self):
# The detailed list of flavors should be filtered by RAM
- resp, flavors = self.client.list_flavors_with_detail()
- flavors = sorted(flavors, key=lambda k: k['ram'])
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
- params = {self._min_ram: flavors[0]['ram'] + 1}
+ params = {self._min_ram: flavor['ram'] + 1}
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
@test.attr(type='gate')
def test_list_flavors_filter_by_min_disk(self):
# The list of flavors should be filtered by disk space
- resp, flavors = self.client.list_flavors_with_detail()
- flavors = sorted(flavors, key=lambda k: k['disk'])
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
- params = {self._min_disk: flavors[0]['disk'] + 1}
+ params = {self._min_disk: flavor['disk'] + 1}
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
@test.attr(type='gate')
def test_list_flavors_filter_by_min_ram(self):
# The list of flavors should be filtered by RAM
- resp, flavors = self.client.list_flavors_with_detail()
- flavors = sorted(flavors, key=lambda k: k['ram'])
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
- params = {self._min_ram: flavors[0]['ram'] + 1}
+ params = {self._min_ram: flavor['ram'] + 1}
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index b3789f8..1eae66f 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -54,6 +54,7 @@
pass
@test.attr(type='gate')
+ @test.services('network')
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
# should be successful
@@ -69,6 +70,7 @@
self.assertIn(floating_ip_details, body)
@test.attr(type='gate')
+ @test.services('network')
def test_delete_floating_ip(self):
# Positive test:Deletion of valid floating IP from project
# should be successful
@@ -85,6 +87,7 @@
self.client.wait_for_resource_deletion(floating_ip_body['id'])
@test.attr(type='gate')
+ @test.services('network')
def test_associate_disassociate_floating_ip(self):
# Positive test:Associate and disassociate the provided floating IP
# to a specific server should be successful
@@ -101,6 +104,7 @@
self.assertEqual(202, resp.status)
@test.attr(type='gate')
+ @test.services('network')
def test_associate_already_associated_floating_ip(self):
# positive test:Association of an already associated floating IP
# to specific server should change the association of the Floating IP
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index 9fc43e2..042a19a 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -48,6 +48,7 @@
break
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_allocate_floating_ip_from_nonexistent_pool(self):
# Negative test:Allocation of a new floating IP from a nonexistent_pool
# to a project should fail
@@ -56,6 +57,7 @@
"non_exist_pool")
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_delete_nonexistent_floating_ip(self):
# Negative test:Deletion of a nonexistent floating IP
# from project should fail
@@ -65,6 +67,7 @@
self.non_exist_id)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_associate_nonexistent_floating_ip(self):
# Negative test:Association of a non existent floating IP
# to specific server should fail
@@ -74,6 +77,7 @@
"0.0.0.0", self.server_id)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_dissociate_nonexistent_floating_ip(self):
# Negative test:Dissociation of a non existent floating IP should fail
# Dissociating non existent floating IP
@@ -82,6 +86,7 @@
"0.0.0.0", self.server_id)
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_associate_ip_to_server_without_passing_floating_ip(self):
# Negative test:Association of empty floating IP to specific server
# should raise NotFound exception
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 94dcf61..a6878d9 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -37,6 +37,7 @@
super(FloatingIPDetailsTestJSON, cls).tearDownClass()
@test.attr(type='gate')
+ @test.services('network')
def test_list_floating_ips(self):
# Positive test:Should return the list of floating IPs
resp, body = self.client.list_floating_ips()
@@ -48,6 +49,7 @@
self.assertIn(self.floating_ip[i], floating_ips)
@test.attr(type='gate')
+ @test.services('network')
def test_get_floating_ip_details(self):
# Positive test:Should be able to GET the details of floatingIP
# Creating a floating IP for which details are to be checked
@@ -70,6 +72,7 @@
self.assertEqual(floating_ip_id, body['id'])
@test.attr(type='gate')
+ @test.services('network')
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()
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index 8cb2f08..b11ef5b 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -32,6 +32,7 @@
cls.client = cls.floating_ips_client
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_get_nonexistent_floating_ip_details(self):
# Negative test:Should not be able to GET the details
# of non-existent floating IP
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index ae00ae2..6163f4d 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -40,7 +40,7 @@
# Delete server before trying to create server
self.servers_client.delete_server(server['id'])
self.servers_client.wait_for_server_termination(server['id'])
- # Create a new image after server is deleted
+ # Create a new image after server is deleted
name = data_utils.rand_name('image')
meta = {'image_type': 'test'}
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index b04ab8a..a1808dc 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -29,6 +29,7 @@
cls.neutron_available = CONF.service_available.neutron
@test.attr(type='smoke')
+ @test.services('network')
def test_security_group_rules_create(self):
# Positive test: Creation of Security Group rule
# should be successful
@@ -48,6 +49,7 @@
self.assertEqual(200, resp.status)
@test.attr(type='smoke')
+ @test.services('network')
def test_security_group_rules_create_with_optional_arguments(self):
# Positive test: Creation of Security Group rule
# with optional arguments
@@ -75,10 +77,10 @@
to_port,
cidr=cidr,
group_id=group_id)
- self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
@test.attr(type='smoke')
+ @test.services('network')
def test_security_group_rules_list(self):
# Positive test: Created Security Group rules should be
# in the list of all rules
@@ -95,8 +97,6 @@
ip_protocol1,
from_port1, to_port1)
rule1_id = rule['id']
- # Delete the Security Group rule1 at the end of this method
- self.addCleanup(self.client.delete_security_group_rule, rule1_id)
# Add a second rule to the created Security Group
ip_protocol2 = 'icmp'
@@ -117,6 +117,7 @@
self.assertTrue(any([i for i in rules if i['id'] == rule2_id]))
@test.attr(type='smoke')
+ @test.services('network')
def test_security_group_rules_delete_when_peer_group_deleted(self):
# Positive test:rule will delete when peer group deleting
# Creating a Security Group to add rules to it
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 0b53037..cfa839a 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -37,6 +37,7 @@
cls.client = cls.security_groups_client
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_with_non_existent_id(self):
# Negative test: Creation of Security Group rule should FAIL
# with non existent Parent group id
@@ -50,6 +51,7 @@
parent_group_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_with_invalid_id(self):
# Negative test: Creation of Security Group rule should FAIL
# with Parent group id which is not integer
@@ -63,6 +65,7 @@
parent_group_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_duplicate(self):
# Negative test: Create Security Group rule duplicate should fail
# Creating a Security Group to add rule to it
@@ -86,6 +89,7 @@
parent_group_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_with_invalid_ip_protocol(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid ip_protocol
@@ -102,6 +106,7 @@
parent_group_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_with_invalid_from_port(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid from_port
@@ -117,6 +122,7 @@
parent_group_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_with_invalid_to_port(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid to_port
@@ -132,6 +138,7 @@
parent_group_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_create_security_group_rule_with_invalid_port_range(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid port range.
@@ -147,6 +154,7 @@
secgroup_id, ip_protocol, from_port, to_port)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_delete_security_group_rule_with_non_existent_id(self):
# Negative test: Deletion of Security Group rule should be FAIL
# with non existent id
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index a077943..860aebc 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -27,6 +27,7 @@
cls.client = cls.security_groups_client
@test.attr(type='smoke')
+ @test.services('network')
def test_security_groups_create_list_delete(self):
# Positive test:Should return the list of Security Groups
# Create 3 Security Groups
@@ -61,6 +62,7 @@
for m_group in deleted_sgs))
@test.attr(type='smoke')
+ @test.services('network')
def test_security_group_create_get_delete(self):
# Security Group should be created, fetched and deleted
# with char space between name along with
@@ -85,6 +87,7 @@
self.client.wait_for_resource_deletion(securitygroup['id'])
@test.attr(type='smoke')
+ @test.services('network')
def test_server_security_groups(self):
# Checks that security groups may be added and linked to a server
# and not deleted if the server is active.
@@ -131,6 +134,7 @@
self.assertEqual(202, resp.status)
@test.attr(type='smoke')
+ @test.services('network')
def test_update_security_groups(self):
# Update security group name and description
# Create a security group
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index aa2d32e..a9cca55 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -47,6 +47,7 @@
return non_exist_id
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_security_group_get_nonexistent_group(self):
# Negative test:Should not be able to GET the details
# of non-existent Security Group
@@ -57,6 +58,7 @@
@test.skip_because(bug="1161411",
condition=CONF.service_available.neutron)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_security_group_create_with_invalid_group_name(self):
# Negative test: Security Group should not be created with group name
# as an empty string/with white spaces/chars more than 255
@@ -77,6 +79,7 @@
@test.skip_because(bug="1161411",
condition=CONF.service_available.neutron)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_security_group_create_with_invalid_group_description(self):
# Negative test:Security Group should not be created with description
# as an empty string/with white spaces/chars more than 255
@@ -96,6 +99,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron allows duplicate names for security groups")
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_security_group_create_with_duplicate_name(self):
# Negative test:Security Group with duplicate name should not
# be created
@@ -110,6 +114,7 @@
s_description)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_delete_the_default_security_group(self):
# Negative test:Deletion of the "default" Security Group should Fail
default_security_group_id = None
@@ -124,6 +129,7 @@
default_security_group_id)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_delete_nonexistent_security_group(self):
# Negative test:Deletion of a non-existent Security Group should fail
non_exist_id = self._generate_a_non_existent_security_group_id()
@@ -131,6 +137,7 @@
self.client.delete_security_group, non_exist_id)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_delete_security_group_without_passing_id(self):
# Negative test:Deletion of a Security Group with out passing ID
# should Fail
@@ -140,6 +147,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron not check the security_group_id")
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_update_security_group_with_invalid_sg_id(self):
# Update security_group with invalid sg_id should fail
s_name = data_utils.rand_name('sg-')
@@ -153,6 +161,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron not check the security_group_name")
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_update_security_group_with_invalid_sg_name(self):
# Update security_group with invalid sg_name should fail
resp, securitygroup = self.create_security_group()
@@ -168,6 +177,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron not check the security_group_description")
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_update_security_group_with_invalid_sg_des(self):
# Update security_group with invalid sg_des should fail
resp, securitygroup = self.create_security_group()
@@ -181,6 +191,7 @@
securitygroup_id, description=s_new_des)
@test.attr(type=['negative', 'smoke'])
+ @test.services('network')
def test_update_non_existent_security_group(self):
# Update a non-existent Security Group should Fail
non_exist_id = self._generate_a_non_existent_security_group_id()
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 297b300..067d721 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -107,6 +107,7 @@
self.assertEqual(sorted(list1), sorted(list2))
@test.attr(type='smoke')
+ @test.services('network')
def test_create_list_show_delete_interfaces(self):
server, ifs = self._create_server_get_interfaces()
interface_count = len(ifs)
@@ -128,6 +129,7 @@
self.assertEqual(len(ifs) - 1, len(_ifs))
@test.attr(type='smoke')
+ @test.services('network')
def test_add_remove_fixed_ip(self):
# Add and Remove the fixed IP to server.
server, ifs = self._create_server_get_interfaces()
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index e135eca..279dc51 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -102,6 +102,28 @@
self.password)
self.assertTrue(linux_client.hostname_equals_servername(self.name))
+ @test.skip_because(bug="1306367", interface="xml")
+ @test.attr(type='gate')
+ def test_create_server_with_scheduler_hint_group(self):
+ # Create a server with the scheduler hint "group".
+ name = data_utils.rand_name('server_group')
+ policies = ['affinity']
+ resp, body = self.client.create_server_group(name=name,
+ policies=policies)
+ self.assertEqual(200, resp.status)
+ group_id = body['id']
+ self.addCleanup(self.client.delete_server_group, group_id)
+
+ hints = {'group': group_id}
+ resp, server = self.create_test_server(sched_hints=hints,
+ wait_until='ACTIVE')
+ self.assertEqual(202, resp.status)
+
+ # Check a server is in the group
+ resp, server_group = self.client.get_server_group(group_id)
+ self.assertEqual(200, resp.status)
+ self.assertIn(server['id'], server_group['members'])
+
class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
disk_config = 'AUTO'
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 768cc11..28d64fb 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
-
from six import moves
from tempest.api.compute import base
@@ -37,9 +35,8 @@
# tearDownClass method of the super-class.
cls.existing_fixtures = []
cls.deleted_fixtures = []
- cls.start_time = datetime.datetime.utcnow()
for x in moves.xrange(2):
- resp, srv = cls.create_test_server()
+ resp, srv = cls.create_test_server(wait_until='ACTIVE')
cls.existing_fixtures.append(srv)
resp, srv = cls.create_test_server()
@@ -127,19 +124,6 @@
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'limit': -1})
- @test.attr(type='gate')
- def test_list_servers_by_changes_since(self):
- # Servers are listed by specifying changes-since date
- changes_since = {'changes-since': self.start_time.isoformat()}
- resp, body = self.client.list_servers(changes_since)
- self.assertEqual('200', resp['status'])
- # changes-since returns all instances, including deleted.
- num_expected = (len(self.existing_fixtures) +
- len(self.deleted_fixtures))
- self.assertEqual(num_expected, len(body['servers']),
- "Number of servers %d is wrong in %s" %
- (num_expected, body['servers']))
-
@test.attr(type=['negative', 'gate'])
def test_list_servers_by_changes_since_invalid_date(self):
# Return an error when invalid date format is passed
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 80e6008..71fcbff 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -14,6 +14,7 @@
# under the License.
import base64
+import logging
import testtools
import urlparse
@@ -27,6 +28,8 @@
CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
class ServerActionsTestJSON(base.BaseV2ComputeTest):
run_ssh = CONF.compute.run_ssh
@@ -193,26 +196,46 @@
if current_flavor == self.flavor_ref else self.flavor_ref
return current_flavor, new_flavor_ref
- @testtools.skipUnless(CONF.compute_feature_enabled.resize,
- 'Resize not available.')
- @test.attr(type='smoke')
- def test_resize_server_confirm(self):
+ def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
previous_flavor_ref, new_flavor_ref = \
self._detect_server_image_flavor(self.server_id)
+ if stop:
+ resp = self.servers_client.stop(self.server_id)[0]
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'SHUTOFF')
+
resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
self.client.confirm_resize(self.server_id)
- self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ expected_status = 'SHUTOFF' if stop else 'ACTIVE'
+ self.client.wait_for_server_status(self.server_id, expected_status)
resp, server = self.client.get_server(self.server_id)
self.assertEqual(new_flavor_ref, server['flavor']['id'])
+ if stop:
+ # NOTE(mriedem): tearDown requires the server to be started.
+ self.client.start(self.server_id)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm(self):
+ self._test_resize_server_confirm(stop=False)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm_from_stopped(self):
+ self._test_resize_server_confirm(stop=True)
+
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@test.attr(type='gate')
@@ -247,7 +270,14 @@
# the oldest one should be deleted automatically in this test
def _clean_oldest_backup(oldest_backup):
if oldest_backup_exist:
- self.os.image_client.delete_image(oldest_backup)
+ try:
+ self.os.image_client.delete_image(oldest_backup)
+ except exceptions.NotFound:
+ pass
+ else:
+ LOG.warning("Deletion of oldest backup %s should not have "
+ "been successful as it should have been "
+ "deleted during rotation." % oldest_backup)
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index 0c14dc2..846bf3e 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -34,6 +34,7 @@
@test.skip_because(bug="1210483",
condition=CONF.service_available.neutron)
@test.attr(type='smoke')
+ @test.services('network')
def test_list_server_addresses(self):
# All public and private addresses for
# a server should be returned
@@ -51,6 +52,7 @@
self.assertTrue(address['version'])
@test.attr(type='smoke')
+ @test.services('network')
def test_list_server_addresses_by_network(self):
# Providing a network type should filter
# the addresses return by that type
diff --git a/tempest/api/compute/servers/test_server_addresses_negative.py b/tempest/api/compute/servers/test_server_addresses_negative.py
index d37f7fa..e190161 100644
--- a/tempest/api/compute/servers/test_server_addresses_negative.py
+++ b/tempest/api/compute/servers/test_server_addresses_negative.py
@@ -29,12 +29,14 @@
resp, cls.server = cls.create_test_server(wait_until='ACTIVE')
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_list_server_addresses_invalid_server_id(self):
# List addresses request should fail if server id not in system
self.assertRaises(exceptions.NotFound, self.client.list_addresses,
'999')
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_list_server_addresses_by_network_neg(self):
# List addresses by network should fail if network name not valid
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 0cd23fd..f1ef5d5 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -75,6 +75,7 @@
policy = ['anti-affinity']
self._create_delete_server_group(policy)
+ @test.skip_because(bug="1324348")
@test.attr(type='gate')
def test_create_delete_server_group_with_multiple_policies(self):
# Create and Delete the server-group with multiple policies
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 448b8ff..01ff6b9 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -24,10 +24,7 @@
super(ServerMetadataTestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.quotas = cls.quotas_client
- cls.admin_client = cls._get_identity_admin_client()
- resp, tenants = cls.admin_client.list_tenants()
resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE')
-
cls.server_id = server['id']
def setUp(self):
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 093e9e2..ab98d88 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -15,14 +15,21 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class ServerRescueTestJSON(base.BaseV2ComputeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
+ if not CONF.compute_feature_enabled.rescue:
+ msg = "Server rescue not available."
+ raise cls.skipException(msg)
+
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerRescueTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index dae4709..b35e55c 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -28,6 +28,10 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
+ if not CONF.compute_feature_enabled.rescue:
+ msg = "Server rescue not available."
+ raise cls.skipException(msg)
+
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerRescueNegativeTestJSON, cls).setUpClass()
cls.device = 'vdf'
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 40b97d7..936b871 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -70,20 +70,34 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual(key_name, server['key_name'])
+ def _update_server_name(self, server_id, status):
+ # The server name should be changed to the the provided value
+ new_name = data_utils.rand_name('server')
+ # Update the server with a new name
+ resp, server = self.client.update_server(server_id,
+ name=new_name)
+ self.client.wait_for_server_status(server_id, status)
+
+ # Verify the name of the server has changed
+ resp, server = self.client.get_server(server_id)
+ self.assertEqual(new_name, server['name'])
+ return server
+
@test.attr(type='gate')
def test_update_server_name(self):
# The server name should be changed to the the provided value
resp, server = self.create_test_server(wait_until='ACTIVE')
- # Update the server with a new name
- resp, server = self.client.update_server(server['id'],
- name='newname')
- self.assertEqual(200, resp.status)
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ self._update_server_name(server['id'], 'ACTIVE')
- # Verify the name of the server has changed
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('newname', server['name'])
+ @test.attr(type='gate')
+ def test_update_server_name_in_stop_state(self):
+ # The server name should be changed to the the provided value
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.client.stop(server['id'])
+ self.client.wait_for_server_status(server['id'], 'SHUTOFF')
+ updated_server = self._update_server_name(server['id'], 'SHUTOFF')
+ self.assertNotIn('progress', updated_server)
@test.attr(type='gate')
def test_update_access_server_address(self):
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5ac667e..d3297ce 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -45,7 +45,10 @@
def setUpClass(cls):
super(ServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.alt_os = clients.AltManager()
+ if CONF.compute.allow_tenant_isolation:
+ cls.alt_os = clients.Manager(cls.isolated_creds.get_alt_creds())
+ else:
+ cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_client
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@@ -103,6 +106,8 @@
self.assertRaises(exceptions.BadRequest,
self.create_test_server, accessIPv6=IPv6)
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
@test.attr(type=['negative', 'gate'])
def test_resize_nonexistent_server(self):
# Resize a non-existent server
@@ -111,6 +116,8 @@
self.client.resize,
nonexistent_server, self.flavor_ref)
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
@test.attr(type=['negative', 'gate'])
def test_resize_server_with_non_existent_flavor(self):
# Resize a server with non-existent flavor
@@ -118,6 +125,8 @@
self.assertRaises(exceptions.BadRequest, self.client.resize,
self.server_id, flavor_ref=nonexistent_flavor)
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
@test.attr(type=['negative', 'gate'])
def test_resize_server_with_null_flavor(self):
# Resize a server with null flavor
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 6354996..421ba8b 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -36,6 +36,7 @@
@test.skip_because(bug="1183436",
condition=CONF.service_available.neutron)
@test.attr(type='gate')
+ @test.services('network')
def test_list_virtual_interfaces(self):
# Positive test:Should be able to GET the virtual interfaces list
# for a given server_id
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index 87289d8..bcb2686 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -30,6 +30,7 @@
cls.client = cls.servers_client
@test.attr(type=['negative', 'gate'])
+ @test.services('network')
def test_list_virtual_interfaces_invalid_server_id(self):
# Negative test: Should not be able to GET virtual interfaces
# for an invalid server_id
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index 375ddf8..fb8ded3 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import StringIO
+
from tempest.api.compute import base
from tempest import clients
from tempest.common.utils import data_utils
@@ -27,9 +29,10 @@
class AuthorizationTestJSON(base.BaseV2ComputeTest):
-
@classmethod
def setUpClass(cls):
+ if not CONF.service_available.glance:
+ raise cls.skipException('Glance is not available.')
# No network resources required for this test
cls.set_network_resources()
super(AuthorizationTestJSON, cls).setUpClass()
@@ -38,6 +41,7 @@
raise cls.skipException(msg)
cls.client = cls.os.servers_client
cls.images_client = cls.os.images_client
+ cls.glance_client = cls.os.image_client
cls.keypairs_client = cls.os.keypairs_client
cls.security_client = cls.os.security_groups_client
@@ -57,9 +61,14 @@
resp, cls.server = cls.client.get_server(server['id'])
name = data_utils.rand_name('image')
- resp, body = cls.images_client.create_image(server['id'], name)
- image_id = data_utils.parse_image_id(resp['location'])
- cls.images_client.wait_for_image_status(image_id, 'ACTIVE')
+ resp, body = cls.glance_client.create_image(name=name,
+ container_format='bare',
+ disk_format='raw',
+ is_public=False)
+ image_id = body['id']
+ image_file = StringIO.StringIO(('*' * 1024))
+ resp, body = cls.glance_client.update_image(image_id, data=image_file)
+ cls.glance_client.wait_for_image_status(image_id, 'active')
resp, cls.image = cls.images_client.get_image(image_id)
cls.keypairname = data_utils.rand_name('keypair')
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index dc85e76..eeff3ce 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -14,11 +14,17 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest import test
class QuotasTestJSON(base.BaseV2ComputeTest):
+ def setUp(self):
+ # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotasTestJSON, self).setUp()
+
@classmethod
def setUpClass(cls):
super(QuotasTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/v3/admin/test_flavors.py b/tempest/api/compute/v3/admin/test_flavors.py
index 2a4fc02..09d76b8 100644
--- a/tempest/api/compute/v3/admin/test_flavors.py
+++ b/tempest/api/compute/v3/admin/test_flavors.py
@@ -229,7 +229,7 @@
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
- # Create the flavor
+ # Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
self.ram, self.vcpus,
self.disk,
@@ -294,7 +294,7 @@
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
- ram = " 1024 "
+ ram = "1024"
resp, flavor = self.client.create_flavor(flavor_name,
ram, self.vcpus,
self.disk,
diff --git a/tempest/api/compute/v3/admin/test_flavors_negative.py b/tempest/api/compute/v3/admin/test_flavors_negative.py
index 3f8a2da..6d3308e 100644
--- a/tempest/api/compute/v3/admin/test_flavors_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_negative.py
@@ -57,7 +57,7 @@
resp, flavor = self.client.create_flavor(flavor_name,
self.ram,
self.vcpus, self.disk,
- '',
+ None,
ephemeral=self.ephemeral,
swap=self.swap,
rxtx=self.rxtx)
diff --git a/tempest/api/compute/v3/admin/test_hypervisor.py b/tempest/api/compute/v3/admin/test_hypervisor.py
index f3397a8..9a23789 100644
--- a/tempest/api/compute/v3/admin/test_hypervisor.py
+++ b/tempest/api/compute/v3/admin/test_hypervisor.py
@@ -83,7 +83,27 @@
# Verify that GET shows the specified hypervisor uptime
hypers = self._list_hypervisors()
- resp, uptime = self.client.get_hypervisor_uptime(hypers[0]['id'])
+ # Ironic will register each baremetal node as a 'hypervisor',
+ # so the hypervisor list can contain many hypervisors of type
+ # 'ironic'. If they are ALL ironic, skip this test since ironic
+ # doesn't support hypervisor uptime. Otherwise, remove them
+ # from the list of hypervisors to test.
+ ironic_only = True
+ hypers_without_ironic = []
+ for hyper in hypers:
+ resp, details = (self.client.
+ get_hypervisor_show_details(hypers[0]['id']))
+ self.assertEqual(200, resp.status)
+ if details['hypervisor_type'] != 'ironic':
+ hypers_without_ironic.append(hyper)
+ ironic_only = False
+
+ if ironic_only:
+ raise self.skipException(
+ "Ironic does not support hypervisor uptime")
+
+ resp, uptime = self.client.get_hypervisor_uptime(
+ hypers_without_ironic[0]['id'])
self.assertEqual(200, resp.status)
self.assertTrue(len(uptime) > 0)
diff --git a/tempest/api/compute/v3/admin/test_quotas_negative.py b/tempest/api/compute/v3/admin/test_quotas_negative.py
index 307462f..7739f09 100644
--- a/tempest/api/compute/v3/admin/test_quotas_negative.py
+++ b/tempest/api/compute/v3/admin/test_quotas_negative.py
@@ -34,6 +34,7 @@
# TODO(afazekas): Add dedicated tenant to the skiped quota tests
# it can be moved into the setUpClass as well
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_create_server_when_cpu_quota_is_full(self):
# Disallow server creation when tenant's vcpu quota is full
@@ -47,8 +48,9 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
cores=default_vcpu_quota)
- self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ self.assertRaises(exceptions.Unauthorized, self.create_test_server)
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_create_server_when_memory_quota_is_full(self):
# Disallow server creation when tenant's memory quota is full
@@ -62,7 +64,7 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
ram=default_mem_quota)
- self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ self.assertRaises(exceptions.Unauthorized, self.create_test_server)
@test.attr(type=['negative', 'gate'])
def test_update_quota_normal_user(self):
@@ -71,6 +73,7 @@
self.demo_tenant_id,
ram=0)
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_create_server_when_instances_quota_is_full(self):
# Once instances quota limit is reached, disallow server creation
@@ -83,4 +86,4 @@
instances=instances_quota)
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
instances=default_instances_quota)
- self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ self.assertRaises(exceptions.Unauthorized, self.create_test_server)
diff --git a/tempest/api/compute/v3/admin/test_servers_negative.py b/tempest/api/compute/v3/admin/test_servers_negative.py
index a971463..5eb6395 100644
--- a/tempest/api/compute/v3/admin/test_servers_negative.py
+++ b/tempest/api/compute/v3/admin/test_servers_negative.py
@@ -54,6 +54,7 @@
flavor_id = data_utils.rand_int_id(start=1000)
return flavor_id
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_ram(self):
flavor_name = data_utils.rand_name("flavor-")
@@ -67,11 +68,12 @@
ram, vcpus, disk,
flavor_id)
self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises(exceptions.Unauthorized,
self.client.resize,
self.servers[0]['id'],
flavor_ref['id'])
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_vcpus(self):
flavor_name = data_utils.rand_name("flavor-")
@@ -85,7 +87,7 @@
ram, vcpus, disk,
flavor_id)
self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises(exceptions.Unauthorized,
self.client.resize,
self.servers[0]['id'],
flavor_ref['id'])
diff --git a/tempest/api/compute/v3/servers/test_instance_actions.py b/tempest/api/compute/v3/servers/test_instance_actions.py
index 399541b..4c2dcbe 100644
--- a/tempest/api/compute/v3/servers/test_instance_actions.py
+++ b/tempest/api/compute/v3/servers/test_instance_actions.py
@@ -27,7 +27,6 @@
cls.resp = resp
cls.server_id = server['id']
- @test.skip_because(bug="1206032")
@test.attr(type='gate')
def test_list_server_actions(self):
# List actions of the provided server
@@ -40,12 +39,10 @@
self.assertTrue(any([i for i in body if i['action'] == 'create']))
self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
- @test.skip_because(bug="1206032")
@test.attr(type='gate')
- @test.skip_because(bug="1281915")
def test_get_server_action(self):
# Get the action details of the provided server
- request_id = self.resp['x-compute-request-id']
+ request_id = self.resp['x-openstack-request-id']
resp, body = self.client.get_server_action(self.server_id,
request_id)
self.assertEqual(200, resp.status)
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 9cbc4e0..18e5c67 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -39,7 +39,7 @@
cls.deleted_fixtures = []
cls.start_time = datetime.datetime.utcnow()
for x in moves.xrange(2):
- resp, srv = cls.create_test_server()
+ resp, srv = cls.create_test_server(wait_until='ACTIVE')
cls.existing_fixtures.append(srv)
resp, srv = cls.create_test_server()
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 721fe42..3ee8050 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import logging
+
import testtools
import urlparse
@@ -25,6 +27,8 @@
CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
class ServerActionsV3Test(base.BaseV3ComputeTest):
run_ssh = CONF.compute.run_ssh
@@ -186,26 +190,46 @@
if current_flavor == self.flavor_ref else self.flavor_ref
return current_flavor, new_flavor_ref
- @testtools.skipUnless(CONF.compute_feature_enabled.resize,
- 'Resize not available.')
- @test.attr(type='smoke')
- def test_resize_server_confirm(self):
+ def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
previous_flavor_ref, new_flavor_ref = \
self._detect_server_image_flavor(self.server_id)
+ if stop:
+ resp = self.servers_client.stop(self.server_id)[0]
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'SHUTOFF')
+
resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
self.client.confirm_resize(self.server_id)
- self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ expected_status = 'SHUTOFF' if stop else 'ACTIVE'
+ self.client.wait_for_server_status(self.server_id, expected_status)
resp, server = self.client.get_server(self.server_id)
self.assertEqual(new_flavor_ref, server['flavor']['id'])
+ if stop:
+ # NOTE(mriedem): tearDown requires the server to be started.
+ self.client.start(self.server_id)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm(self):
+ self._test_resize_server_confirm(stop=False)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm_from_stopped(self):
+ self._test_resize_server_confirm(stop=True)
+
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@test.attr(type='gate')
@@ -240,7 +264,14 @@
# the oldest one should be deleted automatically in this test
def _clean_oldest_backup(oldest_backup):
if oldest_backup_exist:
- self.images_client.delete_image(oldest_backup)
+ try:
+ self.images_client.delete_image(oldest_backup)
+ except exceptions.NotFound:
+ pass
+ else:
+ LOG.warning("Deletion of oldest backup %s should not have "
+ "been successful as it should have been "
+ "deleted during rotation." % oldest_backup)
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
diff --git a/tempest/api/compute/v3/servers/test_server_metadata.py b/tempest/api/compute/v3/servers/test_server_metadata.py
index 298cd3c..c5443ee 100644
--- a/tempest/api/compute/v3/servers/test_server_metadata.py
+++ b/tempest/api/compute/v3/servers/test_server_metadata.py
@@ -24,9 +24,7 @@
super(ServerMetadataV3Test, cls).setUpClass()
cls.client = cls.servers_client
cls.quotas = cls.quotas_client
- cls.tenant_id = cls.client.tenant_id
resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE')
-
cls.server_id = server['id']
def setUp(self):
diff --git a/tempest/api/compute/v3/servers/test_server_rescue.py b/tempest/api/compute/v3/servers/test_server_rescue.py
index b3dcb51..da58f26 100644
--- a/tempest/api/compute/v3/servers/test_server_rescue.py
+++ b/tempest/api/compute/v3/servers/test_server_rescue.py
@@ -14,13 +14,19 @@
# under the License.
from tempest.api.compute import base
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class ServerRescueV3Test(base.BaseV3ComputeTest):
@classmethod
def setUpClass(cls):
+ if not CONF.compute_feature_enabled.rescue:
+ msg = "Server rescue not available."
+ raise cls.skipException(msg)
super(ServerRescueV3Test, cls).setUpClass()
# Server for positive tests
diff --git a/tempest/api/compute/v3/servers/test_server_rescue_negative.py b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
index eb6bcdd..5eb6c9a 100644
--- a/tempest/api/compute/v3/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
@@ -28,6 +28,10 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
+ if not CONF.compute_feature_enabled.rescue:
+ msg = "Server rescue not available."
+ raise cls.skipException(msg)
+
super(ServerRescueNegativeV3Test, cls).setUpClass()
cls.device = 'vdf'
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 827c4c4..90deaa9 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -45,7 +45,10 @@
def setUpClass(cls):
super(ServersNegativeV3Test, cls).setUpClass()
cls.client = cls.servers_client
- cls.alt_os = clients.AltManager()
+ if CONF.compute.allow_tenant_isolation:
+ cls.alt_os = clients.Manager(cls.isolated_creds.get_alt_creds())
+ else:
+ cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_v3_client
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
diff --git a/tempest/api/compute/v3/test_quotas.py b/tempest/api/compute/v3/test_quotas.py
index 62a7556..ecf70cf 100644
--- a/tempest/api/compute/v3/test_quotas.py
+++ b/tempest/api/compute/v3/test_quotas.py
@@ -14,11 +14,17 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest import test
class QuotasV3Test(base.BaseV3ComputeTest):
+ def setUp(self):
+ # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotasV3Test, self).setUp()
+
@classmethod
def setUpClass(cls):
super(QuotasV3Test, cls).setUpClass()
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 4585912..5a64544 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -53,28 +53,28 @@
def _create_and_attach(self):
# Start a server and wait for it to become ready
admin_pass = self.image_ssh_password
- resp, server = self.create_test_server(wait_until='ACTIVE',
- adminPass=admin_pass)
- self.server = server
+ _, self.server = self.create_test_server(wait_until='ACTIVE',
+ adminPass=admin_pass)
# Record addresses so that we can ssh later
- resp, server['addresses'] = \
- self.servers_client.list_addresses(server['id'])
+ _, self.server['addresses'] = \
+ self.servers_client.list_addresses(self.server['id'])
# Create a volume and wait for it to become ready
- resp, volume = self.volumes_client.create_volume(1,
- display_name='test')
- self.volume = volume
+ _, self.volume = self.volumes_client.create_volume(
+ 1, display_name='test')
self.addCleanup(self._delete_volume)
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ self.volumes_client.wait_for_volume_status(self.volume['id'],
+ 'available')
# Attach the volume to the server
- self.servers_client.attach_volume(server['id'], volume['id'],
+ self.servers_client.attach_volume(self.server['id'],
+ self.volume['id'],
device='/dev/%s' % self.device)
- self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
+ self.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use')
self.attached = True
- self.addCleanup(self._detach, server['id'], volume['id'])
+ self.addCleanup(self._detach, self.server['id'], self.volume['id'])
@testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test')
@test.attr(type='gate')
@@ -82,31 +82,33 @@
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
self._create_and_attach()
- server = self.server
- volume = self.volume
- self.servers_client.stop(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF')
+ self.servers_client.stop(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'],
+ 'SHUTOFF')
- self.servers_client.start(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ self.servers_client.start(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
- linux_client = remote_client.RemoteClient(server, self.image_ssh_user,
- server['adminPass'])
+ linux_client = remote_client.RemoteClient(self.server,
+ self.image_ssh_user,
+ self.server['adminPass'])
partitions = linux_client.get_partitions()
self.assertIn(self.device, partitions)
- self._detach(server['id'], volume['id'])
+ self._detach(self.server['id'], self.volume['id'])
self.attached = False
- self.servers_client.stop(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF')
+ self.servers_client.stop(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'],
+ 'SHUTOFF')
- self.servers_client.start(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ self.servers_client.start(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
- linux_client = remote_client.RemoteClient(server, self.image_ssh_user,
- server['adminPass'])
+ linux_client = remote_client.RemoteClient(self.server,
+ self.image_ssh_user,
+ self.server['adminPass'])
partitions = linux_client.get_partitions()
self.assertNotIn(self.device, partitions)
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index cc76880..ab0e83a 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -29,8 +29,8 @@
if not CONF.service_available.sahara:
raise cls.skipException('Sahara support is required')
- os = cls.get_client_manager()
- cls.client = os.data_processing_client
+ cls.os = cls.get_client_manager()
+ cls.client = cls.os.data_processing_client
cls.flavor_ref = CONF.compute.flavor_ref
@@ -39,6 +39,7 @@
cls._cluster_templates = []
cls._data_sources = []
cls._job_binary_internals = []
+ cls._job_binaries = []
@classmethod
def tearDownClass(cls):
@@ -50,6 +51,8 @@
cls.client.delete_data_source)
cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
cls.client.delete_job_binary_internal)
+ cls.cleanup_resources(getattr(cls, '_job_binaries', []),
+ cls.client.delete_job_binary)
cls.clear_isolated_creds()
super(BaseDataProcessingTest, cls).tearDownClass()
@@ -128,3 +131,16 @@
cls._job_binary_internals.append(body['id'])
return resp, body
+
+ def create_job_binary(cls, name, url, extra=None, **kwargs):
+ """Creates watched job binary with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object. All resources created in this method will be automatically
+ removed in tearDownClass method.
+ """
+ resp, body = cls.client.create_job_binary(name, url, extra, **kwargs)
+ # store id of created job binary
+ cls._job_binaries.append(body['id'])
+
+ return resp, body
diff --git a/tempest/api/data_processing/test_cluster_templates.py b/tempest/api/data_processing/test_cluster_templates.py
new file mode 100644
index 0000000..ad9ed2a
--- /dev/null
+++ b/tempest/api/data_processing/test_cluster_templates.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class ClusterTemplateTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.0.html#cluster-templates
+ """
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(ClusterTemplateTest, cls).setUpClass()
+ # create node group template
+ node_group_template = {
+ 'name': data_utils.rand_name('sahara-ng-template'),
+ 'description': 'Test node group template',
+ 'plugin_name': 'vanilla',
+ 'hadoop_version': '1.2.1',
+ 'node_processes': ['datanode'],
+ 'flavor_id': cls.flavor_ref,
+ 'node_configs': {
+ 'HDFS': {
+ 'Data Node Heap Size': 1024
+ }
+ }
+ }
+ resp_body = cls.create_node_group_template(**node_group_template)[1]
+ node_group_template_id = resp_body['id']
+
+ cls.full_cluster_template = {
+ 'description': 'Test cluster template',
+ 'plugin_name': 'vanilla',
+ 'hadoop_version': '1.2.1',
+ 'cluster_configs': {
+ 'HDFS': {
+ 'dfs.replication': 2
+ },
+ 'MapReduce': {
+ 'mapred.map.tasks.speculative.execution': False,
+ 'mapred.child.java.opts': '-Xmx500m'
+ },
+ 'general': {
+ 'Enable Swift': False
+ }
+ },
+ 'node_groups': [
+ {
+ 'name': 'master-node',
+ 'flavor_id': cls.flavor_ref,
+ 'node_processes': ['namenode'],
+ 'count': 1
+ },
+ {
+ 'name': 'worker-node',
+ 'node_group_template_id': node_group_template_id,
+ 'count': 3
+ }
+ ]
+ }
+ # create cls.cluster_template variable to use for comparison to cluster
+ # template response body. The 'node_groups' field in the response body
+ # has some extra info that post body does not have. The 'node_groups'
+ # field in the response body is something like this
+ #
+ # 'node_groups': [
+ # {
+ # 'count': 3,
+ # 'name': 'worker-node',
+ # 'volume_mount_prefix': '/volumes/disk',
+ # 'created_at': '2014-05-21 14:31:37',
+ # 'updated_at': None,
+ # 'floating_ip_pool': None,
+ # ...
+ # },
+ # ...
+ # ]
+ cls.cluster_template = cls.full_cluster_template.copy()
+ del cls.cluster_template['node_groups']
+
+ def _create_cluster_template(self, template_name=None):
+ """Creates Cluster Template with optional name specified.
+
+ It creates template and ensures response status, template name and
+ response body. Returns id and name of created template.
+ """
+ if not template_name:
+ # generate random name if it's not specified
+ template_name = data_utils.rand_name('sahara-cluster-template')
+
+ # create cluster template
+ resp, body = self.create_cluster_template(template_name,
+ **self.full_cluster_template)
+
+ # ensure that template created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(template_name, body['name'])
+ self.assertDictContainsSubset(self.cluster_template, body)
+
+ return body['id'], template_name
+
+ @test.attr(type='smoke')
+ def test_cluster_template_create(self):
+ self._create_cluster_template()
+
+ @test.attr(type='smoke')
+ def test_cluster_template_list(self):
+ template_info = self._create_cluster_template()
+
+ # check for cluster template in list
+ resp, templates = self.client.list_cluster_templates()
+ self.assertEqual(200, resp.status)
+ templates_info = [(template['id'], template['name'])
+ for template in templates]
+ self.assertIn(template_info, templates_info)
+
+ @test.attr(type='smoke')
+ def test_cluster_template_get(self):
+ template_id, template_name = self._create_cluster_template()
+
+ # check cluster template fetch by id
+ resp, template = self.client.get_cluster_template(template_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(template_name, template['name'])
+ self.assertDictContainsSubset(self.cluster_template, template)
+
+ @test.attr(type='smoke')
+ def test_cluster_template_delete(self):
+ template_id = self._create_cluster_template()[0]
+
+ # delete the cluster template by id
+ resp = self.client.delete_cluster_template(template_id)[0]
+ self.assertEqual(204, resp.status)
+ # TODO(ylobankov): check that cluster template is really deleted
diff --git a/tempest/api/data_processing/test_data_sources.py b/tempest/api/data_processing/test_data_sources.py
index c72e828..345153b 100644
--- a/tempest/api/data_processing/test_data_sources.py
+++ b/tempest/api/data_processing/test_data_sources.py
@@ -14,11 +14,8 @@
from tempest.api.data_processing import base as dp_base
from tempest.common.utils import data_utils
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class DataSourceTest(dp_base.BaseDataProcessingTest):
@classmethod
@@ -28,8 +25,8 @@
'url': 'swift://sahara-container.sahara/input-source',
'description': 'Test data source',
'credentials': {
- 'user': CONF.identity.username,
- 'password': CONF.identity.password
+ 'user': cls.os.credentials.username,
+ 'password': cls.os.credentials.password
},
'type': 'swift'
}
diff --git a/tempest/api/data_processing/test_job_binaries.py b/tempest/api/data_processing/test_job_binaries.py
new file mode 100644
index 0000000..689c1fe
--- /dev/null
+++ b/tempest/api/data_processing/test_job_binaries.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class JobBinaryTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.1_EDP.html#job-binaries
+ """
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(JobBinaryTest, cls).setUpClass()
+ cls.swift_job_binary_with_extra = {
+ 'url': 'swift://sahara-container.sahara/example.jar',
+ 'description': 'Test job binary',
+ 'extra': {
+ 'user': cls.os.credentials.username,
+ 'password': cls.os.credentials.password
+ }
+ }
+ # Create extra cls.swift_job_binary variable to use for comparison to
+ # job binary response body because response body has no 'extra' field.
+ cls.swift_job_binary = cls.swift_job_binary_with_extra.copy()
+ del cls.swift_job_binary['extra']
+
+ name = data_utils.rand_name('sahara-internal-job-binary')
+ cls.job_binary_data = 'Some script may be data'
+ job_binary_internal = cls.create_job_binary_internal(
+ name, cls.job_binary_data)[1]
+ cls.internal_db_job_binary = {
+ 'url': 'internal-db://%s' % job_binary_internal['id'],
+ 'description': 'Test job binary',
+ }
+
+ def _create_job_binary(self, binary_body, binary_name=None):
+ """Creates Job Binary with optional name specified.
+
+ It creates a link to data (jar, pig files, etc.) and ensures response
+ status, job binary name and response body. Returns id and name of
+ created job binary. Data may not exist when using Swift
+ as data storage. In other cases data must exist in storage.
+ """
+ if not binary_name:
+ # generate random name if it's not specified
+ binary_name = data_utils.rand_name('sahara-job-binary')
+
+ # create job binary
+ resp, body = self.create_job_binary(binary_name, **binary_body)
+
+ # ensure that binary created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(binary_name, body['name'])
+ if 'swift' in binary_body['url']:
+ binary_body = self.swift_job_binary
+ self.assertDictContainsSubset(binary_body, body)
+
+ return body['id'], binary_name
+
+ @test.attr(type='smoke')
+ def test_swift_job_binary_create(self):
+ self._create_job_binary(self.swift_job_binary_with_extra)
+
+ @test.attr(type='smoke')
+ def test_swift_job_binary_list(self):
+ binary_info = self._create_job_binary(self.swift_job_binary_with_extra)
+
+ # check for job binary in list
+ resp, binaries = self.client.list_job_binaries()
+ self.assertEqual(200, resp.status)
+ binaries_info = [(binary['id'], binary['name']) for binary in binaries]
+ self.assertIn(binary_info, binaries_info)
+
+ @test.attr(type='smoke')
+ def test_swift_job_binary_get(self):
+ binary_id, binary_name = self._create_job_binary(
+ self.swift_job_binary_with_extra)
+
+ # check job binary fetch by id
+ resp, binary = self.client.get_job_binary(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(binary_name, binary['name'])
+ self.assertDictContainsSubset(self.swift_job_binary, binary)
+
+ @test.attr(type='smoke')
+ def test_swift_job_binary_delete(self):
+ binary_id = self._create_job_binary(
+ self.swift_job_binary_with_extra)[0]
+
+ # delete the job binary by id
+ resp = self.client.delete_job_binary(binary_id)[0]
+ self.assertEqual(204, resp.status)
+
+ @test.attr(type='smoke')
+ def test_internal_db_job_binary_create(self):
+ self._create_job_binary(self.internal_db_job_binary)
+
+ @test.attr(type='smoke')
+ def test_internal_db_job_binary_list(self):
+ binary_info = self._create_job_binary(self.internal_db_job_binary)
+
+ # check for job binary in list
+ resp, binaries = self.client.list_job_binaries()
+ self.assertEqual(200, resp.status)
+ binaries_info = [(binary['id'], binary['name']) for binary in binaries]
+ self.assertIn(binary_info, binaries_info)
+
+ @test.attr(type='smoke')
+ def test_internal_db_job_binary_get(self):
+ binary_id, binary_name = self._create_job_binary(
+ self.internal_db_job_binary)
+
+ # check job binary fetch by id
+ resp, binary = self.client.get_job_binary(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(binary_name, binary['name'])
+ self.assertDictContainsSubset(self.internal_db_job_binary, binary)
+
+ @test.attr(type='smoke')
+ def test_internal_db_job_binary_delete(self):
+ binary_id = self._create_job_binary(self.internal_db_job_binary)[0]
+
+ # delete the job binary by id
+ resp = self.client.delete_job_binary(binary_id)[0]
+ self.assertEqual(204, resp.status)
+
+ @test.attr(type='smoke')
+ def test_job_binary_get_data(self):
+ binary_id = self._create_job_binary(self.internal_db_job_binary)[0]
+
+ # get data of job binary by id
+ resp, data = self.client.get_job_binary_data(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(data, self.job_binary_data)
diff --git a/tempest/api/data_processing/test_job_binary_internals.py b/tempest/api/data_processing/test_job_binary_internals.py
new file mode 100644
index 0000000..6d59177
--- /dev/null
+++ b/tempest/api/data_processing/test_job_binary_internals.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class JobBinaryInternalTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.1_EDP.html#job-binary-internals
+ """
+ @classmethod
+ def setUpClass(cls):
+ super(JobBinaryInternalTest, cls).setUpClass()
+ cls.job_binary_internal_data = 'Some script may be data'
+
+ def _create_job_binary_internal(self, binary_name=None):
+ """Creates Job Binary Internal with optional name specified.
+
+ It puts data into Sahara database and ensures response status and
+ job binary internal name. Returns id and name of created job binary
+ internal.
+ """
+ if not binary_name:
+ # generate random name if it's not specified
+ binary_name = data_utils.rand_name('sahara-job-binary-internal')
+
+ # create job binary internal
+ resp, body = self.create_job_binary_internal(
+ binary_name, self.job_binary_internal_data)
+
+ # ensure that job binary internal created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(binary_name, body['name'])
+
+ return body['id'], binary_name
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_create(self):
+ self._create_job_binary_internal()
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_list(self):
+ binary_info = self._create_job_binary_internal()
+
+ # check for job binary internal in list
+ resp, binaries = self.client.list_job_binary_internals()
+ self.assertEqual(200, resp.status)
+ binaries_info = [(binary['id'], binary['name']) for binary in binaries]
+ self.assertIn(binary_info, binaries_info)
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_get(self):
+ binary_id, binary_name = self._create_job_binary_internal()
+
+ # check job binary internal fetch by id
+ resp, binary = self.client.get_job_binary_internal(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(binary_name, binary['name'])
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_delete(self):
+ binary_id = self._create_job_binary_internal()[0]
+
+ # delete the job binary internal by id
+ resp = self.client.delete_job_binary_internal(binary_id)[0]
+ self.assertEqual(204, resp.status)
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_get_data(self):
+ binary_id = self._create_job_binary_internal()[0]
+
+ # get data of job binary internal by id
+ resp, data = self.client.get_job_binary_internal_data(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(data, self.job_binary_internal_data)
diff --git a/tempest/api/database/base.py b/tempest/api/database/base.py
index cf70d11..b68c84a 100644
--- a/tempest/api/database/base.py
+++ b/tempest/api/database/base.py
@@ -41,4 +41,5 @@
os = cls.get_client_manager()
cls.os = os
cls.database_flavors_client = cls.os.database_flavors_client
+ cls.os_flavors_client = cls.os.flavors_client
cls.database_versions_client = cls.os.database_versions_client
diff --git a/tempest/api/database/flavors/test_flavors.py b/tempest/api/database/flavors/test_flavors.py
index a591e8e..64d71b9 100644
--- a/tempest/api/database/flavors/test_flavors.py
+++ b/tempest/api/database/flavors/test_flavors.py
@@ -28,6 +28,7 @@
def test_get_db_flavor(self):
# The expected flavor details should be returned
resp, flavor = self.client.get_db_flavor_details(self.db_flavor_ref)
+ self.assertEqual(200, resp.status)
self.assertEqual(self.db_flavor_ref, str(flavor['id']))
self.assertIn('ram', flavor)
self.assertIn('links', flavor)
@@ -36,6 +37,36 @@
@test.attr(type='smoke')
def test_list_db_flavors(self):
resp, flavor = self.client.get_db_flavor_details(self.db_flavor_ref)
+ self.assertEqual(200, resp.status)
# List of all flavors should contain the expected flavor
resp, flavors = self.client.list_db_flavors()
+ self.assertEqual(200, resp.status)
self.assertIn(flavor, flavors)
+
+ def _check_values(self, names, db_flavor, os_flavor, in_db=True):
+ for name in names:
+ self.assertIn(name, os_flavor)
+ if in_db:
+ self.assertIn(name, db_flavor)
+ self.assertEqual(str(db_flavor[name]), str(os_flavor[name]),
+ "DB flavor differs from OS on '%s' value"
+ % name)
+ else:
+ self.assertNotIn(name, db_flavor)
+
+ @test.attr(type='smoke')
+ def test_compare_db_flavors_with_os(self):
+ resp, db_flavors = self.client.list_db_flavors()
+ self.assertEqual(200, resp.status)
+ resp, os_flavors = self.os_flavors_client.list_flavors_with_detail()
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(os_flavors), len(db_flavors),
+ "OS flavors %s do not match DB flavors %s" %
+ (os_flavors, db_flavors))
+ for os_flavor in os_flavors:
+ resp, db_flavor =\
+ self.client.get_db_flavor_details(os_flavor['id'])
+ self.assertEqual(200, resp.status)
+ self._check_values(['id', 'name', 'ram'], db_flavor, os_flavor)
+ self._check_values(['disk', 'vcpus', 'swap'], db_flavor, os_flavor,
+ in_db=False)
diff --git a/tempest/api/identity/admin/test_roles.py b/tempest/api/identity/admin/test_roles.py
index a29f27e..7a6d07f 100644
--- a/tempest/api/identity/admin/test_roles.py
+++ b/tempest/api/identity/admin/test_roles.py
@@ -29,7 +29,7 @@
super(RolesTestJSON, cls).setUpClass()
for _ in moves.xrange(5):
role_name = data_utils.rand_name(name='role-')
- resp, role = cls.client.create_role(role_name)
+ _, role = cls.client.create_role(role_name)
cls.data.roles.append(role)
def _get_role_params(self):
@@ -50,7 +50,7 @@
@test.attr(type='gate')
def test_list_roles(self):
# Return a list of all roles
- resp, body = self.client.list_roles()
+ _, body = self.client.list_roles()
found = [role for role in body if role in self.data.roles]
self.assertTrue(any(found))
self.assertEqual(len(found), len(self.data.roles))
@@ -59,18 +59,16 @@
def test_role_create_delete(self):
# Role should be created, verified, and deleted
role_name = data_utils.rand_name(name='role-test-')
- resp, body = self.client.create_role(role_name)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_role(role_name)
self.assertEqual(role_name, body['name'])
- resp, body = self.client.list_roles()
+ _, body = self.client.list_roles()
found = [role for role in body if role['name'] == role_name]
self.assertTrue(any(found))
- resp, body = self.client.delete_role(found[0]['id'])
- self.assertEqual(204, resp.status)
+ _, body = self.client.delete_role(found[0]['id'])
- resp, body = self.client.list_roles()
+ _, body = self.client.list_roles()
found = [role for role in body if role['name'] == role_name]
self.assertFalse(any(found))
@@ -80,9 +78,7 @@
self.data.setup_test_role()
role_id = self.data.role['id']
role_name = self.data.role['name']
- resp, body = self.client.get_role(role_id)
- self.assertIn('status', resp)
- self.assertTrue('200', resp['status'])
+ _, body = self.client.get_role(role_id)
self.assertEqual(role_id, body['id'])
self.assertEqual(role_name, body['name'])
@@ -91,25 +87,24 @@
# Assign a role to a user on a tenant
(user, tenant, role) = self._get_role_params()
self.client.assign_user_role(tenant['id'], user['id'], role['id'])
- resp, roles = self.client.list_user_roles(tenant['id'], user['id'])
+ _, roles = self.client.list_user_roles(tenant['id'], user['id'])
self.assert_role_in_role_list(role, roles)
@test.attr(type='gate')
def test_remove_user_role(self):
# Remove a role assigned to a user on a tenant
(user, tenant, role) = self._get_role_params()
- resp, user_role = self.client.assign_user_role(tenant['id'],
- user['id'], role['id'])
- resp, body = self.client.remove_user_role(tenant['id'], user['id'],
- user_role['id'])
- self.assertEqual(204, resp.status)
+ _, user_role = self.client.assign_user_role(tenant['id'],
+ user['id'], role['id'])
+ self.client.remove_user_role(tenant['id'], user['id'],
+ user_role['id'])
@test.attr(type='gate')
def test_list_user_roles(self):
# List roles assigned to a user on tenant
(user, tenant, role) = self._get_role_params()
self.client.assign_user_role(tenant['id'], user['id'], role['id'])
- resp, roles = self.client.list_user_roles(tenant['id'], user['id'])
+ _, roles = self.client.list_user_roles(tenant['id'], user['id'])
self.assert_role_in_role_list(role, roles)
diff --git a/tempest/api/identity/admin/test_roles_negative.py b/tempest/api/identity/admin/test_roles_negative.py
index 6f8f9b5..37a981e 100644
--- a/tempest/api/identity/admin/test_roles_negative.py
+++ b/tempest/api/identity/admin/test_roles_negative.py
@@ -72,9 +72,8 @@
def test_role_create_duplicate(self):
# Role names should be unique
role_name = data_utils.rand_name(name='role-dup-')
- resp, body = self.client.create_role(role_name)
+ _, body = self.client.create_role(role_name)
role1_id = body.get('id')
- self.assertEqual(200, resp.status)
self.addCleanup(self.client.delete_role, role1_id)
self.assertRaises(exceptions.Conflict, self.client.create_role,
role_name)
@@ -83,8 +82,7 @@
def test_delete_role_by_unauthorized_user(self):
# Non-administrator user should not be able to delete role
role_name = data_utils.rand_name(name='role-')
- resp, body = self.client.create_role(role_name)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_role(role_name)
self.data.roles.append(body)
role_id = body.get('id')
self.assertRaises(exceptions.Unauthorized,
@@ -94,8 +92,7 @@
def test_delete_role_request_without_token(self):
# Request to delete role without a valid token should fail
role_name = data_utils.rand_name(name='role-')
- resp, body = self.client.create_role(role_name)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_role(role_name)
self.data.roles.append(body)
role_id = body.get('id')
token = self.client.auth_provider.get_token()
diff --git a/tempest/api/identity/admin/test_services.py b/tempest/api/identity/admin/test_services.py
index 0472e07..a9782a9 100644
--- a/tempest/api/identity/admin/test_services.py
+++ b/tempest/api/identity/admin/test_services.py
@@ -26,8 +26,7 @@
def _del_service(self, service_id):
# Deleting the service created in this method
- resp, _ = self.client.delete_service(service_id)
- self.assertEqual(204, resp.status)
+ self.client.delete_service(service_id)
# Checking whether service is deleted successfully
self.assertRaises(exceptions.NotFound, self.client.get_service,
service_id)
@@ -39,11 +38,10 @@
name = data_utils.rand_name('service-')
type = data_utils.rand_name('type--')
description = data_utils.rand_name('description-')
- resp, service_data = self.client.create_service(
+ _, service_data = self.client.create_service(
name, type, description=description)
self.assertFalse(service_data['id'] is None)
self.addCleanup(self._del_service, service_data['id'])
- self.assertEqual(200, resp.status)
# Verifying response body of create service
self.assertIn('id', service_data)
self.assertIn('name', service_data)
@@ -53,8 +51,7 @@
self.assertIn('description', service_data)
self.assertEqual(description, service_data['description'])
# Get service
- resp, fetched_service = self.client.get_service(service_data['id'])
- self.assertEqual(200, resp.status)
+ _, fetched_service = self.client.get_service(service_data['id'])
# verifying the existence of service created
self.assertIn('id', fetched_service)
self.assertEqual(fetched_service['id'], service_data['id'])
@@ -71,9 +68,8 @@
# Create a service only with name and type
name = data_utils.rand_name('service-')
type = data_utils.rand_name('type--')
- resp, service = self.client.create_service(name, type)
+ _, service = self.client.create_service(name, type)
self.assertIn('id', service)
- self.assertTrue('200', resp['status'])
self.addCleanup(self._del_service, service['id'])
self.assertIn('name', service)
self.assertEqual(name, service['name'])
@@ -88,7 +84,7 @@
name = data_utils.rand_name('service-')
type = data_utils.rand_name('type--')
description = data_utils.rand_name('description-')
- resp, service = self.client.create_service(
+ _, service = self.client.create_service(
name, type, description=description)
services.append(service)
service_ids = map(lambda x: x['id'], services)
@@ -99,9 +95,8 @@
self.addCleanup(delete_services)
# List and Verify Services
- resp, body = self.client.list_services()
- self.assertEqual(200, resp.status)
- found = [service for service in body if service['id'] in service_ids]
+ _, body = self.client.list_services()
+ found = [serv for serv in body if serv['id'] in service_ids]
self.assertEqual(len(found), len(services), 'Services not found')
diff --git a/tempest/api/identity/admin/test_tenant_negative.py b/tempest/api/identity/admin/test_tenant_negative.py
index 622ad81..dcfacee 100644
--- a/tempest/api/identity/admin/test_tenant_negative.py
+++ b/tempest/api/identity/admin/test_tenant_negative.py
@@ -42,8 +42,7 @@
def test_tenant_delete_by_unauthorized_user(self):
# Non-administrator user should not be able to delete a tenant
tenant_name = data_utils.rand_name(name='tenant-')
- resp, tenant = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, tenant = self.client.create_tenant(tenant_name)
self.data.tenants.append(tenant)
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.delete_tenant, tenant['id'])
@@ -52,8 +51,7 @@
def test_tenant_delete_request_without_token(self):
# Request to delete a tenant without a valid token should fail
tenant_name = data_utils.rand_name(name='tenant-')
- resp, tenant = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, tenant = self.client.create_tenant(tenant_name)
self.data.tenants.append(tenant)
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
@@ -71,8 +69,7 @@
def test_tenant_create_duplicate(self):
# Tenant names should be unique
tenant_name = data_utils.rand_name(name='tenant-')
- resp, body = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_tenant(tenant_name)
tenant = body
self.data.tenants.append(tenant)
tenant1_id = body.get('id')
@@ -122,8 +119,7 @@
def test_tenant_update_by_unauthorized_user(self):
# Non-administrator user should not be able to update a tenant
tenant_name = data_utils.rand_name(name='tenant-')
- resp, tenant = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, tenant = self.client.create_tenant(tenant_name)
self.data.tenants.append(tenant)
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.update_tenant, tenant['id'])
@@ -132,8 +128,7 @@
def test_tenant_update_request_without_token(self):
# Request to update a tenant without a valid token should fail
tenant_name = data_utils.rand_name(name='tenant-')
- resp, tenant = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, tenant = self.client.create_tenant(tenant_name)
self.data.tenants.append(tenant)
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
diff --git a/tempest/api/identity/admin/test_tenants.py b/tempest/api/identity/admin/test_tenants.py
index b989664..8d3b402 100644
--- a/tempest/api/identity/admin/test_tenants.py
+++ b/tempest/api/identity/admin/test_tenants.py
@@ -29,22 +29,19 @@
tenants = []
for _ in moves.xrange(3):
tenant_name = data_utils.rand_name(name='tenant-new')
- resp, tenant = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, tenant = self.client.create_tenant(tenant_name)
self.data.tenants.append(tenant)
tenants.append(tenant)
tenant_ids = map(lambda x: x['id'], tenants)
- resp, body = self.client.list_tenants()
- self.assertEqual(200, resp.status)
- found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+ _, body = self.client.list_tenants()
+ found = [t for t in body if t['id'] in tenant_ids]
self.assertEqual(len(found), len(tenants), 'Tenants not created')
for tenant in tenants:
- resp, body = self.client.delete_tenant(tenant['id'])
- self.assertEqual(204, resp.status)
+ self.client.delete_tenant(tenant['id'])
self.data.tenants.remove(tenant)
- resp, body = self.client.list_tenants()
+ _, body = self.client.list_tenants()
found = [tenant for tenant in body if tenant['id'] in tenant_ids]
self.assertFalse(any(found), 'Tenants failed to delete')
@@ -53,16 +50,15 @@
# Create tenant with a description
tenant_name = data_utils.rand_name(name='tenant-')
tenant_desc = data_utils.rand_name(name='desc-')
- resp, body = self.client.create_tenant(tenant_name,
- description=tenant_desc)
+ _, body = self.client.create_tenant(tenant_name,
+ description=tenant_desc)
tenant = body
self.data.tenants.append(tenant)
tenant_id = body['id']
desc1 = body['description']
- self.assertEqual(200, resp.status)
self.assertEqual(desc1, tenant_desc, 'Description should have '
'been sent in response for create')
- resp, body = self.client.get_tenant(tenant_id)
+ _, body = self.client.get_tenant(tenant_id)
desc2 = body['description']
self.assertEqual(desc2, tenant_desc, 'Description does not appear'
'to be set')
@@ -73,14 +69,13 @@
def test_tenant_create_enabled(self):
# Create a tenant that is enabled
tenant_name = data_utils.rand_name(name='tenant-')
- resp, body = self.client.create_tenant(tenant_name, enabled=True)
+ _, body = self.client.create_tenant(tenant_name, enabled=True)
tenant = body
self.data.tenants.append(tenant)
tenant_id = body['id']
en1 = body['enabled']
- self.assertEqual(200, resp.status)
self.assertTrue(en1, 'Enable should be True in response')
- resp, body = self.client.get_tenant(tenant_id)
+ _, body = self.client.get_tenant(tenant_id)
en2 = body['enabled']
self.assertTrue(en2, 'Enable should be True in lookup')
self.client.delete_tenant(tenant_id)
@@ -90,15 +85,14 @@
def test_tenant_create_not_enabled(self):
# Create a tenant that is not enabled
tenant_name = data_utils.rand_name(name='tenant-')
- resp, body = self.client.create_tenant(tenant_name, enabled=False)
+ _, body = self.client.create_tenant(tenant_name, enabled=False)
tenant = body
self.data.tenants.append(tenant)
tenant_id = body['id']
en1 = body['enabled']
- self.assertEqual(200, resp.status)
self.assertEqual('false', str(en1).lower(),
'Enable should be False in response')
- resp, body = self.client.get_tenant(tenant_id)
+ _, body = self.client.get_tenant(tenant_id)
en2 = body['enabled']
self.assertEqual('false', str(en2).lower(),
'Enable should be False in lookup')
@@ -109,8 +103,7 @@
def test_tenant_update_name(self):
# Update name attribute of a tenant
t_name1 = data_utils.rand_name(name='tenant-')
- resp, body = self.client.create_tenant(t_name1)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_tenant(t_name1)
tenant = body
self.data.tenants.append(tenant)
@@ -118,12 +111,11 @@
resp1_name = body['name']
t_name2 = data_utils.rand_name(name='tenant2-')
- resp, body = self.client.update_tenant(t_id, name=t_name2)
+ _, body = self.client.update_tenant(t_id, name=t_name2)
resp2_name = body['name']
- self.assertEqual(200, resp.status)
self.assertNotEqual(resp1_name, resp2_name)
- resp, body = self.client.get_tenant(t_id)
+ _, body = self.client.get_tenant(t_id)
resp3_name = body['name']
self.assertNotEqual(resp1_name, resp3_name)
@@ -138,8 +130,7 @@
# Update description attribute of a tenant
t_name = data_utils.rand_name(name='tenant-')
t_desc = data_utils.rand_name(name='desc-')
- resp, body = self.client.create_tenant(t_name, description=t_desc)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_tenant(t_name, description=t_desc)
tenant = body
self.data.tenants.append(tenant)
@@ -147,12 +138,11 @@
resp1_desc = body['description']
t_desc2 = data_utils.rand_name(name='desc2-')
- resp, body = self.client.update_tenant(t_id, description=t_desc2)
+ _, body = self.client.update_tenant(t_id, description=t_desc2)
resp2_desc = body['description']
- self.assertEqual(200, resp.status)
self.assertNotEqual(resp1_desc, resp2_desc)
- resp, body = self.client.get_tenant(t_id)
+ _, body = self.client.get_tenant(t_id)
resp3_desc = body['description']
self.assertNotEqual(resp1_desc, resp3_desc)
@@ -167,8 +157,7 @@
# Update the enabled attribute of a tenant
t_name = data_utils.rand_name(name='tenant-')
t_en = False
- resp, body = self.client.create_tenant(t_name, enabled=t_en)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_tenant(t_name, enabled=t_en)
tenant = body
self.data.tenants.append(tenant)
@@ -176,12 +165,11 @@
resp1_en = body['enabled']
t_en2 = True
- resp, body = self.client.update_tenant(t_id, enabled=t_en2)
+ _, body = self.client.update_tenant(t_id, enabled=t_en2)
resp2_en = body['enabled']
- self.assertEqual(200, resp.status)
self.assertNotEqual(resp1_en, resp2_en)
- resp, body = self.client.get_tenant(t_id)
+ _, body = self.client.get_tenant(t_id)
resp3_en = body['enabled']
self.assertNotEqual(resp1_en, resp3_en)
diff --git a/tempest/api/identity/admin/test_tokens.py b/tempest/api/identity/admin/test_tokens.py
index 08e12f0..e1db008 100644
--- a/tempest/api/identity/admin/test_tokens.py
+++ b/tempest/api/identity/admin/test_tokens.py
@@ -28,13 +28,11 @@
user_password = data_utils.rand_name(name='pass-')
# first:create a tenant
tenant_name = data_utils.rand_name(name='tenant-')
- resp, tenant = self.client.create_tenant(tenant_name)
- self.assertEqual(200, resp.status)
+ _, tenant = self.client.create_tenant(tenant_name)
self.data.tenants.append(tenant)
# second:create a user
- resp, user = self.client.create_user(user_name, user_password,
- tenant['id'], '')
- self.assertEqual(200, resp.status)
+ _, user = self.client.create_user(user_name, user_password,
+ tenant['id'], '')
self.data.users.append(user)
# then get a token for the user
rsp, body = self.token_client.auth(user_name,
@@ -45,16 +43,14 @@
tenant['name'])
# Perform GET Token
token_id = body['token']['id']
- resp, token_details = self.client.get_token(token_id)
- self.assertEqual(resp['status'], '200')
+ _, token_details = self.client.get_token(token_id)
self.assertEqual(token_id, token_details['token']['id'])
self.assertEqual(user['id'], token_details['user']['id'])
self.assertEqual(user_name, token_details['user']['name'])
self.assertEqual(tenant['name'],
token_details['token']['tenant']['name'])
# then delete the token
- resp, body = self.client.delete_token(token_id)
- self.assertEqual(resp['status'], '204')
+ self.client.delete_token(token_id)
@test.attr(type='gate')
def test_rescope_token(self):
@@ -67,56 +63,51 @@
user_password = data_utils.rand_name(name='pass-')
tenant_id = None # No default tenant so will get unscoped token.
email = ''
- resp, user = self.client.create_user(user_name, user_password,
- tenant_id, email)
- self.assertEqual(200, resp.status)
+ _, user = self.client.create_user(user_name, user_password,
+ tenant_id, email)
self.data.users.append(user)
# Create a couple tenants.
tenant1_name = data_utils.rand_name(name='tenant-')
- resp, tenant1 = self.client.create_tenant(tenant1_name)
- self.assertEqual(200, resp.status)
+ _, tenant1 = self.client.create_tenant(tenant1_name)
self.data.tenants.append(tenant1)
tenant2_name = data_utils.rand_name(name='tenant-')
- resp, tenant2 = self.client.create_tenant(tenant2_name)
- self.assertEqual(200, resp.status)
+ _, tenant2 = self.client.create_tenant(tenant2_name)
self.data.tenants.append(tenant2)
# Create a role
role_name = data_utils.rand_name(name='role-')
- resp, role = self.client.create_role(role_name)
- self.assertEqual(200, resp.status)
+ _, role = self.client.create_role(role_name)
self.data.roles.append(role)
# Grant the user the role on the tenants.
- resp, _ = self.client.assign_user_role(tenant1['id'], user['id'],
- role['id'])
- self.assertEqual(200, resp.status)
+ self.client.assign_user_role(tenant1['id'], user['id'],
+ role['id'])
- resp, _ = self.client.assign_user_role(tenant2['id'], user['id'],
- role['id'])
- self.assertEqual(200, resp.status)
+ self.client.assign_user_role(tenant2['id'], user['id'],
+ role['id'])
# Get an unscoped token.
- rsp, body = self.token_client.auth(user_name, user_password)
+ resp, body = self.token_client.auth(user_name, user_password)
self.assertEqual(200, resp.status)
token_id = body['token']['id']
# Use the unscoped token to get a token scoped to tenant1
- rsp, body = self.token_client.auth_token(token_id, tenant=tenant1_name)
+ resp, body = self.token_client.auth_token(token_id,
+ tenant=tenant1_name)
self.assertEqual(200, resp.status)
scoped_token_id = body['token']['id']
# Revoke the scoped token
- resp, body = self.client.delete_token(scoped_token_id)
- self.assertEqual(204, resp.status)
+ self.client.delete_token(scoped_token_id)
# Use the unscoped token to get a token scoped to tenant2
- rsp, body = self.token_client.auth_token(token_id, tenant=tenant2_name)
- self.assertEqual(204, resp.status)
+ resp, body = self.token_client.auth_token(token_id,
+ tenant=tenant2_name)
+ self.assertEqual(200, resp.status)
class TokensTestXML(TokensTestJSON):
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index e2c1066..5838da3 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -34,11 +34,10 @@
def test_create_user(self):
# Create a user
self.data.setup_test_tenant()
- resp, user = self.client.create_user(self.alt_user, self.alt_password,
- self.data.tenant['id'],
- self.alt_email)
+ _, user = self.client.create_user(self.alt_user, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email)
self.data.users.append(user)
- self.assertEqual('200', resp['status'])
self.assertEqual(self.alt_user, user['name'])
@test.attr(type='smoke')
@@ -46,11 +45,10 @@
# Create a user with enabled : False
self.data.setup_test_tenant()
name = data_utils.rand_name('test_user_')
- resp, user = self.client.create_user(name, self.alt_password,
- self.data.tenant['id'],
- self.alt_email, enabled=False)
+ _, user = self.client.create_user(name, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email, enabled=False)
self.data.users.append(user)
- self.assertEqual('200', resp['status'])
self.assertEqual(name, user['name'])
self.assertEqual('false', str(user['enabled']).lower())
self.assertEqual(self.alt_email, user['email'])
@@ -60,24 +58,22 @@
# Test case to check if updating of user attributes is successful.
test_user = data_utils.rand_name('test_user_')
self.data.setup_test_tenant()
- resp, user = self.client.create_user(test_user, self.alt_password,
- self.data.tenant['id'],
- self.alt_email)
+ _, user = self.client.create_user(test_user, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email)
# Delete the User at the end of this method
self.addCleanup(self.client.delete_user, user['id'])
# Updating user details with new values
u_name2 = data_utils.rand_name('user2-')
u_email2 = u_name2 + '@testmail.tm'
- resp, update_user = self.client.update_user(user['id'], name=u_name2,
- email=u_email2,
- enabled=False)
- # Assert response body of update user.
- self.assertEqual(200, resp.status)
+ _, update_user = self.client.update_user(user['id'], name=u_name2,
+ email=u_email2,
+ enabled=False)
self.assertEqual(u_name2, update_user['name'])
self.assertEqual(u_email2, update_user['email'])
self.assertEqual('false', str(update_user['enabled']).lower())
# GET by id after updating
- resp, updated_user = self.client.get_user(user['id'])
+ _, updated_user = self.client.get_user(user['id'])
# Assert response body of GET after updating
self.assertEqual(u_name2, updated_user['name'])
self.assertEqual(u_email2, updated_user['email'])
@@ -88,12 +84,10 @@
# Delete a user
test_user = data_utils.rand_name('test_user_')
self.data.setup_test_tenant()
- resp, user = self.client.create_user(test_user, self.alt_password,
- self.data.tenant['id'],
- self.alt_email)
- self.assertEqual('200', resp['status'])
- resp, body = self.client.delete_user(user['id'])
- self.assertEqual('204', resp['status'])
+ _, user = self.client.create_user(test_user, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email)
+ self.client.delete_user(user['id'])
@test.attr(type='smoke')
def test_user_authentication(self):
@@ -129,7 +123,7 @@
def test_get_users(self):
# Get a list of users and find the test user
self.data.setup_test_user()
- resp, users = self.client.get_users()
+ _, users = self.client.get_users()
self.assertThat([u['name'] for u in users],
matchers.Contains(self.data.test_user),
"Could not find %s" % self.data.test_user)
@@ -141,23 +135,20 @@
user_ids = list()
fetched_user_ids = list()
alt_tenant_user1 = data_utils.rand_name('tenant_user1_')
- resp, user1 = self.client.create_user(alt_tenant_user1, 'password1',
- self.data.tenant['id'],
- 'user1@123')
- self.assertEqual('200', resp['status'])
+ _, user1 = self.client.create_user(alt_tenant_user1, 'password1',
+ self.data.tenant['id'],
+ 'user1@123')
user_ids.append(user1['id'])
self.data.users.append(user1)
alt_tenant_user2 = data_utils.rand_name('tenant_user2_')
- resp, user2 = self.client.create_user(alt_tenant_user2, 'password2',
- self.data.tenant['id'],
- 'user2@123')
- self.assertEqual('200', resp['status'])
+ _, user2 = self.client.create_user(alt_tenant_user2, 'password2',
+ self.data.tenant['id'],
+ 'user2@123')
user_ids.append(user2['id'])
self.data.users.append(user2)
# List of users for the respective tenant ID
- resp, body = self.client.list_users_for_tenant(self.data.tenant['id'])
- self.assertIn(resp['status'], ('200', '203'))
+ _, body = self.client.list_users_for_tenant(self.data.tenant['id'])
for i in body:
fetched_user_ids.append(i['id'])
# verifying the user Id in the list
@@ -179,24 +170,20 @@
user_ids = list()
fetched_user_ids = list()
user_ids.append(user['id'])
- resp, role = self.client.assign_user_role(tenant['id'], user['id'],
- role['id'])
- self.assertEqual('200', resp['status'])
+ _, role = self.client.assign_user_role(tenant['id'], user['id'],
+ role['id'])
alt_user2 = data_utils.rand_name('second_user_')
- resp, second_user = self.client.create_user(alt_user2, 'password1',
- self.data.tenant['id'],
- 'user2@123')
- self.assertEqual('200', resp['status'])
+ _, second_user = self.client.create_user(alt_user2, 'password1',
+ self.data.tenant['id'],
+ 'user2@123')
user_ids.append(second_user['id'])
self.data.users.append(second_user)
- resp, role = self.client.assign_user_role(tenant['id'],
- second_user['id'],
- role['id'])
- self.assertEqual('200', resp['status'])
+ _, role = self.client.assign_user_role(tenant['id'],
+ second_user['id'],
+ role['id'])
# List of users with roles for the respective tenant ID
- resp, body = self.client.list_users_for_tenant(self.data.tenant['id'])
- self.assertEqual('200', resp['status'])
+ _, body = self.client.list_users_for_tenant(self.data.tenant['id'])
for i in body:
fetched_user_ids.append(i['id'])
# verifying the user Id in the list
@@ -212,10 +199,8 @@
self.data.setup_test_user()
# Updating the user with new password
new_pass = data_utils.rand_name('pass-')
- resp, update_user = self.client.update_user_password(
+ _, update_user = self.client.update_user_password(
self.data.user['id'], new_pass)
- # Assert response body of update user.
- self.assertEqual(200, resp.status)
self.assertEqual(update_user['id'], self.data.user['id'])
# Validate the updated password
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 6bb0ebe..d40e0f3 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -33,28 +33,20 @@
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_name('pass-')
for i in range(2):
- resp, cls.project = cls.client.create_project(
+ _, cls.project = cls.client.create_project(
data_utils.rand_name('project-'),
description=data_utils.rand_name('project-desc-'))
- assert resp['status'] == '201', (
- "Expected 201, but got: %s" % resp['status'])
cls.projects.append(cls.project['id'])
- resp, cls.user_body = cls.client.create_user(
+ _, cls.user_body = cls.client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.projects[0])
- assert resp['status'] == '201', (
- "Expected 201, but got: %s" % resp['status'])
@classmethod
def tearDownClass(cls):
- resp, _ = cls.client.delete_user(cls.user_body['id'])
- assert resp['status'] == '204', (
- "Expected 204, but got: %s" % resp['status'])
+ cls.client.delete_user(cls.user_body['id'])
for p in cls.projects:
- resp, _ = cls.client.delete_project(p)
- assert resp['status'] == '204', (
- "Expected 204, but got: %s" % resp['status'])
+ cls.client.delete_project(p)
super(CredentialsTestJSON, cls).tearDownClass()
def _delete_credential(self, cred_id):
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index a1e6cde..5b73df1 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -25,9 +25,8 @@
def _delete_domain(self, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
- _, body = self.client.update_domain(domain_id, enabled=False)
- resp, _ = self.client.delete_domain(domain_id)
- self.assertEqual(204, resp.status)
+ self.client.update_domain(domain_id, enabled=False)
+ self.client.delete_domain(domain_id)
@test.attr(type='smoke')
def test_list_domains(self):
@@ -42,8 +41,7 @@
self.addCleanup(self._delete_domain, domain['id'])
domain_ids.append(domain['id'])
# List and Verify Domains
- resp, body = self.client.list_domains()
- self.assertEqual(resp['status'], '200')
+ _, body = self.client.list_domains()
for d in body:
fetched_ids.append(d['id'])
missing_doms = [d for d in domain_ids if d not in fetched_ids]
@@ -53,9 +51,8 @@
def test_create_update_delete_domain(self):
d_name = data_utils.rand_name('domain-')
d_desc = data_utils.rand_name('domain-desc-')
- resp_1, domain = self.client.create_domain(
+ _, domain = self.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)
@@ -72,9 +69,8 @@
new_desc = data_utils.rand_name('new-desc-')
new_name = data_utils.rand_name('new-name-')
- resp_2, updated_domain = self.client.update_domain(
+ _, updated_domain = self.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)
@@ -85,8 +81,7 @@
self.assertEqual(new_desc, updated_domain['description'])
self.assertEqual('true', str(updated_domain['enabled']).lower())
- resp_3, fetched_domain = self.client.get_domain(domain['id'])
- self.assertEqual(resp_3['status'], '200')
+ _, fetched_domain = self.client.get_domain(domain['id'])
self.assertEqual(new_name, fetched_domain['name'])
self.assertEqual(new_desc, fetched_domain['description'])
self.assertEqual('true', str(fetched_domain['enabled']).lower())
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index dd3b576..1548f89 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -31,7 +31,7 @@
s_name = data_utils.rand_name('service-')
s_type = data_utils.rand_name('type--')
s_description = data_utils.rand_name('description-')
- resp, cls.service_data =\
+ _, cls.service_data =\
cls.service_client.create_service(s_name, s_type,
description=s_description)
cls.service_id = cls.service_data['id']
@@ -108,22 +108,22 @@
s_name = data_utils.rand_name('service-')
s_type = data_utils.rand_name('type--')
s_description = data_utils.rand_name('description-')
- resp, self.service2 =\
+ _, service2 =\
self.service_client.create_service(s_name, s_type,
description=s_description)
- self.service_ids.append(self.service2['id'])
+ self.service_ids.append(service2['id'])
# Updating endpoint with new values
region2 = data_utils.rand_name('region')
url2 = data_utils.rand_name('url')
interface2 = 'internal'
resp, endpoint = \
self.client.update_endpoint(endpoint_for_update['id'],
- service_id=self.service2['id'],
+ service_id=service2['id'],
interface=interface2, url=url2,
region=region2, enabled=False)
self.assertEqual(resp['status'], '200')
# Asserting if the attributes of endpoint are updated
- self.assertEqual(self.service2['id'], endpoint['service_id'])
+ self.assertEqual(service2['id'], endpoint['service_id'])
self.assertEqual(interface2, endpoint['interface'])
self.assertEqual(url2, endpoint['url'])
self.assertEqual(region2, endpoint['region'])
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 5b46f89..1d63cce 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -33,7 +33,7 @@
s_name = data_utils.rand_name('service-')
s_type = data_utils.rand_name('type--')
s_description = data_utils.rand_name('description-')
- resp, cls.service_data = (
+ _, cls.service_data = (
cls.service_client.create_service(s_name, s_type,
description=s_description))
cls.service_id = cls.service_data['id']
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 056f713..4d2cc46 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -29,24 +29,21 @@
def test_group_create_update_get(self):
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
- resp, group = self.client.create_group(name,
- description=description)
+ _, group = self.client.create_group(name,
+ description=description)
self.addCleanup(self.client.delete_group, group['id'])
- self.assertEqual(resp['status'], '201')
self.assertEqual(group['name'], name)
self.assertEqual(group['description'], description)
new_name = data_utils.rand_name('UpdateGroup')
new_desc = data_utils.rand_name('UpdateDescription')
- resp, updated_group = self.client.update_group(group['id'],
- name=new_name,
- description=new_desc)
- self.assertEqual(resp['status'], '200')
+ _, updated_group = self.client.update_group(group['id'],
+ name=new_name,
+ description=new_desc)
self.assertEqual(updated_group['name'], new_name)
self.assertEqual(updated_group['description'], new_desc)
- resp, new_group = self.client.get_group(group['id'])
- self.assertEqual(resp['status'], '200')
+ _, new_group = self.client.get_group(group['id'])
self.assertEqual(group['id'], new_group['id'])
self.assertEqual(new_name, new_group['name'])
self.assertEqual(new_desc, new_group['description'])
@@ -54,33 +51,31 @@
@test.attr(type='smoke')
def test_group_users_add_list_delete(self):
name = data_utils.rand_name('Group')
- resp, group = self.client.create_group(name)
+ _, group = self.client.create_group(name)
self.addCleanup(self.client.delete_group, group['id'])
# add user into group
users = []
for i in range(3):
name = data_utils.rand_name('User')
- resp, user = self.client.create_user(name)
+ _, user = self.client.create_user(name)
users.append(user)
self.addCleanup(self.client.delete_user, user['id'])
self.client.add_group_user(group['id'], user['id'])
# list users in group
- resp, group_users = self.client.list_group_users(group['id'])
- self.assertEqual(resp['status'], '200')
+ _, group_users = self.client.list_group_users(group['id'])
self.assertEqual(sorted(users), sorted(group_users))
# delete user in group
for user in users:
- resp, body = self.client.delete_group_user(group['id'],
- user['id'])
- self.assertEqual(resp['status'], '204')
- resp, group_users = self.client.list_group_users(group['id'])
+ self.client.delete_group_user(group['id'],
+ user['id'])
+ _, group_users = self.client.list_group_users(group['id'])
self.assertEqual(len(group_users), 0)
@test.attr(type='smoke')
def test_list_user_groups(self):
# create a user
- resp, user = self.client.create_user(
+ _, user = self.client.create_user(
data_utils.rand_name('User-'),
password=data_utils.rand_name('Pass-'))
self.addCleanup(self.client.delete_user, user['id'])
@@ -88,13 +83,12 @@
groups = []
for i in range(2):
name = data_utils.rand_name('Group-')
- resp, group = self.client.create_group(name)
+ _, group = self.client.create_group(name)
groups.append(group)
self.addCleanup(self.client.delete_group, group['id'])
self.client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
- resp, user_groups = self.client.list_user_groups(user['id'])
- self.assertEqual('200', resp['status'])
+ _, user_groups = self.client.list_user_groups(user['id'])
self.assertEqual(sorted(groups), sorted(user_groups))
self.assertEqual(2, len(user_groups))
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 31a0ddd..77acd57 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -25,8 +25,7 @@
_interface = 'json'
def _delete_project(self, project_id):
- resp, _ = self.client.delete_project(project_id)
- self.assertEqual(resp['status'], '204')
+ self.client.delete_project(project_id)
self.assertRaises(
exceptions.NotFound, self.client.get_project, project_id)
@@ -34,14 +33,13 @@
def test_project_list_delete(self):
# Create several projects and delete them
for _ in moves.xrange(3):
- resp, project = self.client.create_project(
+ _, project = self.client.create_project(
data_utils.rand_name('project-new'))
self.addCleanup(self._delete_project, project['id'])
- resp, list_projects = self.client.list_projects()
- self.assertEqual(resp['status'], '200')
+ _, list_projects = self.client.list_projects()
- resp, get_project = self.client.get_project(project['id'])
+ _, get_project = self.client.get_project(project['id'])
self.assertIn(get_project, list_projects)
@test.attr(type='gate')
@@ -49,16 +47,14 @@
# Create project with a description
project_name = data_utils.rand_name('project-')
project_desc = data_utils.rand_name('desc-')
- resp, project = self.client.create_project(
+ _, project = self.client.create_project(
project_name, description=project_desc)
self.data.projects.append(project)
- st1 = resp['status']
project_id = project['id']
desc1 = project['description']
- self.assertEqual(st1, '201')
self.assertEqual(desc1, project_desc, 'Description should have '
'been sent in response for create')
- resp, body = self.client.get_project(project_id)
+ _, body = self.client.get_project(project_id)
desc2 = body['description']
self.assertEqual(desc2, project_desc, 'Description does not appear'
'to be set')
@@ -67,15 +63,13 @@
def test_project_create_enabled(self):
# Create a project that is enabled
project_name = data_utils.rand_name('project-')
- resp, project = self.client.create_project(
+ _, project = self.client.create_project(
project_name, enabled=True)
self.data.projects.append(project)
project_id = project['id']
- st1 = resp['status']
en1 = project['enabled']
- self.assertEqual(st1, '201')
self.assertTrue(en1, 'Enable should be True in response')
- resp, body = self.client.get_project(project_id)
+ _, body = self.client.get_project(project_id)
en2 = body['enabled']
self.assertTrue(en2, 'Enable should be True in lookup')
@@ -83,15 +77,13 @@
def test_project_create_not_enabled(self):
# Create a project that is not enabled
project_name = data_utils.rand_name('project-')
- resp, project = self.client.create_project(
+ _, project = self.client.create_project(
project_name, enabled=False)
self.data.projects.append(project)
- st1 = resp['status']
en1 = project['enabled']
- self.assertEqual(st1, '201')
self.assertEqual('false', str(en1).lower(),
'Enable should be False in response')
- resp, body = self.client.get_project(project['id'])
+ _, body = self.client.get_project(project['id'])
en2 = body['enabled']
self.assertEqual('false', str(en2).lower(),
'Enable should be False in lookup')
@@ -100,19 +92,17 @@
def test_project_update_name(self):
# Update name attribute of a project
p_name1 = data_utils.rand_name('project-')
- resp, project = self.client.create_project(p_name1)
+ _, project = self.client.create_project(p_name1)
self.data.projects.append(project)
resp1_name = project['name']
p_name2 = data_utils.rand_name('project2-')
- resp, body = self.client.update_project(project['id'], name=p_name2)
- st2 = resp['status']
+ _, body = self.client.update_project(project['id'], name=p_name2)
resp2_name = body['name']
- self.assertEqual(st2, '200')
self.assertNotEqual(resp1_name, resp2_name)
- resp, body = self.client.get_project(project['id'])
+ _, body = self.client.get_project(project['id'])
resp3_name = body['name']
self.assertNotEqual(resp1_name, resp3_name)
@@ -124,20 +114,18 @@
# Update description attribute of a project
p_name = data_utils.rand_name('project-')
p_desc = data_utils.rand_name('desc-')
- resp, project = self.client.create_project(
+ _, project = self.client.create_project(
p_name, description=p_desc)
self.data.projects.append(project)
resp1_desc = project['description']
p_desc2 = data_utils.rand_name('desc2-')
- resp, body = self.client.update_project(
+ _, body = self.client.update_project(
project['id'], description=p_desc2)
- st2 = resp['status']
resp2_desc = body['description']
- self.assertEqual(st2, '200')
self.assertNotEqual(resp1_desc, resp2_desc)
- resp, body = self.client.get_project(project['id'])
+ _, body = self.client.get_project(project['id'])
resp3_desc = body['description']
self.assertNotEqual(resp1_desc, resp3_desc)
@@ -149,20 +137,18 @@
# Update the enabled attribute of a project
p_name = data_utils.rand_name('project-')
p_en = False
- resp, project = self.client.create_project(p_name, enabled=p_en)
+ _, project = self.client.create_project(p_name, enabled=p_en)
self.data.projects.append(project)
resp1_en = project['enabled']
p_en2 = True
- resp, body = self.client.update_project(
+ _, body = self.client.update_project(
project['id'], enabled=p_en2)
- st2 = resp['status']
resp2_en = body['enabled']
- self.assertEqual(st2, '200')
self.assertNotEqual(resp1_en, resp2_en)
- resp, body = self.client.get_project(project['id'])
+ _, body = self.client.get_project(project['id'])
resp3_en = body['enabled']
self.assertNotEqual(resp1_en, resp3_en)
@@ -171,27 +157,26 @@
@test.attr(type='gate')
def test_associate_user_to_project(self):
- #Associate a user to a project
- #Create a Project
+ # Associate a user to a project
+ # Create a Project
p_name = data_utils.rand_name('project-')
- resp, project = self.client.create_project(p_name)
+ _, project = self.client.create_project(p_name)
self.data.projects.append(project)
- #Create a User
+ # Create a User
u_name = data_utils.rand_name('user-')
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_name('pass-')
- resp, user = self.client.create_user(
+ _, user = self.client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=project['id'])
- self.assertEqual(resp['status'], '201')
# Delete the User at the end of this method
self.addCleanup(self.client.delete_user, user['id'])
# Get User To validate the user details
- resp, new_user_get = self.client.get_user(user['id'])
- #Assert response body of GET
+ _, new_user_get = self.client.get_user(user['id'])
+ # Assert response body of GET
self.assertEqual(u_name, new_user_get['name'])
self.assertEqual(u_desc, new_user_get['description'])
self.assertEqual(project['id'],
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 6b60d04..9e8f613 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -32,7 +32,7 @@
def test_project_create_duplicate(self):
# Project names should be unique
project_name = data_utils.rand_name('project-dup-')
- resp, project = self.client.create_project(project_name)
+ _, project = self.client.create_project(project_name)
self.data.projects.append(project)
self.assertRaises(
@@ -63,7 +63,7 @@
def test_project_delete_by_unauthorized_user(self):
# Non-admin user should not be able to delete a project
project_name = data_utils.rand_name('project-')
- resp, project = self.client.create_project(project_name)
+ _, project = self.client.create_project(project_name)
self.data.projects.append(project)
self.assertRaises(
exceptions.Unauthorized, self.non_admin_client.delete_project,
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
new file mode 100644
index 0000000..c8b034f
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -0,0 +1,102 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class RegionsTestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(RegionsTestJSON, cls).setUpClass()
+ cls.setup_regions = list()
+ cls.client = cls.region_client
+ for i in range(2):
+ r_description = data_utils.rand_name('description-')
+ _, region = cls.client.create_region(r_description)
+ cls.setup_regions.append(region)
+
+ @classmethod
+ def tearDownClass(cls):
+ for r in cls.setup_regions:
+ cls.client.delete_region(r['id'])
+ super(RegionsTestJSON, cls).tearDownClass()
+
+ def _delete_region(self, region_id):
+ resp, _ = self.client.delete_region(region_id)
+ self.assertEqual(204, resp.status)
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_region, region_id)
+
+ @test.attr(type='gate')
+ def test_create_update_get_delete_region(self):
+ r_description = data_utils.rand_name('description-')
+ resp, region = self.client.create_region(
+ r_description, parent_region_id=self.setup_regions[0]['id'])
+ self.assertEqual(201, resp.status)
+ self.addCleanup(self._delete_region, region['id'])
+ self.assertEqual(r_description, region['description'])
+ self.assertEqual(self.setup_regions[0]['id'],
+ region['parent_region_id'])
+ # Update region with new description and parent ID
+ r_alt_description = data_utils.rand_name('description-')
+ resp, region = self.client.update_region(
+ region['id'],
+ description=r_alt_description,
+ parent_region_id=self.setup_regions[1]['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(r_alt_description, region['description'])
+ self.assertEqual(self.setup_regions[1]['id'],
+ region['parent_region_id'])
+ # Get the details of region
+ resp, region = self.client.get_region(region['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(r_alt_description, region['description'])
+ self.assertEqual(self.setup_regions[1]['id'],
+ region['parent_region_id'])
+
+ @test.attr(type='smoke')
+ def test_create_region_with_specific_id(self):
+ # Create a region with a specific id
+ r_region_id = data_utils.rand_uuid()
+ r_description = data_utils.rand_name('description-')
+ resp, region = self.client.create_region(
+ r_description, unique_region_id=r_region_id)
+ self.addCleanup(self._delete_region, region['id'])
+ # Asserting Create Region with specific id response body
+ self.assertEqual(201, resp.status)
+ self.assertEqual(r_region_id, region['id'])
+ self.assertEqual(r_description, region['description'])
+
+ @test.attr(type='gate')
+ def test_list_regions(self):
+ # Get a list of regions
+ resp, fetched_regions = self.client.list_regions()
+ self.assertEqual(200, resp.status)
+ missing_regions =\
+ [e for e in self.setup_regions if e not in fetched_regions]
+ # Asserting List Regions response
+ self.assertEqual(0, len(missing_regions),
+ "Failed to find region %s in fetched list" %
+ ', '.join(str(e) for e in missing_regions))
+
+
+class RegionsTestXML(RegionsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 90dccca..2e732fe 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,131 +25,117 @@
@test.safe_setup
def setUpClass(cls):
super(RolesV3TestJSON, cls).setUpClass()
+ for _ in range(3):
+ role_name = data_utils.rand_name(name='role-')
+ _, role = cls.client.create_role(role_name)
+ cls.data.v3_roles.append(role)
cls.fetched_role_ids = list()
u_name = data_utils.rand_name('user-')
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
cls.u_password = data_utils.rand_name('pass-')
- resp = [None] * 5
- resp[0], cls.domain = cls.client.create_domain(
+ _, cls.domain = cls.client.create_domain(
data_utils.rand_name('domain-'),
description=data_utils.rand_name('domain-desc-'))
- resp[1], cls.project = cls.client.create_project(
+ _, cls.project = cls.client.create_project(
data_utils.rand_name('project-'),
description=data_utils.rand_name('project-desc-'),
domain_id=cls.domain['id'])
- resp[2], cls.group_body = cls.client.create_group(
+ _, cls.group_body = cls.client.create_group(
data_utils.rand_name('Group-'), project_id=cls.project['id'],
domain_id=cls.domain['id'])
- resp[3], cls.user_body = cls.client.create_user(
+ _, cls.user_body = cls.client.create_user(
u_name, description=u_desc, password=cls.u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])
- resp[4], cls.role = cls.client.create_role(
+ _, cls.role = cls.client.create_role(
data_utils.rand_name('Role-'))
- for r in resp:
- assert r['status'] == '201', (
- "Expected 201, but got: %s" % r['status'])
@classmethod
def tearDownClass(cls):
- resp = [None] * 5
- resp[0], _ = cls.client.delete_role(cls.role['id'])
- resp[1], _ = cls.client.delete_group(cls.group_body['id'])
- resp[2], _ = cls.client.delete_user(cls.user_body['id'])
- resp[3], _ = cls.client.delete_project(cls.project['id'])
+ cls.client.delete_role(cls.role['id'])
+ cls.client.delete_group(cls.group_body['id'])
+ cls.client.delete_user(cls.user_body['id'])
+ cls.client.delete_project(cls.project['id'])
# NOTE(harika-vakadi): It is necessary to disable the domain
# before deleting,or else it would result in unauthorized error
cls.client.update_domain(cls.domain['id'], enabled=False)
- resp[4], _ = cls.client.delete_domain(cls.domain['id'])
- for r in resp:
- assert r['status'] == '204', (
- "Expected 204, but got: %s" % r['status'])
+ cls.client.delete_domain(cls.domain['id'])
super(RolesV3TestJSON, cls).tearDownClass()
- def _list_assertions(self, resp, body, fetched_role_ids, role_id):
- self.assertEqual(resp['status'], '200')
+ def _list_assertions(self, body, fetched_role_ids, role_id):
self.assertEqual(len(body), 1)
self.assertIn(role_id, fetched_role_ids)
@test.attr(type='smoke')
def test_role_create_update_get_list(self):
r_name = data_utils.rand_name('Role-')
- resp, role = self.client.create_role(r_name)
+ _, role = self.client.create_role(r_name)
self.addCleanup(self.client.delete_role, role['id'])
- self.assertEqual(resp['status'], '201')
self.assertIn('name', role)
self.assertEqual(role['name'], r_name)
new_name = data_utils.rand_name('NewRole-')
- resp, updated_role = self.client.update_role(new_name, role['id'])
- self.assertEqual(resp['status'], '200')
+ _, updated_role = self.client.update_role(new_name, role['id'])
self.assertIn('name', updated_role)
self.assertIn('id', updated_role)
self.assertIn('links', updated_role)
self.assertNotEqual(r_name, updated_role['name'])
- resp, new_role = self.client.get_role(role['id'])
- self.assertEqual(resp['status'], '200')
+ _, new_role = self.client.get_role(role['id'])
self.assertEqual(new_name, new_role['name'])
self.assertEqual(updated_role['id'], new_role['id'])
- resp, roles = self.client.list_roles()
- self.assertEqual(resp['status'], '200')
+ _, roles = self.client.list_roles()
self.assertIn(role['id'], [r['id'] for r in roles])
@test.attr(type='smoke')
def test_grant_list_revoke_role_to_user_on_project(self):
- resp, _ = self.client.assign_user_role_on_project(
+ self.client.assign_user_role_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
- resp, roles = self.client.list_user_roles_on_project(
+ _, roles = self.client.list_user_roles_on_project(
self.project['id'], self.user_body['id'])
for i in roles:
self.fetched_role_ids.append(i['id'])
- self._list_assertions(resp, roles, self.fetched_role_ids,
+ self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- resp, _ = self.client.revoke_role_from_user_on_project(
+ self.client.revoke_role_from_user_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
@test.attr(type='smoke')
def test_grant_list_revoke_role_to_user_on_domain(self):
- resp, _ = self.client.assign_user_role_on_domain(
+ self.client.assign_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
- resp, roles = self.client.list_user_roles_on_domain(
+ _, roles = self.client.list_user_roles_on_domain(
self.domain['id'], self.user_body['id'])
for i in roles:
self.fetched_role_ids.append(i['id'])
- self._list_assertions(resp, roles, self.fetched_role_ids,
+ self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- resp, _ = self.client.revoke_role_from_user_on_domain(
+ self.client.revoke_role_from_user_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
@test.attr(type='smoke')
def test_grant_list_revoke_role_to_group_on_project(self):
# Grant role to group on project
- resp, _ = self.client.assign_group_role_on_project(
+ self.client.assign_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
# List group roles on project
- resp, roles = self.client.list_group_roles_on_project(
+ _, roles = self.client.list_group_roles_on_project(
self.project['id'], self.group_body['id'])
for i in roles:
self.fetched_role_ids.append(i['id'])
- self._list_assertions(resp, roles, self.fetched_role_ids,
+ self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
# Add user to group, and insure user has role on project
self.client.add_group_user(self.group_body['id'], self.user_body['id'])
@@ -163,28 +149,32 @@
self.assertEqual(len(roles), 1)
self.assertEqual(roles[0]['id'], self.role['id'])
# Revoke role to group on project
- resp, _ = self.client.revoke_role_from_group_on_project(
+ self.client.revoke_role_from_group_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
@test.attr(type='smoke')
def test_grant_list_revoke_role_to_group_on_domain(self):
- resp, _ = self.client.assign_group_role_on_domain(
+ self.client.assign_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
- resp, roles = self.client.list_group_roles_on_domain(
+ _, roles = self.client.list_group_roles_on_domain(
self.domain['id'], self.group_body['id'])
for i in roles:
self.fetched_role_ids.append(i['id'])
- self._list_assertions(resp, roles, self.fetched_role_ids,
+ self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- resp, _ = self.client.revoke_role_from_group_on_domain(
+ self.client.revoke_role_from_group_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
- self.assertEqual(resp['status'], '204')
+
+ @test.attr(type='gate')
+ def test_list_roles(self):
+ # Return a list of all roles
+ _, body = self.client.list_roles()
+ found = [role for role in body if role in self.data.v3_roles]
+ self.assertEqual(len(found), len(self.data.v3_roles))
class RolesV3TestXML(RolesV3TestJSON):
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index 36e5327..f6078da 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -28,9 +28,8 @@
name = data_utils.rand_name('service-')
serv_type = data_utils.rand_name('type--')
desc = data_utils.rand_name('description-')
- resp, body = self.service_client.create_service(name, serv_type,
- description=desc)
- self.assertEqual('201', resp['status'])
+ _, body = self.service_client.create_service(name, serv_type,
+ description=desc)
# Deleting the service created in this method
self.addCleanup(self.service_client.delete_service, body['id'])
@@ -38,14 +37,13 @@
resp1_desc = body['description']
s_desc2 = data_utils.rand_name('desc2-')
- resp, body = self.service_client.update_service(
+ _, body = self.service_client.update_service(
s_id, description=s_desc2)
resp2_desc = body['description']
- self.assertEqual('200', resp['status'])
self.assertNotEqual(resp1_desc, resp2_desc)
# Get service
- resp, body = self.service_client.get_service(s_id)
+ _, body = self.service_client.get_service(s_id)
resp3_desc = body['description']
self.assertNotEqual(resp1_desc, resp3_desc)
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index fe3eb03..e61b738 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -30,23 +30,21 @@
u_desc = '%s-description' % u_name
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_name('pass-')
- resp, user = self.client.create_user(
+ _, user = self.client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email)
- self.assertEqual(201, resp.status)
self.addCleanup(self.client.delete_user, user['id'])
# Perform Authentication
resp, body = self.token.auth(user['id'], u_password)
self.assertEqual(201, resp.status)
subject_token = resp['x-subject-token']
# Perform GET Token
- resp, token_details = self.client.get_token(subject_token)
- self.assertEqual(200, resp.status)
+ _, token_details = self.client.get_token(subject_token)
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.client.delete_token(subject_token)
+ self.client.delete_token(subject_token)
self.assertRaises(exceptions.NotFound, self.client.get_token,
subject_token)
@@ -63,35 +61,29 @@
# Create a user.
user_name = data_utils.rand_name(name='user-')
user_password = data_utils.rand_name(name='pass-')
- resp, user = self.client.create_user(user_name, password=user_password)
- self.assertEqual(201, resp.status)
+ _, user = self.client.create_user(user_name, password=user_password)
self.addCleanup(self.client.delete_user, user['id'])
# Create a couple projects
project1_name = data_utils.rand_name(name='project-')
- resp, project1 = self.client.create_project(project1_name)
- self.assertEqual(201, resp.status)
+ _, project1 = self.client.create_project(project1_name)
self.addCleanup(self.client.delete_project, project1['id'])
project2_name = data_utils.rand_name(name='project-')
- resp, project2 = self.client.create_project(project2_name)
- self.assertEqual(201, resp.status)
+ _, project2 = self.client.create_project(project2_name)
self.addCleanup(self.client.delete_project, project2['id'])
# Create a role
role_name = data_utils.rand_name(name='role-')
- resp, role = self.client.create_role(role_name)
- self.assertEqual(201, resp.status)
+ _, role = self.client.create_role(role_name)
self.addCleanup(self.client.delete_role, role['id'])
# Grant the user the role on both projects.
- resp, _ = self.client.assign_user_role(project1['id'], user['id'],
- role['id'])
- self.assertEqual(204, resp.status)
+ self.client.assign_user_role(project1['id'], user['id'],
+ role['id'])
- resp, _ = self.client.assign_user_role(project2['id'], user['id'],
- role['id'])
- self.assertEqual(204, resp.status)
+ self.client.assign_user_role(project2['id'], user['id'],
+ role['id'])
# Get an unscoped token.
resp, token_auth = self.token.auth(user=user['id'],
@@ -145,8 +137,7 @@
self.assertEqual(role['name'], token_auth['token']['roles'][0]['name'])
# Revoke the unscoped token.
- resp, _ = self.client.delete_token(token1_id)
- self.assertEqual(204, resp.status)
+ self.client.delete_token(token1_id)
# Now get another scoped token using the unscoped token.
resp, token_auth = self.token.auth(token=token_id,
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 8e3a7d1..fed5171 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -54,25 +54,22 @@
u_desc = self.trustor_username + 'description'
u_email = self.trustor_username + '@testmail.xx'
self.trustor_password = data_utils.rand_name('pass-')
- resp, user = self.client.create_user(
+ _, user = self.client.create_user(
self.trustor_username,
description=u_desc,
password=self.trustor_password,
email=u_email,
project_id=self.trustor_project_id)
- self.assertEqual(resp['status'], '201')
self.trustor_user_id = user['id']
# And two roles, one we'll delegate and one we won't
self.delegated_role = data_utils.rand_name('DelegatedRole-')
self.not_delegated_role = data_utils.rand_name('NotDelegatedRole-')
- resp, role = self.client.create_role(self.delegated_role)
- self.assertEqual(resp['status'], '201')
+ _, role = self.client.create_role(self.delegated_role)
self.delegated_role_id = role['id']
- resp, role = self.client.create_role(self.not_delegated_role)
- self.assertEqual(resp['status'], '201')
+ _, role = self.client.create_role(self.not_delegated_role)
self.not_delegated_role_id = role['id']
# Assign roles to trustor
@@ -108,14 +105,13 @@
def create_trust(self, impersonate=True, expires=None):
- resp, trust_create = self.trustor_client.create_trust(
+ _, trust_create = self.trustor_client.create_trust(
trustor_user_id=self.trustor_user_id,
trustee_user_id=self.trustee_user_id,
project_id=self.trustor_project_id,
role_names=[self.delegated_role],
impersonation=impersonate,
expires_at=expires)
- self.assertEqual('201', resp['status'])
self.trust_id = trust_create['id']
return trust_create
@@ -141,8 +137,7 @@
self.assertEqual(1, len(trust['roles']))
def get_trust(self):
- resp, trust_get = self.trustor_client.get_trust(self.trust_id)
- self.assertEqual('200', resp['status'])
+ _, trust_get = self.trustor_client.get_trust(self.trust_id)
return trust_get
def validate_role(self, role):
@@ -155,22 +150,26 @@
self.assertNotIn('v3/roles/%s' % self.not_delegated_role_id,
role['links']['self'])
+ @test.skip_because(bug='1334368')
def check_trust_roles(self):
# Check we find the delegated role
- resp, roles_get = self.trustor_client.get_trust_roles(
+ _, roles_get = self.trustor_client.get_trust_roles(
self.trust_id)
- self.assertEqual('200', resp['status'])
self.assertEqual(1, len(roles_get))
self.validate_role(roles_get[0])
- resp, role_get = self.trustor_client.get_trust_role(
+ _, role_get = self.trustor_client.get_trust_role(
self.trust_id, self.delegated_role_id)
- self.assertEqual('200', resp['status'])
self.validate_role(role_get)
- resp, role_get = self.trustor_client.check_trust_role(
+ _, role_get = self.trustor_client.check_trust_role(
self.trust_id, self.delegated_role_id)
- self.assertEqual('204', resp['status'])
+ # This tempest two-step change conflicted with the change
+ # moving response checking to the client. This test should be
+ # re-enabled by removing the following assert and changing
+ # the response code in tempest/services/identity/v3/json/
+ # identity_client.py in the check_trust_role_method.
+ # self.assertEqual('200', resp['status'])
# And that we don't find not_delegated_role
self.assertRaises(exceptions.NotFound,
@@ -184,8 +183,7 @@
self.not_delegated_role_id)
def delete_trust(self):
- resp, trust_delete = self.trustor_client.delete_trust(self.trust_id)
- self.assertEqual('204', resp['status'])
+ self.trustor_client.delete_trust(self.trust_id)
self.assertRaises(exceptions.NotFound,
self.trustor_client.get_trust,
self.trust_id)
@@ -253,17 +251,15 @@
@test.attr(type='smoke')
def test_get_trusts_query(self):
self.create_trust()
- resp, trusts_get = self.trustor_client.get_trusts(
+ _, trusts_get = self.trustor_client.get_trusts(
trustor_user_id=self.trustor_user_id)
- self.assertEqual('200', resp['status'])
self.assertEqual(1, len(trusts_get))
self.validate_trust(trusts_get[0], summary=True)
@test.attr(type='smoke')
def test_get_trusts_all(self):
self.create_trust()
- resp, trusts_get = self.client.get_trusts()
- self.assertEqual('200', resp['status'])
+ _, trusts_get = self.client.get_trusts()
trusts = [t for t in trusts_get
if t['id'] == self.trust_id]
self.assertEqual(1, len(trusts))
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 7316c7f..558575e 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -29,13 +29,13 @@
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_name('pass-')
- resp, user = self.client.create_user(
+ _, user = self.client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False)
# Delete the User at the end of this method
self.addCleanup(self.client.delete_user, user['id'])
# Creating second project for updation
- resp, project = self.client.create_project(
+ _, project = self.client.create_project(
data_utils.rand_name('project-'),
description=data_utils.rand_name('project-desc-'))
# Delete the Project at the end of this method
@@ -44,12 +44,10 @@
u_name2 = data_utils.rand_name('user2-')
u_email2 = u_name2 + '@testmail.tm'
u_description2 = u_name2 + ' description'
- resp, update_user = self.client.update_user(
+ _, update_user = self.client.update_user(
user['id'], name=u_name2, description=u_description2,
project_id=project['id'],
email=u_email2, enabled=False)
- # Assert response body of update user.
- self.assertEqual(200, resp.status)
self.assertEqual(u_name2, update_user['name'])
self.assertEqual(u_description2, update_user['description'])
self.assertEqual(project['id'],
@@ -57,7 +55,7 @@
self.assertEqual(u_email2, update_user['email'])
self.assertEqual('false', str(update_user['enabled']).lower())
# GET by id after updation
- resp, new_user_get = self.client.get_user(user['id'])
+ _, new_user_get = self.client.get_user(user['id'])
# Assert response body of GET after updation
self.assertEqual(u_name2, new_user_get['name'])
self.assertEqual(u_description2, new_user_get['description'])
@@ -107,8 +105,7 @@
user['id'],
role['id'])
assigned_project_ids.append(project['id'])
- resp, body = self.client.list_user_projects(user['id'])
- self.assertEqual(200, resp.status)
+ _, body = self.client.list_user_projects(user['id'])
for i in body:
fetched_project_ids.append(i['id'])
# verifying the project ids in list
@@ -124,7 +121,7 @@
def test_get_user(self):
# Get a user detail
self.data.setup_test_v3_user()
- resp, user = self.client.get_user(self.data.v3_user['id'])
+ _, user = self.client.get_user(self.data.v3_user['id'])
self.assertEqual(self.data.v3_user['id'], user['id'])
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index e4e74c1..0991576 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -96,6 +96,7 @@
cls.client = cls.os_adm.identity_v3_client
cls.token = cls.os_adm.token_v3_client
cls.endpoints_client = cls.os_adm.endpoints_client
+ cls.region_client = cls.os_adm.region_client
cls.data = DataGenerator(cls.client)
cls.non_admin_client = cls.os.identity_v3_client
cls.service_client = cls.os_adm.service_client
@@ -135,17 +136,17 @@
self.test_user = data_utils.rand_name('test_user_')
self.test_password = data_utils.rand_name('pass_')
self.test_email = self.test_user + '@testmail.tm'
- resp, self.user = self.client.create_user(self.test_user,
- self.test_password,
- self.tenant['id'],
- self.test_email)
+ _, self.user = self.client.create_user(self.test_user,
+ self.test_password,
+ self.tenant['id'],
+ self.test_email)
self.users.append(self.user)
def setup_test_tenant(self):
"""Set up a test tenant."""
self.test_tenant = data_utils.rand_name('test_tenant_')
self.test_description = data_utils.rand_name('desc_')
- resp, self.tenant = self.client.create_tenant(
+ _, self.tenant = self.client.create_tenant(
name=self.test_tenant,
description=self.test_description)
self.tenants.append(self.tenant)
@@ -153,7 +154,7 @@
def setup_test_role(self):
"""Set up a test role."""
self.test_role = data_utils.rand_name('role')
- resp, self.role = self.client.create_role(self.test_role)
+ _, self.role = self.client.create_role(self.test_role)
self.roles.append(self.role)
def setup_test_v3_user(self):
@@ -162,7 +163,7 @@
self.test_user = data_utils.rand_name('test_user_')
self.test_password = data_utils.rand_name('pass_')
self.test_email = self.test_user + '@testmail.tm'
- resp, self.v3_user = self.client.create_user(
+ _, self.v3_user = self.client.create_user(
self.test_user,
password=self.test_password,
project_id=self.project['id'],
@@ -173,7 +174,7 @@
"""Set up a test project."""
self.test_project = data_utils.rand_name('test_project_')
self.test_description = data_utils.rand_name('desc_')
- resp, self.project = self.client.create_project(
+ _, self.project = self.client.create_project(
name=self.test_project,
description=self.test_description)
self.projects.append(self.project)
@@ -181,7 +182,7 @@
def setup_test_v3_role(self):
"""Set up a test v3 role."""
self.test_role = data_utils.rand_name('role')
- resp, self.v3_role = self.client.create_role(self.test_role)
+ _, self.v3_role = self.client.create_role(self.test_role)
self.v3_roles.append(self.v3_role)
def teardown_all(self):
diff --git a/tempest/api/identity/test_extension.py b/tempest/api/identity/test_extension.py
new file mode 100644
index 0000000..a06ee53
--- /dev/null
+++ b/tempest/api/identity/test_extension.py
@@ -0,0 +1,36 @@
+# Copyright 2014 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.api.identity import base
+from tempest import test
+
+
+class ExtensionTestJSON(base.BaseIdentityV2AdminTest):
+ _interface = 'json'
+
+ @test.attr(type='gate')
+ def test_list_extensions(self):
+ # List all the extensions
+ _, body = self.non_admin_client.list_extensions()
+ self.assertNotEmpty(body)
+ keys = ['name', 'updated', 'alias', 'links',
+ 'namespace', 'description']
+ for value in body:
+ for key in keys:
+ self.assertIn(key, value)
+
+
+class ExtensionTestXML(ExtensionTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 9981292..02d391b 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -142,9 +142,9 @@
def _create_image(self):
name = data_utils.rand_name('image')
- resp, image = self.os_img_client.create_image(name,
- container_format='bare',
- disk_format='raw')
+ _, image = self.os_img_client.create_image(name,
+ container_format='bare',
+ disk_format='raw')
image_id = image['id']
self.addCleanup(self.os_img_client.delete_image, image_id)
return image_id
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index 4cbb62f..f91cb69 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -22,27 +22,21 @@
@test.attr(type='gate')
def test_add_image_member(self):
image = self._create_image()
- resp = self.client.add_member(self.alt_tenant_id, image)
- self.assertEqual(204, resp.status)
- resp, body = self.client.get_image_membership(image)
- self.assertEqual(200, resp.status)
+ self.client.add_member(self.alt_tenant_id, image)
+ _, body = self.client.get_image_membership(image)
members = body['members']
members = map(lambda x: x['member_id'], members)
self.assertIn(self.alt_tenant_id, members)
# get image as alt user
- resp, body = self.alt_img_cli.get_image(image)
- self.assertEqual(200, resp.status)
+ self.alt_img_cli.get_image(image)
@test.attr(type='gate')
def test_get_shared_images(self):
image = self._create_image()
- resp = self.client.add_member(self.alt_tenant_id, image)
- self.assertEqual(204, resp.status)
+ self.client.add_member(self.alt_tenant_id, image)
share_image = self._create_image()
- resp = self.client.add_member(self.alt_tenant_id, share_image)
- self.assertEqual(204, resp.status)
- resp, body = self.client.get_shared_images(self.alt_tenant_id)
- self.assertEqual(200, resp.status)
+ self.client.add_member(self.alt_tenant_id, share_image)
+ _, body = self.client.get_shared_images(self.alt_tenant_id)
images = body['shared_images']
images = map(lambda x: x['image_id'], images)
self.assertIn(share_image, images)
@@ -51,11 +45,8 @@
@test.attr(type='gate')
def test_remove_member(self):
image_id = self._create_image()
- resp = self.client.add_member(self.alt_tenant_id, image_id)
- self.assertEqual(204, resp.status)
- resp = self.client.delete_member(self.alt_tenant_id, image_id)
- self.assertEqual(204, resp.status)
- resp, body = self.client.get_image_membership(image_id)
- self.assertEqual(200, resp.status)
+ self.client.add_member(self.alt_tenant_id, image_id)
+ self.client.delete_member(self.alt_tenant_id, image_id)
+ _, body = self.client.get_image_membership(image_id)
members = body['members']
self.assertEqual(0, len(members), str(members))
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index b90891b..8528e42 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -30,38 +30,37 @@
def test_register_then_upload(self):
# Register, then upload an image
properties = {'prop1': 'val1'}
- resp, body = self.create_image(name='New Name',
- container_format='bare',
- disk_format='raw',
- is_public=True,
- properties=properties)
+ _, body = self.create_image(name='New Name',
+ container_format='bare',
+ disk_format='raw',
+ is_public=False,
+ properties=properties)
self.assertIn('id', body)
image_id = body.get('id')
self.assertEqual('New Name', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
for key, val in properties.items():
self.assertEqual(val, body.get('properties')[key])
# Now try uploading an image file
image_file = StringIO.StringIO(('*' * 1024))
- resp, body = self.client.update_image(image_id, data=image_file)
+ _, body = self.client.update_image(image_id, data=image_file)
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@test.attr(type='gate')
def test_register_remote_image(self):
# Register a new remote image
- resp, body = self.create_image(name='New Remote Image',
- container_format='bare',
- disk_format='raw', is_public=True,
- location='http://example.com'
- '/someimage.iso',
- properties={'key1': 'value1',
- 'key2': 'value2'})
+ _, body = self.create_image(name='New Remote Image',
+ container_format='bare',
+ disk_format='raw', is_public=False,
+ location=CONF.image.http_image,
+ properties={'key1': 'value1',
+ 'key2': 'value2'})
self.assertIn('id', body)
self.assertEqual('New Remote Image', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.assertEqual('active', body.get('status'))
properties = body.get('properties')
self.assertEqual(properties['key1'], 'value1')
@@ -69,37 +68,35 @@
@test.attr(type='gate')
def test_register_http_image(self):
- resp, body = self.create_image(name='New Http Image',
- container_format='bare',
- disk_format='raw', is_public=True,
- copy_from=CONF.image.http_image)
+ _, body = self.create_image(name='New Http Image',
+ container_format='bare',
+ disk_format='raw', is_public=False,
+ copy_from=CONF.image.http_image)
self.assertIn('id', body)
image_id = body.get('id')
self.assertEqual('New Http Image', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.client.wait_for_image_status(image_id, 'active')
- resp, body = self.client.get_image(image_id)
- self.assertEqual(resp['status'], '200')
+ self.client.get_image(image_id)
@test.attr(type='gate')
def test_register_image_with_min_ram(self):
# Register an image with min ram
properties = {'prop1': 'val1'}
- resp, body = self.create_image(name='New_image_with_min_ram',
- container_format='bare',
- disk_format='raw',
- is_public=True,
- min_ram=40,
- properties=properties)
+ _, body = self.create_image(name='New_image_with_min_ram',
+ container_format='bare',
+ disk_format='raw',
+ is_public=False,
+ min_ram=40,
+ properties=properties)
self.assertIn('id', body)
self.assertEqual('New_image_with_min_ram', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
self.assertEqual(40, body.get('min_ram'))
for key, val in properties.items():
self.assertEqual(val, body.get('properties')[key])
- resp, body = self.client.delete_image(body['id'])
- self.assertEqual('200', resp['status'])
+ self.client.delete_image(body['id'])
class ListImagesTest(base.BaseV1ImageTest):
@@ -143,12 +140,12 @@
image
"""
name = 'New Remote Image %s' % name
- location = 'http://example.com/someimage_%s.iso' % name
- resp, image = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- is_public=True,
- location=location)
+ location = CONF.image.http_image
+ _, image = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ is_public=False,
+ location=location)
image_id = image['id']
return image_id
@@ -162,26 +159,24 @@
"""
image_file = StringIO.StringIO('*' * size)
name = 'New Standard Image %s' % name
- resp, image = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- is_public=True, data=image_file)
+ _, image = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ is_public=False, data=image_file)
image_id = image['id']
return image_id
@test.attr(type='gate')
def test_index_no_params(self):
# Simple test to see all fixture images returned
- resp, images_list = self.client.image_list()
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list()
image_list = map(lambda x: x['id'], images_list)
for image_id in self.created_images:
self.assertIn(image_id, image_list)
@test.attr(type='gate')
def test_index_disk_format(self):
- resp, images_list = self.client.image_list(disk_format='ami')
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list(disk_format='ami')
for image in images_list:
self.assertEqual(image['disk_format'], 'ami')
result_set = set(map(lambda x: x['id'], images_list))
@@ -190,8 +185,7 @@
@test.attr(type='gate')
def test_index_container_format(self):
- resp, images_list = self.client.image_list(container_format='bare')
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list(container_format='bare')
for image in images_list:
self.assertEqual(image['container_format'], 'bare')
result_set = set(map(lambda x: x['id'], images_list))
@@ -200,8 +194,7 @@
@test.attr(type='gate')
def test_index_max_size(self):
- resp, images_list = self.client.image_list(size_max=42)
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list(size_max=42)
for image in images_list:
self.assertTrue(image['size'] <= 42)
result_set = set(map(lambda x: x['id'], images_list))
@@ -210,8 +203,7 @@
@test.attr(type='gate')
def test_index_min_size(self):
- resp, images_list = self.client.image_list(size_min=142)
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list(size_min=142)
for image in images_list:
self.assertTrue(image['size'] >= 142)
result_set = set(map(lambda x: x['id'], images_list))
@@ -220,10 +212,9 @@
@test.attr(type='gate')
def test_index_status_active_detail(self):
- resp, images_list = self.client.image_list_detail(status='active',
- sort_key='size',
- sort_dir='desc')
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list_detail(status='active',
+ sort_key='size',
+ sort_dir='desc')
top_size = images_list[0]['size'] # We have non-zero sized images
for image in images_list:
size = image['size']
@@ -233,9 +224,8 @@
@test.attr(type='gate')
def test_index_name(self):
- resp, images_list = self.client.image_list_detail(
+ _, images_list = self.client.image_list_detail(
name='New Remote Image dup')
- self.assertEqual(resp['status'], '200')
result_set = set(map(lambda x: x['id'], images_list))
for image in images_list:
self.assertEqual(image['name'], 'New Remote Image dup')
@@ -247,11 +237,17 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(ListSnapshotImagesTest, cls).setUpClass()
+ # This test class only uses nova v3 api to create snapshot
+ # as the similar test which uses nova v2 api already exists
+ # in nova v2 compute images api tests.
+ # Since nova v3 doesn't have images api proxy, this test
+ # class was added in the image api tests.
if not CONF.compute_feature_enabled.api_v3:
- cls.servers_client = cls.os.servers_client
- else:
- cls.servers_client = cls.os.servers_v3_client
+ skip_msg = ("%s skipped as nova v3 api is not available" %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+ super(ListSnapshotImagesTest, cls).setUpClass()
+ cls.servers_client = cls.os.servers_v3_client
cls.servers = []
# We add a few images here to test the listing functionality of
# the images API
@@ -261,10 +257,10 @@
cls.snapshot_set = set((cls.snapshot,))
image_file = StringIO.StringIO('*' * 42)
- resp, image = cls.create_image(name="Standard Image",
- container_format='ami',
- disk_format='ami',
- is_public=True, data=image_file)
+ _, image = cls.create_image(name="Standard Image",
+ container_format='ami',
+ disk_format='ami',
+ is_public=False, data=image_file)
cls.image_id = image['id']
cls.client.wait_for_image_status(image['id'], 'active')
@@ -276,13 +272,12 @@
@classmethod
def _create_snapshot(cls, name, image_id, flavor, **kwargs):
- resp, server = cls.servers_client.create_server(
+ _, server = cls.servers_client.create_server(
name, image_id, flavor, **kwargs)
cls.servers.append(server)
cls.servers_client.wait_for_server_status(
server['id'], 'ACTIVE')
- resp, image = cls.servers_client.create_image(
- server['id'], name)
+ resp, _ = cls.servers_client.create_image(server['id'], name)
image_id = data_utils.parse_image_id(resp['location'])
cls.created_images.append(image_id)
cls.client.wait_for_image_status(image_id,
@@ -293,9 +288,8 @@
@test.services('compute')
def test_index_server_id(self):
# The images should contain images filtered by server id
- resp, images = self.client.image_list_detail(
+ _, images = self.client.image_list_detail(
{'instance_uuid': self.servers[0]['id']})
- self.assertEqual(200, resp.status)
result_set = set(map(lambda x: x['id'], images))
self.assertEqual(self.snapshot_set, result_set)
@@ -304,9 +298,8 @@
def test_index_type(self):
# The list of servers should be filtered by image type
params = {'image_type': 'snapshot'}
- resp, images = self.client.image_list_detail(params)
+ _, images = self.client.image_list_detail(params)
- self.assertEqual(200, resp.status)
result_set = set(map(lambda x: x['id'], images))
self.assertIn(self.snapshot, result_set)
@@ -314,9 +307,8 @@
@test.services('compute')
def test_index_limit(self):
# Verify only the expected number of results are returned
- resp, images = self.client.image_list_detail(limit=1)
+ _, images = self.client.image_list_detail(limit=1)
- self.assertEqual(200, resp.status)
self.assertEqual(1, len(images))
@test.attr(type='gate')
@@ -325,13 +317,11 @@
# Verify an update image is returned
# Becoming ACTIVE will modify the updated time
# Filter by the image's created time
- resp, image = self.client.get_image_meta(self.snapshot)
- self.assertEqual(200, resp.status)
+ _, image = self.client.get_image_meta(self.snapshot)
self.assertEqual(self.snapshot, image['id'])
- resp, images = self.client.image_list_detail(
+ _, images = self.client.image_list_detail(
changes_since=image['updated_at'])
- self.assertEqual(200, resp.status)
result_set = set(map(lambda x: x['id'], images))
self.assertIn(self.image_id, result_set)
self.assertNotIn(self.snapshot, result_set)
@@ -353,18 +343,18 @@
"""
image_file = StringIO.StringIO('*' * size)
name = 'New Standard Image %s' % name
- resp, image = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- is_public=True, data=image_file,
- properties={'key1': 'value1'})
+ _, image = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ is_public=False, data=image_file,
+ properties={'key1': 'value1'})
image_id = image['id']
return image_id
@test.attr(type='gate')
def test_list_image_metadata(self):
# All metadata key/value pairs for an image should be returned
- resp, resp_metadata = self.client.get_image_meta(self.image_id)
+ _, resp_metadata = self.client.get_image_meta(self.image_id)
expected = {'key1': 'value1'}
self.assertEqual(expected, resp_metadata['properties'])
@@ -372,13 +362,12 @@
def test_update_image_metadata(self):
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key2': 'value2'}
- resp, metadata = self.client.get_image_meta(self.image_id)
- self.assertEqual(200, resp.status)
+ _, metadata = self.client.get_image_meta(self.image_id)
self.assertEqual(metadata['properties'], {'key1': 'value1'})
metadata['properties'].update(req_metadata)
- resp, metadata = self.client.update_image(
+ _, metadata = self.client.update_image(
self.image_id, properties=metadata['properties'])
- resp, resp_metadata = self.client.get_image_meta(self.image_id)
+ _, resp_metadata = self.client.get_image_meta(self.image_id)
expected = {'key1': 'alt1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata['properties'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 9eda13e..ae777eb 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -37,30 +37,27 @@
uuid = '00000000-1111-2222-3333-444455556666'
image_name = data_utils.rand_name('image')
- resp, body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='public',
- ramdisk_id=uuid)
-
+ _, body = self.create_image(name=image_name,
+ container_format='bare',
+ disk_format='raw',
+ visibility='private',
+ ramdisk_id=uuid)
self.assertIn('id', body)
image_id = body.get('id')
self.assertIn('name', body)
self.assertEqual(image_name, body['name'])
self.assertIn('visibility', body)
- self.assertEqual('public', body['visibility'])
+ self.assertEqual('private', body['visibility'])
self.assertIn('status', body)
self.assertEqual('queued', body['status'])
# Now try uploading an image file
file_content = '*' * 1024
image_file = StringIO.StringIO(file_content)
- resp, body = self.client.store_image(image_id, image_file)
- self.assertEqual(resp.status, 204)
+ self.client.store_image(image_id, image_file)
# Now try to get image details
- resp, body = self.client.get_image(image_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_image(image_id)
self.assertEqual(image_id, body['id'])
self.assertEqual(image_name, body['name'])
self.assertEqual(uuid, body['ramdisk_id'])
@@ -68,8 +65,7 @@
self.assertEqual(1024, body.get('size'))
# Now try get image file
- resp, body = self.client.get_image_file(image_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_image_file(image_id)
self.assertEqual(file_content, body)
@test.attr(type='gate')
@@ -78,11 +74,10 @@
# Create image
image_name = data_utils.rand_name('image')
- resp, body = self.client.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='public')
- self.assertEqual(201, resp.status)
+ _, body = self.client.create_image(name=image_name,
+ container_format='bare',
+ disk_format='raw',
+ visibility='private')
image_id = body['id']
# Delete Image
@@ -90,8 +85,7 @@
self.client.wait_for_resource_deletion(image_id)
# Verifying deletion
- resp, images = self.client.image_list()
- self.assertEqual(resp.status, 200)
+ _, images = self.client.image_list()
self.assertNotIn(image_id, images)
@test.attr(type='gate')
@@ -100,11 +94,10 @@
# Create image
image_name = data_utils.rand_name('image')
- resp, body = self.client.create_image(name=image_name,
- container_format='bare',
- disk_format='iso',
- visibility='public')
- self.assertEqual(201, resp.status)
+ _, body = self.client.create_image(name=image_name,
+ container_format='bare',
+ disk_format='iso',
+ visibility='private')
self.addCleanup(self.client.delete_image, body['id'])
self.assertEqual('queued', body['status'])
image_id = body['id']
@@ -112,25 +105,18 @@
# Now try uploading an image file
file_content = '*' * 1024
image_file = StringIO.StringIO(file_content)
- resp, body = self.client.store_image(image_id, image_file)
- self.assertEqual(204, resp.status)
+ self.client.store_image(image_id, image_file)
# Update Image
new_image_name = data_utils.rand_name('new-image')
- new_visibility = 'private'
- resp, body = self.client.update_image(image_id, [
- dict(replace='/name', value=new_image_name),
- dict(replace='/visibility', value=new_visibility)])
-
- self.assertEqual(200, resp.status)
+ _, body = self.client.update_image(image_id, [
+ dict(replace='/name', value=new_image_name)])
# Verifying updating
- resp, body = self.client.get_image(image_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_image(image_id)
self.assertEqual(image_id, body['id'])
self.assertEqual(new_image_name, body['name'])
- self.assertEqual(new_visibility, body['visibility'])
class ListImagesTest(base.BaseV2ImageTest):
@@ -161,12 +147,12 @@
"""
image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
name = data_utils.rand_name('image-')
- resp, body = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='public')
+ _, body = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private')
image_id = body['id']
- resp, body = cls.client.store_image(image_id, data=image_file)
+ cls.client.store_image(image_id, data=image_file)
return image_id
@@ -174,8 +160,7 @@
"""
Perform list action with given params and validates result.
"""
- resp, images_list = self.client.image_list(params=params)
- self.assertEqual(200, resp.status)
+ _, images_list = self.client.image_list(params=params)
# Validating params of fetched images
for image in images_list:
for key in params:
@@ -185,8 +170,7 @@
@test.attr(type='gate')
def test_index_no_params(self):
# Simple test to see all fixture images returned
- resp, images_list = self.client.image_list()
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list()
image_list = map(lambda x: x['id'], images_list)
for image in self.created_images:
@@ -206,8 +190,8 @@
@test.attr(type='gate')
def test_list_images_param_visibility(self):
- # Test to get all images with visibility = public
- params = {"visibility": "public"}
+ # Test to get all images with visibility = private
+ params = {"visibility": "private"}
self._list_by_param_value_and_assert(params)
@test.attr(type='gate')
@@ -215,8 +199,7 @@
# Test to get all images by size
image_id = self.created_images[1]
# Get image metadata
- resp, image = self.client.get_image(image_id)
- self.assertEqual(resp['status'], '200')
+ _, image = self.client.get_image(image_id)
params = {"size": image['size']}
self._list_by_param_value_and_assert(params)
@@ -226,13 +209,11 @@
# Test to get all images with size between 2000 to 3000
image_id = self.created_images[1]
# Get image metadata
- resp, image = self.client.get_image(image_id)
- self.assertEqual(resp['status'], '200')
+ _, image = self.client.get_image(image_id)
size = image['size']
params = {"size_min": size - 500, "size_max": size + 500}
- resp, images_list = self.client.image_list(params=params)
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list(params=params)
image_size_list = map(lambda x: x['size'], images_list)
for image_size in image_size_list:
@@ -250,8 +231,7 @@
def test_list_images_param_limit(self):
# Test to get images by limit
params = {"limit": 2}
- resp, images_list = self.client.image_list(params=params)
- self.assertEqual(resp['status'], '200')
+ _, images_list = self.client.image_list(params=params)
self.assertEqual(len(images_list), params['limit'],
"Failed to get images by limit")
@@ -260,14 +240,12 @@
def test_get_image_schema(self):
# Test to get image schema
schema = "image"
- resp, body = self.client.get_schema(schema)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_schema(schema)
self.assertEqual("image", body['name'])
@test.attr(type='gate')
def test_get_images_schema(self):
# Test to get images schema
schema = "images"
- resp, body = self.client.get_schema(schema)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_schema(schema)
self.assertEqual("images", body['name'])
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index f80c818..5aaf578 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -20,9 +20,8 @@
@test.attr(type='gate')
def test_image_share_accept(self):
image_id = self._create_image()
- resp, member = self.os_img_client.add_member(image_id,
- self.alt_tenant_id)
- self.assertEqual(200, resp.status)
+ _, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
@@ -31,8 +30,7 @@
self.alt_tenant_id,
'accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- resp, body = self.os_img_client.get_image_membership(image_id)
- self.assertEqual(200, resp.status)
+ _, body = self.os_img_client.get_image_membership(image_id)
members = body['members']
member = members[0]
self.assertEqual(len(members), 1, str(members))
@@ -43,17 +41,15 @@
@test.attr(type='gate')
def test_image_share_reject(self):
image_id = self._create_image()
- resp, member = self.os_img_client.add_member(image_id,
- self.alt_tenant_id)
- self.assertEqual(200, resp.status)
+ _, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
- resp, _ = self.alt_img_client.update_member_status(image_id,
- self.alt_tenant_id,
- 'rejected')
- self.assertEqual(200, resp.status)
+ self.alt_img_client.update_member_status(image_id,
+ self.alt_tenant_id,
+ 'rejected')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
@test.attr(type='gate')
@@ -66,9 +62,8 @@
'accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- resp, member = self.os_img_client.get_member(image_id,
- self.alt_tenant_id)
- self.assertEqual(200, resp.status)
+ _, member = self.os_img_client.get_member(image_id,
+ self.alt_tenant_id)
self.assertEqual(self.alt_tenant_id, member['member_id'])
self.assertEqual(image_id, member['image_id'])
self.assertEqual('accepted', member['status'])
@@ -83,18 +78,15 @@
'accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
- resp = self.os_img_client.remove_member(image_id, self.alt_tenant_id)
- self.assertEqual(204, resp.status)
+ self.os_img_client.remove_member(image_id, self.alt_tenant_id)
self.assertNotIn(image_id, self._list_image_ids_as_alt())
@test.attr(type='gate')
def test_get_image_member_schema(self):
- resp, body = self.os_img_client.get_schema("member")
- self.assertEqual(200, resp.status)
+ _, body = self.os_img_client.get_schema("member")
self.assertEqual("member", body['name'])
@test.attr(type='gate')
def test_get_image_members_schema(self):
- resp, body = self.os_img_client.get_schema("members")
- self.assertEqual(200, resp.status)
+ _, body = self.os_img_client.get_schema("members")
self.assertEqual("members", body['name'])
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index 98ef649..7da6e65 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -21,8 +21,8 @@
@test.attr(type=['negative', 'gate'])
def test_image_share_invalid_status(self):
image_id = self._create_image()
- resp, member = self.os_img_client.add_member(image_id,
- self.alt_tenant_id)
+ _, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
self.assertEqual(member['status'], 'pending')
self.assertRaises(exceptions.BadRequest,
self.alt_img_client.update_member_status,
@@ -31,8 +31,8 @@
@test.attr(type=['negative', 'gate'])
def test_image_share_owner_cannot_accept(self):
image_id = self._create_image()
- resp, member = self.os_img_client.add_member(image_id,
- self.alt_tenant_id)
+ _, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
self.assertRaises(exceptions.Unauthorized,
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index 27ba39c..722929e 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -52,11 +52,10 @@
def test_get_delete_deleted_image(self):
# get and delete the deleted image
# create and delete image
- resp, body = self.client.create_image(name='test',
- container_format='bare',
- disk_format='raw')
+ _, body = self.client.create_image(name='test',
+ container_format='bare',
+ disk_format='raw')
image_id = body['id']
- self.assertEqual(201, resp.status)
self.client.delete_image(image_id)
self.client.wait_for_resource_deletion(image_id)
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index 504c0e8..a9db24b 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -21,23 +21,19 @@
@test.attr(type='gate')
def test_update_delete_tags_for_image(self):
- resp, body = self.create_image(container_format='bare',
- disk_format='raw',
- visibility='public')
+ _, body = self.create_image(container_format='bare',
+ disk_format='raw',
+ visibility='private')
image_id = body['id']
tag = data_utils.rand_name('tag-')
self.addCleanup(self.client.delete_image, image_id)
# Creating image tag and verify it.
- resp, body = self.client.add_image_tag(image_id, tag)
- self.assertEqual(resp.status, 204)
- resp, body = self.client.get_image(image_id)
- self.assertEqual(resp.status, 200)
+ self.client.add_image_tag(image_id, tag)
+ _, body = self.client.get_image(image_id)
self.assertIn(tag, body['tags'])
# Deleting image tag and verify it.
- resp = self.client.delete_image_tag(image_id, tag)
- self.assertEqual(resp.status, 204)
- resp, body = self.client.get_image(image_id)
- self.assertEqual(resp.status, 200)
+ self.client.delete_image_tag(image_id, tag)
+ _, body = self.client.get_image(image_id)
self.assertNotIn(tag, body['tags'])
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index d8953d8..8e42b7c 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -33,10 +33,10 @@
@test.attr(type=['negative', 'gate'])
def test_delete_non_existing_tag(self):
# Delete non existing tag.
- resp, body = self.create_image(container_format='bare',
- disk_format='raw',
- visibility='public'
- )
+ _, body = self.create_image(container_format='bare',
+ disk_format='raw',
+ visibility='private'
+ )
image_id = body['id']
tag = data_utils.rand_name('non-exist-tag-')
self.addCleanup(self.client.delete_image, image_id)
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index a307986..d1a8faf 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -85,3 +85,50 @@
self.assertEqual('200', resp['status'])
for q in non_default_quotas['quotas']:
self.assertNotEqual(tenant_id, q['tenant_id'])
+
+ @test.requires_ext(extension='lbaas', service='network')
+ @test.attr(type='gate')
+ def test_lbaas_quotas(self):
+ # Add a tenant to conduct the test
+ test_tenant = data_utils.rand_name('test_tenant_')
+ test_description = data_utils.rand_name('desc_')
+ _, tenant = self.identity_admin_client.create_tenant(
+ name=test_tenant,
+ description=test_description)
+ tenant_id = tenant['id']
+ self.addCleanup(self.identity_admin_client.delete_tenant, tenant_id)
+ # Change lbaas quotas for tenant
+ new_quotas = {'vip': 1, 'pool': 2,
+ 'member': 3, 'health_monitor': 4}
+
+ resp, quota_set = self.admin_client.update_quotas(tenant_id,
+ **new_quotas)
+ self.assertEqual('200', resp['status'])
+ self.addCleanup(self.admin_client.reset_quotas, tenant_id)
+ self.assertEqual(1, quota_set['vip'])
+ self.assertEqual(2, quota_set['pool'])
+ self.assertEqual(3, quota_set['member'])
+ self.assertEqual(4, quota_set['health_monitor'])
+ # Confirm our tenant is listed among tenants with non default quotas
+ resp, non_default_quotas = self.admin_client.list_quotas()
+ self.assertEqual('200', resp['status'])
+ found = False
+ for qs in non_default_quotas['quotas']:
+ if qs['tenant_id'] == tenant_id:
+ found = True
+ self.assertTrue(found)
+ # Confirm from APi quotas were changed as requested for tenant
+ resp, quota_set = self.admin_client.show_quotas(tenant_id)
+ quota_set = quota_set['quota']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(1, quota_set['vip'])
+ self.assertEqual(2, quota_set['pool'])
+ self.assertEqual(3, quota_set['member'])
+ self.assertEqual(4, quota_set['health_monitor'])
+ # Reset quotas to default and confirm
+ resp, body = self.admin_client.reset_quotas(tenant_id)
+ self.assertEqual('204', resp['status'])
+ resp, non_default_quotas = self.admin_client.list_quotas()
+ self.assertEqual('200', resp['status'])
+ for q in non_default_quotas['quotas']:
+ self.assertNotEqual(tenant_id, q['tenant_id'])
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index dcd9bff..087b87a 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -81,9 +81,13 @@
cls.metering_label_rules = []
cls.fw_rules = []
cls.fw_policies = []
+ cls.ipsecpolicies = []
@classmethod
def tearDownClass(cls):
+ # Clean up ipsec policies
+ for ipsecpolicy in cls.ipsecpolicies:
+ cls.client.delete_ipsecpolicy(ipsecpolicy['id'])
# Clean up firewall policies
for fw_policy in cls.fw_policies:
cls.client.delete_firewall_policy(fw_policy['id'])
@@ -145,15 +149,17 @@
return network
@classmethod
- def create_subnet(cls, network, gateway=None):
+ def create_subnet(cls, network, gateway=None, cidr=None, mask_bits=None,
+ **kwargs):
"""Wrapper utility that returns a test subnet."""
# The cidr and mask_bits depend on the ip version.
if cls._ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ cidr = cidr or netaddr.IPNetwork(CONF.network.tenant_network_cidr)
+ mask_bits = mask_bits or CONF.network.tenant_network_mask_bits
elif cls._ip_version == 6:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
- mask_bits = CONF.network.tenant_network_v6_mask_bits
+ cidr = (
+ cidr or netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr))
+ mask_bits = mask_bits or CONF.network.tenant_network_v6_mask_bits
# Find a cidr that is not in use yet and create a subnet with it
for subnet_cidr in cidr.subnet(mask_bits):
if not gateway:
@@ -163,7 +169,8 @@
network_id=network['id'],
cidr=str(subnet_cidr),
ip_version=cls._ip_version,
- gateway_ip=gateway)
+ gateway_ip=gateway,
+ **kwargs)
break
except exceptions.BadRequest as e:
is_overlapping_cidr = 'overlaps with another subnet' in str(e)
@@ -342,6 +349,14 @@
router['id'], i['fixed_ips'][0]['subnet_id'])
cls.client.delete_router(router['id'])
+ @classmethod
+ def create_ipsecpolicy(cls, name):
+ """Wrapper utility that returns a test ipsec policy."""
+ _, body = cls.client.create_ipsecpolicy(name=name)
+ ipsecpolicy = body['ipsecpolicy']
+ cls.ipsecpolicies.append(ipsecpolicy)
+ return ipsecpolicy
+
class BaseAdminNetworkTest(BaseNetworkTest):
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
index b278002..1303bcf 100644
--- a/tempest/api/network/base_routers.py
+++ b/tempest/api/network/base_routers.py
@@ -37,6 +37,15 @@
routers_list.append(router['id'])
self.assertNotIn(router_id, routers_list)
+ def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
+ resp, interface = self.client.add_router_interface_with_subnet_id(
+ router_id, subnet_id)
+ self.assertEqual('200', resp['status'])
+ self.addCleanup(self._remove_router_interface_with_subnet_id,
+ router_id, subnet_id)
+ self.assertEqual(subnet_id, interface['subnet_id'])
+ return interface
+
def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
resp, body = self.client.remove_router_interface_with_subnet_id(
router_id, subnet_id)
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index e0e26da..c897716 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -70,6 +70,27 @@
self.assertTrue(port, msg)
self._confirm_allowed_address_pair(port[0], self.ip_address)
+ @test.attr(type='smoke')
+ def test_update_port_with_address_pair(self):
+ # Create a port without allowed address pair
+ resp, body = self.client.create_port(network_id=self.network['id'])
+ self.assertEqual('201', resp['status'])
+ port_id = body['port']['id']
+ self.addCleanup(self.client.delete_port, port_id)
+
+ # Confirm port is created
+ resp, body = self.client.show_port(port_id)
+ self.assertEqual('200', resp['status'])
+
+ # Update allowed address pair attribute of port
+ allowed_address_pairs = [{'ip_address': self.ip_address,
+ 'mac_address': self.mac_address}]
+ resp, body = self.client.update_port(port_id,
+ allowed_address_pairs=allowed_address_pairs)
+ self.assertEqual('200', resp['status'])
+ newport = body['port']
+ self._confirm_allowed_address_pair(newport, self.ip_address)
+
def _confirm_allowed_address_pair(self, port, ip):
msg = 'Port allowed address pairs should not be empty'
self.assertTrue(port['allowed_address_pairs'], msg)
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 673fc47..db24e0d 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -259,7 +259,7 @@
self.assertEqual('200', resp['status'])
member = body['member']
for key, value in member.iteritems():
- # 'status' should not be confirmed in api tests
+ # 'status' should not be confirmed in api tests
if key != 'status':
self.assertEqual(self.member[key], value)
@@ -340,7 +340,7 @@
self.assertEqual('200', resp['status'])
health_monitor = body['health_monitor']
for key, value in health_monitor.iteritems():
- # 'status' should not be confirmed in api tests
+ # 'status' should not be confirmed in api tests
if key != 'status':
self.assertEqual(self.health_monitor[key], value)
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index ac0fd11..ac3a072 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -14,6 +14,7 @@
# under the License.
import netaddr
+import testtools
from tempest.api.network import base
from tempest.common.utils import data_utils
@@ -276,13 +277,6 @@
block defined by tenant-network_cidr
"""
- @classmethod
- @test.safe_setup
- def setUpClass(cls):
- super(BulkNetworkOpsTestJSON, cls).setUpClass()
- cls.network1 = cls.create_network()
- cls.network2 = cls.create_network()
-
def _delete_networks(self, created_networks):
for n in created_networks:
resp, body = self.client.delete_network(n['id'])
@@ -331,11 +325,11 @@
@test.attr(type='smoke')
def test_bulk_create_delete_subnet(self):
+ networks = [self.create_network(), self.create_network()]
# Creates 2 subnets in one request
cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
mask_bits = CONF.network.tenant_network_mask_bits
cidrs = [subnet_cidr for subnet_cidr in cidr.subnet(mask_bits)]
- networks = [self.network1['id'], self.network2['id']]
names = [data_utils.rand_name('subnet-') for i in range(len(networks))]
subnets_list = []
# TODO(raies): "for IPv6, version list [4, 6] will be used.
@@ -343,7 +337,7 @@
ip_version = [4, 4]
for i in range(len(names)):
p1 = {
- 'network_id': networks[i],
+ 'network_id': networks[i]['id'],
'cidr': str(cidrs[(i)]),
'name': names[i],
'ip_version': ip_version[i]
@@ -363,14 +357,14 @@
@test.attr(type='smoke')
def test_bulk_create_delete_port(self):
+ networks = [self.create_network(), self.create_network()]
# Creates 2 ports in one request
- networks = [self.network1['id'], self.network2['id']]
names = [data_utils.rand_name('port-') for i in range(len(networks))]
port_list = []
state = [True, False]
for i in range(len(names)):
p1 = {
- 'network_id': networks[i],
+ 'network_id': networks[i]['id'],
'name': names[i],
'admin_state_up': state[i],
}
@@ -433,6 +427,29 @@
self.assertEqual('204', resp['status'])
self.subnets.pop()
+ @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+ "IPv6 extended attributes for subnets not "
+ "available")
+ @test.attr(type='smoke')
+ def test_create_delete_subnet_with_v6_attributes(self):
+ name = data_utils.rand_name('network-')
+ resp, body = self.client.create_network(name=name)
+ self.assertEqual('201', resp['status'])
+ network = body['network']
+ net_id = network['id']
+ subnet = self.create_subnet(network,
+ gateway='fe80::1',
+ ipv6_ra_mode='slaac',
+ ipv6_address_mode='slaac')
+ # Verifies Subnet GW in IPv6
+ self.assertEqual(subnet['gateway_ip'], 'fe80::1')
+ self.assertEqual(subnet['ipv6_ra_mode'], 'slaac')
+ self.assertEqual(subnet['ipv6_address_mode'], 'slaac')
+ # Delete network and subnet
+ resp, body = self.client.delete_network(net_id)
+ self.assertEqual('204', resp['status'])
+ self.subnets.pop()
+
class NetworksIpV6TestXML(NetworksIpV6TestJSON):
_interface = 'xml'
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 7605b8a..d38633f 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -300,9 +300,13 @@
@test.attr(type='smoke')
def test_add_multiple_router_interfaces(self):
- network = self.create_network()
- subnet01 = self.create_subnet(network)
- subnet02 = self.create_subnet(network)
+ network01 = self.create_network(
+ network_name=data_utils.rand_name('router-network01-'))
+ network02 = self.create_network(
+ network_name=data_utils.rand_name('router-network02-'))
+ subnet01 = self.create_subnet(network01)
+ sub02_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr).next()
+ subnet02 = self.create_subnet(network02, cidr=sub02_cidr)
router = self._create_router(data_utils.rand_name('router-'))
interface01 = self._add_router_interface_with_subnet_id(router['id'],
subnet01['id'])
@@ -313,15 +317,6 @@
self._verify_router_interface(router['id'], subnet02['id'],
interface02['port_id'])
- def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
- resp, interface = self.client.add_router_interface_with_subnet_id(
- router_id, subnet_id)
- self.assertEqual('200', resp['status'])
- self.addCleanup(self._remove_router_interface_with_subnet_id,
- router_id, subnet_id)
- self.assertEqual(subnet_id, interface['subnet_id'])
- return interface
-
def _verify_router_interface(self, router_id, subnet_id, port_id):
resp, show_port_body = self.client.show_port(port_id)
self.assertEqual('200', resp['status'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 91ab9d6..feee51b 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -13,11 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
+import netaddr
+
from tempest.api.network import base_routers as base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest import test
+CONF = config.CONF
+
class RoutersNegativeTest(base.BaseRouterTest):
_interface = 'json'
@@ -43,12 +48,30 @@
@test.attr(type=['negative', 'smoke'])
def test_router_add_gateway_net_not_external_returns_400(self):
- self.create_subnet(self.network)
+ alt_network = self.create_network(
+ network_name=data_utils.rand_name('router-negative-'))
+ sub_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr).next()
+ self.create_subnet(alt_network, cidr=sub_cidr)
self.assertRaises(exceptions.BadRequest,
self.client.update_router,
self.router['id'],
external_gateway_info={
- 'network_id': self.network['id']})
+ 'network_id': alt_network['id']})
+
+ @test.attr(type=['negative', 'smoke'])
+ def test_add_router_interfaces_on_overlapping_subnets_returns_400(self):
+ network01 = self.create_network(
+ network_name=data_utils.rand_name('router-network01-'))
+ network02 = self.create_network(
+ network_name=data_utils.rand_name('router-network02-'))
+ subnet01 = self.create_subnet(network01)
+ subnet02 = self.create_subnet(network02)
+ self._add_router_interface_with_subnet_id(self.router['id'],
+ subnet01['id'])
+ self.assertRaises(exceptions.BadRequest,
+ self._add_router_interface_with_subnet_id,
+ self.router['id'],
+ subnet02['id'])
@test.attr(type=['negative', 'smoke'])
def test_router_remove_interface_in_use_returns_409(self):
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 97b3c4b..b98cea1 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -86,9 +86,6 @@
direction='ingress'
)
self.assertEqual('201', resp['status'])
- self.addCleanup(self._delete_security_group_rule,
- rule_create_body['security_group_rule']['id']
- )
# Show details of the created security rule
resp, show_rule_body = self.client.show_security_group_rule(
@@ -130,9 +127,6 @@
self.assertEqual('201', resp['status'])
sec_group_rule = rule_create_body['security_group_rule']
- self.addCleanup(self._delete_security_group_rule,
- sec_group_rule['id']
- )
self.assertEqual(sec_group_rule['direction'], direction)
self.assertEqual(sec_group_rule['protocol'], protocol)
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index 0b86398..53c9d12 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -55,7 +55,7 @@
def test_create_security_group_rule_with_bad_protocol(self):
group_create_body, _ = self._create_security_group()
- #Create rule with bad protocol name
+ # Create rule with bad protocol name
pname = 'bad_protocol_name'
self.assertRaises(
exceptions.BadRequest, self.client.create_security_group_rule,
@@ -66,7 +66,7 @@
def test_create_security_group_rule_with_invalid_ports(self):
group_create_body, _ = self._create_security_group()
- #Create rule with invalid ports
+ # Create rule with invalid ports
states = [(-16, 80, 'Invalid value for port -16'),
(80, 79, 'port_range_min must be <= port_range_max'),
(80, 65536, 'Invalid value for port 65536'),
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index a49e944..0cc3f19 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -16,24 +16,21 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest import test
CONF = config.CONF
-class VPNaaSTestJSON(base.BaseNetworkTest):
+class VPNaaSTestJSON(base.BaseAdminNetworkTest):
_interface = 'json'
"""
Tests the following operations in the Neutron API using the REST client for
Neutron:
-
- List VPN Services
- Show VPN Services
- Create VPN Services
- Update VPN Services
- Delete VPN Services
+ List, Show, Create, Delete, and Update VPN Service
List, Show, Create, Delete, and Update IKE policy
+ List, Show, Create, Delete, and Update IPSec policy
"""
@classmethod
@@ -46,13 +43,16 @@
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
cls.router = cls.create_router(
- data_utils.rand_name("router-"),
+ data_utils.rand_name("router"),
external_network_id=CONF.network.public_network_id)
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
cls.vpnservice = cls.create_vpnservice(cls.subnet['id'],
cls.router['id'])
+
cls.ikepolicy = cls.create_ikepolicy(
data_utils.rand_name("ike-policy-"))
+ cls.ipsecpolicy = cls.create_ipsecpolicy(
+ data_utils.rand_name("ipsec-policy-"))
def _delete_ike_policy(self, ike_policy_id):
# Deletes a ike policy and verifies if it is deleted or not
@@ -70,6 +70,99 @@
ike_id_list.append(i['id'])
self.assertNotIn(ike_policy_id, ike_id_list)
+ def _delete_ipsec_policy(self, ipsec_policy_id):
+ # Deletes an ike policy if it exists
+ try:
+ self.client.delete_ipsecpolicy(ipsec_policy_id)
+
+ except exceptions.NotFound:
+ pass
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in expected.iteritems():
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
+
+ def _delete_vpn_service(self, vpn_service_id):
+ resp, _ = self.client.delete_vpnservice(vpn_service_id)
+ self.assertEqual('204', resp['status'])
+ # Asserting if vpn service is found in the list after deletion
+ _, body = self.client.list_vpnservices()
+ vpn_services = [vs['id'] for vs in body['vpnservices']]
+ self.assertNotIn(vpn_service_id, vpn_services)
+
+ def _get_tenant_id(self):
+ """
+ Returns the tenant_id of the client current user
+ """
+ # TODO(jroovers) This is a temporary workaround to get the tenant_id
+ # of the the current client. Replace this once tenant_isolation for
+ # neutron is fixed.
+ _, body = self.client.show_network(self.network['id'])
+ return body['network']['tenant_id']
+
+ @test.attr(type='smoke')
+ def test_admin_create_ipsec_policy_for_tenant(self):
+ tenant_id = self._get_tenant_id()
+ # Create IPSec policy for the newly created tenant
+ name = data_utils.rand_name('ipsec-policy')
+ resp, body = (self.admin_client.
+ create_ipsecpolicy(name=name, tenant_id=tenant_id))
+ self.assertEqual('201', resp['status'])
+ ipsecpolicy = body['ipsecpolicy']
+ self.assertIsNotNone(ipsecpolicy['id'])
+ self.addCleanup(self.admin_client.delete_ipsecpolicy,
+ ipsecpolicy['id'])
+
+ # Assert that created ipsec policy is found in API list call
+ _, body = self.client.list_ipsecpolicies()
+ ipsecpolicies = [policy['id'] for policy in body['ipsecpolicies']]
+ self.assertIn(ipsecpolicy['id'], ipsecpolicies)
+
+ @test.attr(type='smoke')
+ def test_admin_create_vpn_service_for_tenant(self):
+ tenant_id = self._get_tenant_id()
+
+ # Create vpn service for the newly created tenant
+ name = data_utils.rand_name('vpn-service')
+ resp, body = self.admin_client.create_vpnservice(
+ subnet_id=self.subnet['id'],
+ router_id=self.router['id'],
+ name=name,
+ admin_state_up=True,
+ tenant_id=tenant_id)
+ self.assertEqual('201', resp['status'])
+ vpnservice = body['vpnservice']
+ self.assertIsNotNone(vpnservice['id'])
+ self.addCleanup(self.admin_client.delete_vpnservice, vpnservice['id'])
+
+ # Assert that created vpnservice is found in API list call
+ _, body = self.client.list_vpnservices()
+ vpn_services = [vs['id'] for vs in body['vpnservices']]
+ self.assertIn(vpnservice['id'], vpn_services)
+
+ @test.attr(type='smoke')
+ def test_admin_create_ike_policy_for_tenant(self):
+ tenant_id = self._get_tenant_id()
+
+ # Create IKE policy for the newly created tenant
+ name = data_utils.rand_name('ike-policy')
+ resp, body = (self.admin_client.
+ create_ikepolicy(name=name, ike_version="v1",
+ encryption_algorithm="aes-128",
+ auth_algorithm="sha1",
+ tenant_id=tenant_id))
+ self.assertEqual('201', resp['status'])
+ ikepolicy = body['ikepolicy']
+ self.assertIsNotNone(ikepolicy['id'])
+ self.addCleanup(self.admin_client.delete_ikepolicy, ikepolicy['id'])
+
+ # Assert that created ike policy is found in API list call
+ _, body = self.client.list_ikepolicies()
+ ikepolicies = [ikp['id'] for ikp in body['ikepolicies']]
+ self.assertIn(ikepolicy['id'], ikepolicies)
+
@test.attr(type='smoke')
def test_list_vpn_services(self):
# Verify the VPN service exists in the list of all VPN services
@@ -80,14 +173,15 @@
@test.attr(type='smoke')
def test_create_update_delete_vpn_service(self):
- # Creates a VPN service
- name = data_utils.rand_name('vpn-service-')
+ # Creates a VPN service and sets up deletion
+ name = data_utils.rand_name('vpn-service')
resp, body = self.client.create_vpnservice(subnet_id=self.subnet['id'],
router_id=self.router['id'],
name=name,
admin_state_up=True)
self.assertEqual('201', resp['status'])
vpnservice = body['vpnservice']
+ self.addCleanup(self._delete_vpn_service, vpnservice['id'])
# Assert if created vpnservices are not found in vpnservices list
resp, body = self.client.list_vpnservices()
vpn_services = [vs['id'] for vs in body['vpnservices']]
@@ -99,14 +193,6 @@
# But precondition is that current state of vpnservice
# should be "ACTIVE" not "PENDING*"
- # Verification of vpn service delete
- resp, body = self.client.delete_vpnservice(vpnservice['id'])
- self.assertEqual('204', resp['status'])
- # Asserting if vpn service is found in the list after deletion
- resp, body = self.client.list_vpnservices()
- vpn_services = [vs['id'] for vs in body['vpnservices']]
- self.assertNotIn(vpnservice['id'], vpn_services)
-
@test.attr(type='smoke')
def test_show_vpn_service(self):
# Verifies the details of a vpn service
@@ -120,6 +206,9 @@
self.assertEqual(self.vpnservice['router_id'], vpnservice['router_id'])
self.assertEqual(self.vpnservice['subnet_id'], vpnservice['subnet_id'])
self.assertEqual(self.vpnservice['tenant_id'], vpnservice['tenant_id'])
+ valid_status = ["ACTIVE", "DOWN", "BUILD", "ERROR", "PENDING_CREATE",
+ "PENDING_UPDATE", "PENDING_DELETE"]
+ self.assertIn(vpnservice['status'], valid_status)
@test.attr(type='smoke')
def test_list_ike_policies(self):
@@ -132,7 +221,7 @@
@test.attr(type='smoke')
def test_create_update_delete_ike_policy(self):
# Creates a IKE policy
- name = data_utils.rand_name('ike-policy-')
+ name = data_utils.rand_name('ike-policy')
resp, body = (self.client.create_ikepolicy(
name=name,
ike_version="v1",
@@ -140,19 +229,31 @@
auth_algorithm="sha1"))
self.assertEqual('201', resp['status'])
ikepolicy = body['ikepolicy']
+ self.assertIsNotNone(ikepolicy['id'])
self.addCleanup(self._delete_ike_policy, ikepolicy['id'])
- # Verification of ike policy update
- description = "Updated ike policy"
- new_ike = {'description': description, 'pfs': 'group5',
- 'name': data_utils.rand_name("New-IKE-")}
- resp, body = self.client.update_ikepolicy(ikepolicy['id'],
- **new_ike)
+
+ # Update IKE Policy
+ new_ike = {'name': data_utils.rand_name("New-IKE"),
+ 'description': "Updated ike policy",
+ 'encryption_algorithm': "aes-256",
+ 'ike_version': "v2",
+ 'pfs': "group14",
+ 'lifetime': {'units': "seconds", 'value': 2000}}
+ resp, _ = self.client.update_ikepolicy(ikepolicy['id'], **new_ike)
self.assertEqual('200', resp['status'])
- updated_ike_policy = body['ikepolicy']
- self.assertEqual(updated_ike_policy['description'], description)
+ # Confirm that update was successful by verifying using 'show'
+ _, body = self.client.show_ikepolicy(ikepolicy['id'])
+ ike_policy = body['ikepolicy']
+ for key, value in new_ike.iteritems():
+ self.assertIn(key, ike_policy)
+ self.assertEqual(value, ike_policy[key])
+
# Verification of ike policy delete
- resp, body = self.client.delete_ikepolicy(ikepolicy['id'])
+ resp, _ = self.client.delete_ikepolicy(ikepolicy['id'])
self.assertEqual('204', resp['status'])
+ _, body = self.client.list_ikepolicies()
+ ikepolicies = [ikp['id'] for ikp in body['ikepolicies']]
+ self.assertNotIn(ike_policy['id'], ikepolicies)
@test.attr(type='smoke')
def test_show_ike_policy(self):
@@ -177,6 +278,51 @@
self.assertEqual(self.ikepolicy['ike_version'],
ikepolicy['ike_version'])
+ @test.attr(type='smoke')
+ def test_list_ipsec_policies(self):
+ # Verify the ipsec policy exists in the list of all ipsec policies
+ resp, body = self.client.list_ipsecpolicies()
+ self.assertEqual('200', resp['status'])
+ ipsecpolicies = body['ipsecpolicies']
+ self.assertIn(self.ipsecpolicy['id'], [i['id'] for i in ipsecpolicies])
+
+ @test.attr(type='smoke')
+ def test_create_update_delete_ipsec_policy(self):
+ # Creates an ipsec policy
+ ipsec_policy_body = {'name': data_utils.rand_name('ipsec-policy'),
+ 'pfs': 'group5',
+ 'encryption_algorithm': "aes-128",
+ 'auth_algorithm': 'sha1'}
+ resp, resp_body = self.client.create_ipsecpolicy(**ipsec_policy_body)
+ self.assertEqual('201', resp['status'])
+ ipsecpolicy = resp_body['ipsecpolicy']
+ self.addCleanup(self._delete_ipsec_policy, ipsecpolicy['id'])
+ self._assertExpected(ipsec_policy_body, ipsecpolicy)
+ # Verification of ipsec policy update
+ new_ipsec = {'description': 'Updated ipsec policy',
+ 'pfs': 'group2',
+ 'name': data_utils.rand_name("New-IPSec"),
+ 'encryption_algorithm': "aes-256",
+ 'lifetime': {'units': "seconds", 'value': '2000'}}
+ resp, body = self.client.update_ipsecpolicy(ipsecpolicy['id'],
+ **new_ipsec)
+ self.assertEqual('200', resp['status'])
+ updated_ipsec_policy = body['ipsecpolicy']
+ self._assertExpected(new_ipsec, updated_ipsec_policy)
+ # Verification of ipsec policy delete
+ resp, _ = self.client.delete_ipsecpolicy(ipsecpolicy['id'])
+ self.assertEqual('204', resp['status'])
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_ipsecpolicy, ipsecpolicy['id'])
+
+ @test.attr(type='smoke')
+ def test_show_ipsec_policy(self):
+ # Verifies the details of an ipsec policy
+ resp, body = self.client.show_ipsecpolicy(self.ipsecpolicy['id'])
+ self.assertEqual('200', resp['status'])
+ ipsecpolicy = body['ipsecpolicy']
+ self._assertExpected(self.ipsecpolicy, ipsecpolicy)
+
class VPNaaSTestXML(VPNaaSTestJSON):
_interface = 'xml'
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 6b18182..ccc0067 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -44,9 +44,6 @@
cls.isolated_creds.get_admin_creds())
# Get isolated creds for alt user
cls.os_alt = clients.Manager(cls.isolated_creds.get_alt_creds())
- # Add isolated users to operator role so that they can create a
- # container in swift.
- cls._assign_member_role()
else:
cls.os = clients.Manager()
cls.os_admin = clients.AdminManager()
@@ -80,22 +77,6 @@
super(BaseObjectTest, cls).tearDownClass()
@classmethod
- def _assign_member_role(cls):
- primary_creds = cls.isolated_creds.get_primary_creds()
- alt_creds = cls.isolated_creds.get_alt_creds()
- swift_role = CONF.object_storage.operator_role
- try:
- resp, roles = cls.os_admin.identity_client.list_roles()
- role = next(r for r in roles if r['name'] == swift_role)
- except StopIteration:
- msg = "No role named %s found" % swift_role
- raise exceptions.NotFound(msg)
- for creds in [primary_creds, alt_creds]:
- cls.os_admin.identity_client.assign_user_role(creds.tenant_id,
- creds.user_id,
- role['id'])
-
- @classmethod
def delete_containers(cls, containers, container_client=None,
object_client=None):
"""Remove given containers and all objects in them.
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 6bda83b..5f46d01 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -67,6 +67,7 @@
super(ContainerSyncTest, cls).tearDownClass()
@test.attr(type='slow')
+ @test.skip_because(bug='1317133')
def test_container_synchronization(self):
# container to container synchronization
# to allow/accept sync requests to/from other accounts
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 53ca20d..73b4f3b 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -54,14 +54,18 @@
self.assertEqual(resp['status'], '200')
self.assertHeaders(resp, 'Object', 'HEAD')
self.assertIn('x-delete-at', resp)
+ # we want to ensure that we will sleep long enough for things to
+ # actually expire, so figure out how many secs in the future that is.
+ sleepy_time = int(resp['x-delete-at']) - int(time.time())
+
resp, body = self.object_client.get_object(self.container_name,
self.object_name)
self.assertEqual(resp['status'], '200')
self.assertHeaders(resp, 'Object', 'GET')
self.assertIn('x-delete-at', resp)
- # sleep for over 5 seconds, so that object expires
- time.sleep(5)
+ # add a couple of seconds for safety.
+ time.sleep(sleepy_time + 3)
# object should not be there anymore
self.assertRaises(exceptions.NotFound, self.object_client.get_object,
@@ -69,10 +73,12 @@
@test.attr(type='gate')
def test_get_object_after_expiry_time(self):
- metadata = {'X-Delete-After': '3'}
+ # the 10s is important, because the get calls can take 3s each
+ # some times
+ metadata = {'X-Delete-After': '10'}
self._test_object_expiry(metadata)
@test.attr(type='gate')
def test_get_object_at_expiry_time(self):
- metadata = {'X-Delete-At': str(int(time.time()) + 3)}
+ metadata = {'X-Delete-At': str(int(time.time()) + 10)}
self._test_object_expiry(metadata)
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 06e63a4..1ef9aa1 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import cStringIO as StringIO
import hashlib
import random
import re
from six import moves
import time
+import zlib
from tempest.api.object_storage import base
from tempest.common import custom_matchers
@@ -61,7 +63,7 @@
return object_name, data_segments
- @test.attr(type='smoke')
+ @test.attr(type='gate')
def test_create_object(self):
# create object
object_name = data_utils.rand_name(name='TestObject')
@@ -76,7 +78,242 @@
self.assertEqual(resp['status'], '201')
self.assertHeaders(resp, 'Object', 'PUT')
- @test.attr(type='smoke')
+ # check uploaded content
+ _, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_content_disposition(self):
+ # create object with content_disposition
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata = {}
+ metadata['content-disposition'] = 'inline'
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=None)
+ self.assertIn('content-disposition', resp)
+ self.assertEqual(resp['content-disposition'], 'inline')
+ self.assertEqual(body, data)
+
+ @test.attr(type='gate')
+ def test_create_object_with_content_encoding(self):
+ # create object with content_encoding
+ object_name = data_utils.rand_name(name='TestObject')
+
+ # put compressed string
+ data_before = 'x' * 2000
+ data = zlib.compress(data_before)
+ metadata = {}
+ metadata['content-encoding'] = 'deflate'
+
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ # download compressed object
+ metadata = {}
+ metadata['accept-encoding'] = 'deflate'
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=metadata)
+ self.assertEqual(body, data_before)
+
+ @test.attr(type='gate')
+ def test_create_object_with_etag(self):
+ # create object with etag
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ md5 = hashlib.md5(data).hexdigest()
+ metadata = {'Etag': md5}
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ # check uploaded content
+ _, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_expect_continue(self):
+ # create object with expect_continue
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata = {'Expect': '100-continue'}
+ resp = self.custom_object_client.create_object_continue(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+
+ self.assertIn('status', resp)
+ self.assertEqual(resp['status'], '100')
+
+ self.custom_object_client.create_object_continue(
+ self.container_name,
+ object_name,
+ data,
+ metadata=None)
+
+ # check uploaded content
+ _, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_transfer_encoding(self):
+ # create object with transfer_encoding
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string(1024)
+ status, _, resp_headers = self.object_client.put_object_with_chunk(
+ container=self.container_name,
+ name=object_name,
+ contents=StringIO.StringIO(data),
+ chunk_size=512)
+ self.assertEqual(status, 201)
+ self.assertHeaders(resp_headers, 'Object', 'PUT')
+
+ # check uploaded content
+ _, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_x_fresh_metadata(self):
+ # create object with x_fresh_metadata
+ object_name_base = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata_1 = {'X-Object-Meta-test-meta': 'Meta'}
+ self.object_client.create_object(self.container_name,
+ object_name_base,
+ data,
+ metadata=metadata_1)
+ object_name = data_utils.rand_name(name='TestObject')
+ metadata_2 = {'X-Copy-From': '%s/%s' % (self.container_name,
+ object_name_base),
+ 'X-Fresh-Metadata': 'true'}
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ '',
+ metadata=metadata_2)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ resp, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertNotIn('x-object-meta-test-meta', resp)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_x_object_meta(self):
+ # create object with object_meta
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata = {'X-Object-Meta-test-meta': 'Meta'}
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ resp, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertIn('x-object-meta-test-meta', resp)
+ self.assertEqual(resp['x-object-meta-test-meta'], 'Meta')
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_x_object_metakey(self):
+ # create object with the blank value of metadata
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata = {'X-Object-Meta-test-meta': ''}
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ resp, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertIn('x-object-meta-test-meta', resp)
+ self.assertEqual(resp['x-object-meta-test-meta'], '')
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_x_remove_object_meta(self):
+ # create object with x_remove_object_meta
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata_add = {'X-Object-Meta-test-meta': 'Meta'}
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=metadata_add)
+ metadata_remove = {'X-Remove-Object-Meta-test-meta': 'Meta'}
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata_remove)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ resp, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertNotIn('x-object-meta-test-meta', resp)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
+ def test_create_object_with_x_remove_object_metakey(self):
+ # create object with the blank value of remove metadata
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata_add = {'X-Object-Meta-test-meta': 'Meta'}
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=metadata_add)
+ metadata_remove = {'X-Remove-Object-Meta-test-meta': ''}
+ resp, _ = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data,
+ metadata=metadata_remove)
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
+ resp, body = self.object_client.get_object(self.container_name,
+ object_name)
+ self.assertNotIn('x-object-meta-test-meta', resp)
+ self.assertEqual(data, body)
+
+ @test.attr(type='gate')
def test_delete_object(self):
# create object
object_name = data_utils.rand_name(name='TestObject')
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
index 3e03a30..ffff580 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
@@ -6,6 +6,7 @@
properties:
size: 1
description: a descriptive description
+ name: volume_name
outputs:
status:
@@ -20,5 +21,8 @@
description: display_description
value: { get_attr: ['volume', 'display_description'] }
+ display_name:
+ value: { get_attr: ['volume', 'display_name'] }
+
volume_id:
value: { get_resource: volume }
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
index 08e3da4..b660c19 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
@@ -7,6 +7,7 @@
properties:
size: 1
description: a descriptive description
+ name: volume_name
outputs:
status:
@@ -21,5 +22,8 @@
description: display_description
value: { get_attr: ['volume', 'display_description'] }
+ display_name:
+ value: { get_attr: ['volume', 'display_name'] }
+
volume_id:
value: { get_resource: volume }
diff --git a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
index 63b03f4..878ff68 100644
--- a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
@@ -8,10 +8,12 @@
type: string
ImageId:
type: string
- ExternalRouterId:
+ SubNetCidr:
type: string
ExternalNetworkId:
type: string
+ DNSServers:
+ type: comma_delimited_list
timeout:
type: number
resources:
@@ -25,21 +27,19 @@
network_id: {Ref: Network}
name: NewSubnet
ip_version: 4
- cidr: 10.0.3.0/24
- dns_nameservers: ["8.8.8.8"]
- allocation_pools:
- - {end: 10.0.3.150, start: 10.0.3.20}
+ cidr: { get_param: SubNetCidr }
+ dns_nameservers: { get_param: DNSServers }
Router:
type: OS::Neutron::Router
properties:
name: NewRouter
- admin_state_up: false
+ admin_state_up: true
external_gateway_info:
network: {get_param: ExternalNetworkId}
RouterInterface:
type: OS::Neutron::RouterInterface
properties:
- router_id: {get_param: ExternalRouterId}
+ router_id: {get_resource: Router}
subnet_id: {get_resource: Subnet}
Server:
type: OS::Nova::Server
@@ -56,8 +56,8 @@
template: |
#!/bin/bash -v
- /opt/aws/bin/cfn-signal -e 0 -r "SmokeServerNeutron created" \
- 'wait_handle'
+ while ! /opt/aws/bin/cfn-signal -e 0 -r "SmokeServerNeutron created" \
+ 'wait_handle' ; do sleep 3; done
params:
wait_handle: {get_resource: WaitHandleNeutron}
WaitHandleNeutron:
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 3086d78..e92b945 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -12,6 +12,7 @@
import logging
+import netaddr
from tempest.api.orchestration import base
from tempest import clients
@@ -41,9 +42,12 @@
template = cls.load_template('neutron_basic')
cls.keypair_name = (CONF.orchestration.keypair_name or
cls._create_keypair()['name'])
- cls.external_router_id = cls._get_external_router_id()
cls.external_network_id = CONF.network.public_network_id
+ tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
+ mask_bits = CONF.network.tenant_network_mask_bits
+ cls.subnet_cidr = tenant_cidr.subnet(mask_bits).next()
+
# create the stack
cls.stack_identifier = cls.create_stack(
cls.stack_name,
@@ -52,9 +56,10 @@
'KeyName': cls.keypair_name,
'InstanceType': CONF.orchestration.instance_type,
'ImageId': CONF.orchestration.image_ref,
- 'ExternalRouterId': cls.external_router_id,
'ExternalNetworkId': cls.external_network_id,
- 'timeout': CONF.orchestration.build_timeout
+ 'timeout': CONF.orchestration.build_timeout,
+ 'DNSServers': CONF.network.dns_servers,
+ 'SubNetCidr': str(cls.subnet_cidr)
})
cls.stack_id = cls.stack_identifier.split('/')[1]
try:
@@ -77,14 +82,6 @@
for resource in resources:
cls.test_resources[resource['logical_resource_id']] = resource
- @classmethod
- def _get_external_router_id(cls):
- resp, body = cls.network_client.list_ports()
- ports = body['ports']
- router_ports = filter(lambda port: port['device_owner'] ==
- 'network:router_interface', ports)
- return router_ports[0]['device_id']
-
@test.attr(type='slow')
def test_created_resources(self):
"""Verifies created neutron resources."""
@@ -121,11 +118,10 @@
self.assertEqual(subnet_id, subnet['id'])
self.assertEqual(network_id, subnet['network_id'])
self.assertEqual('NewSubnet', subnet['name'])
- self.assertEqual('8.8.8.8', subnet['dns_nameservers'][0])
- self.assertEqual('10.0.3.20', subnet['allocation_pools'][0]['start'])
- self.assertEqual('10.0.3.150', subnet['allocation_pools'][0]['end'])
+ self.assertEqual(sorted(CONF.network.dns_servers),
+ sorted(subnet['dns_nameservers']))
self.assertEqual(4, subnet['ip_version'])
- self.assertEqual('10.0.3.0/24', subnet['cidr'])
+ self.assertEqual(str(self.subnet_cidr), subnet['cidr'])
@test.attr(type='slow')
def test_created_router(self):
@@ -137,18 +133,19 @@
self.assertEqual('NewRouter', router['name'])
self.assertEqual(self.external_network_id,
router['external_gateway_info']['network_id'])
- self.assertEqual(False, router['admin_state_up'])
+ self.assertEqual(True, router['admin_state_up'])
@test.attr(type='slow')
def test_created_router_interface(self):
"""Verifies created router interface."""
+ router_id = self.test_resources.get('Router')['physical_resource_id']
network_id = self.test_resources.get('Network')['physical_resource_id']
subnet_id = self.test_resources.get('Subnet')['physical_resource_id']
resp, body = self.network_client.list_ports()
self.assertEqual('200', resp['status'])
ports = body['ports']
router_ports = filter(lambda port: port['device_id'] ==
- self.external_router_id, ports)
+ router_id, ports)
created_network_ports = filter(lambda port: port['network_id'] ==
network_id, router_ports)
self.assertEqual(1, len(created_network_ports))
@@ -158,7 +155,8 @@
subnet_id, fixed_ips)
self.assertEqual(1, len(subnet_fixed_ips))
router_interface_ip = subnet_fixed_ips[0]['ip_address']
- self.assertEqual('10.0.3.1', router_interface_ip)
+ self.assertEqual(str(self.subnet_cidr.iter_hosts().next()),
+ router_interface_ip)
@test.attr(type='slow')
def test_created_server(self):
@@ -170,8 +168,4 @@
self.assertEqual('ACTIVE', server['status'])
network = server['addresses']['NewNetwork'][0]
self.assertEqual(4, network['version'])
- ip_addr_prefix = network['addr'][:7]
- ip_addr_suffix = int(network['addr'].split('.')[3])
- self.assertEqual('10.0.3.', ip_addr_prefix)
- self.assertTrue(ip_addr_suffix >= 20)
- self.assertTrue(ip_addr_suffix <= 150)
+ self.assertIn(netaddr.IPAddress(network['addr']), self.subnet_cidr)
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
index cb70d07..a81a540 100644
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -68,13 +68,13 @@
output_map = {}
for outputs in stack['outputs']:
output_map[outputs['output_key']] = outputs['output_value']
- #Test that first key generated public and private keys
+ # Test that first key generated public and private keys
self.assertTrue('KeyPair_PublicKey' in output_map)
self.assertTrue("Generated" in output_map['KeyPair_PublicKey'])
self.assertTrue('KeyPair_PrivateKey' in output_map)
self.assertTrue('-----BEGIN' in output_map['KeyPair_PrivateKey'])
- #Test that second key generated public key, and private key is not
- #in the output due to save_private_key = false
+ # Test that second key generated public key, and private key is not
+ # in the output due to save_private_key = false
self.assertTrue('KeyPairDontSavePrivate_PublicKey' in output_map)
self.assertTrue('Generated' in
output_map['KeyPairDontSavePrivate_PublicKey'])
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index 2544c41..5ac2a8d 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -39,6 +39,8 @@
self.assertEqual(1, volume.get('size'))
self.assertEqual('a descriptive description',
volume.get('display_description'))
+ self.assertEqual('volume_name',
+ volume.get('display_name'))
def _outputs_verify(self, stack_identifier):
self.assertEqual('available',
@@ -48,6 +50,9 @@
self.assertEqual('a descriptive description',
self.get_stack_output(stack_identifier,
'display_description'))
+ self.assertEqual('volume_name',
+ self.get_stack_output(stack_identifier,
+ 'display_name'))
@test.attr(type='gate')
def test_cinder_volume_create_delete(self):
diff --git a/tempest/api/queuing/base.py b/tempest/api/queuing/base.py
index 5649619..f4ff7f1 100644
--- a/tempest/api/queuing/base.py
+++ b/tempest/api/queuing/base.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
from tempest import test
@@ -89,3 +90,80 @@
"""Wrapper utility that sets the metadata of a queue."""
resp, body = cls.client.set_queue_metadata(queue_name, rbody)
return resp, body
+
+ @classmethod
+ def post_messages(cls, queue_name, rbody):
+ '''Wrapper utility that posts messages to a queue.'''
+ resp, body = cls.client.post_messages(queue_name, rbody)
+
+ return resp, body
+
+ @classmethod
+ def list_messages(cls, queue_name):
+ '''Wrapper utility that lists the messages in a queue.'''
+ resp, body = cls.client.list_messages(queue_name)
+
+ return resp, body
+
+ @classmethod
+ def get_single_message(cls, message_uri):
+ '''Wrapper utility that gets a single message.'''
+ resp, body = cls.client.get_single_message(message_uri)
+
+ return resp, body
+
+ @classmethod
+ def get_multiple_messages(cls, message_uri):
+ '''Wrapper utility that gets multiple messages.'''
+ resp, body = cls.client.get_multiple_messages(message_uri)
+
+ return resp, body
+
+ @classmethod
+ def delete_messages(cls, message_uri):
+ '''Wrapper utility that deletes messages.'''
+ resp, body = cls.client.delete_messages(message_uri)
+
+ return resp, body
+
+ @classmethod
+ def post_claims(cls, queue_name, rbody, url_params=False):
+ '''Wrapper utility that claims messages.'''
+ resp, body = cls.client.post_claims(
+ queue_name, rbody, url_params=False)
+
+ return resp, body
+
+ @classmethod
+ def query_claim(cls, claim_uri):
+ '''Wrapper utility that gets a claim.'''
+ resp, body = cls.client.query_claim(claim_uri)
+
+ return resp, body
+
+ @classmethod
+ def update_claim(cls, claim_uri, rbody):
+ '''Wrapper utility that updates a claim.'''
+ resp, body = cls.client.update_claim(claim_uri, rbody)
+
+ return resp, body
+
+ @classmethod
+ def release_claim(cls, claim_uri):
+ '''Wrapper utility that deletes a claim.'''
+ resp, body = cls.client.release_claim(claim_uri)
+
+ return resp, body
+
+ @classmethod
+ def generate_message_body(cls, repeat=1):
+ '''Wrapper utility that sets the metadata of a queue.'''
+ message_ttl = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_message_ttl)
+
+ key = data_utils.arbitrary_string(size=20, base_text='QueuingKey')
+ value = data_utils.arbitrary_string(size=20, base_text='QueuingValue')
+ message_body = {key: value}
+
+ rbody = ([{'body': message_body, 'ttl': message_ttl}] * repeat)
+ return rbody
diff --git a/tempest/api/queuing/test_claims.py b/tempest/api/queuing/test_claims.py
new file mode 100644
index 0000000..a306623
--- /dev/null
+++ b/tempest/api/queuing/test_claims.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# 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 urlparse
+
+from tempest.api.queuing import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class TestClaims(base.BaseQueuingTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestClaims, cls).setUpClass()
+ cls.queue_name = data_utils.rand_name('Queues-Test')
+ # Create Queue
+ cls.create_queue(cls.queue_name)
+
+ def _post_and_claim_messages(self, queue_name, repeat=1):
+ # Post Messages
+ message_body = self.generate_message_body(repeat=repeat)
+ self.client.post_messages(queue_name=self.queue_name,
+ rbody=message_body)
+
+ # Post Claim
+ claim_ttl = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_claim_ttl)
+ claim_grace = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_claim_grace)
+ claim_body = {"ttl": claim_ttl, "grace": claim_grace}
+ resp, body = self.client.post_claims(queue_name=self.queue_name,
+ rbody=claim_body)
+
+ return resp, body
+
+ @test.attr(type='smoke')
+ def test_post_claim(self):
+ _, body = self._post_and_claim_messages(queue_name=self.queue_name)
+ claimed_message_uri = body[0]['href']
+
+ # Skipping this step till bug-1331517 is fixed
+ # Get posted claim
+ # self.client.query_claim(claimed_message_uri)
+
+ # Delete Claimed message
+ self.client.delete_messages(claimed_message_uri)
+
+ @test.skip_because(bug="1331517")
+ @test.attr(type='smoke')
+ def test_query_claim(self):
+ # Post a Claim
+ resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
+
+ # Query Claim
+ claim_uri = resp['location']
+ self.client.query_claim(claim_uri)
+
+ # Delete Claimed message
+ claimed_message_uri = body[0]['href']
+ self.delete_messages(claimed_message_uri)
+
+ @test.skip_because(bug="1328111")
+ @test.attr(type='smoke')
+ def test_update_claim(self):
+ # Post a Claim
+ resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
+
+ claim_uri = resp['location']
+ claimed_message_uri = body[0]['href']
+
+ # Update Claim
+ claim_ttl = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_claim_ttl)
+ update_rbody = {"ttl": claim_ttl}
+
+ self.client.update_claim(claim_uri, rbody=update_rbody)
+
+ # Verify claim ttl >= updated ttl value
+ _, body = self.client.query_claim(claim_uri)
+ updated_claim_ttl = body["ttl"]
+ self.assertTrue(updated_claim_ttl >= claim_ttl)
+
+ # Delete Claimed message
+ self.client.delete_messages(claimed_message_uri)
+
+ @test.attr(type='smoke')
+ def test_release_claim(self):
+ # Post a Claim
+ resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
+ claim_uri = resp['location']
+
+ # Release Claim
+ self.client.release_claim(claim_uri)
+
+ # Delete Claimed message
+ # This will implicitly verify that the claim is deleted.
+ message_uri = urlparse.urlparse(claim_uri).path
+ self.client.delete_messages(message_uri)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.delete_queue(cls.queue_name)
+ super(TestClaims, cls).tearDownClass()
diff --git a/tempest/api/queuing/test_messages.py b/tempest/api/queuing/test_messages.py
new file mode 100644
index 0000000..9546c91
--- /dev/null
+++ b/tempest/api/queuing/test_messages.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# 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
+
+from tempest.api.queuing import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class TestMessages(base.BaseQueuingTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestMessages, cls).setUpClass()
+ cls.queue_name = data_utils.rand_name('Queues-Test')
+ # Create Queue
+ cls.client.create_queue(cls.queue_name)
+
+ def _post_messages(self, repeat=CONF.queuing.max_messages_per_page):
+ message_body = self.generate_message_body(repeat=repeat)
+ resp, body = self.post_messages(queue_name=self.queue_name,
+ rbody=message_body)
+ return resp, body
+
+ @test.attr(type='smoke')
+ def test_post_messages(self):
+ # Post Messages
+ resp, _ = self._post_messages()
+
+ # Get on the posted messages
+ message_uri = resp['location']
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_list_messages(self):
+ # Post Messages
+ self._post_messages()
+
+ # List Messages
+ resp, _ = self.list_messages(queue_name=self.queue_name)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_get_message(self):
+ # Post Messages
+ _, body = self._post_messages()
+ message_uri = body['resources'][0]
+
+ # Get posted message
+ resp, _ = self.client.get_single_message(message_uri)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_get_multiple_messages(self):
+ # Post Messages
+ resp, _ = self._post_messages()
+ message_uri = resp['location']
+
+ # Get posted messages
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_delete_single_message(self):
+ # Post Messages
+ _, body = self._post_messages()
+ message_uri = body['resources'][0]
+
+ # Delete posted message & verify the delete operration
+ self.client.delete_messages(message_uri)
+
+ message_uri = message_uri.replace('/messages/', '/messages?ids=')
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response has to be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('204', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_delete_multiple_messages(self):
+ # Post Messages
+ resp, _ = self._post_messages()
+ message_uri = resp['location']
+
+ # Delete multiple messages
+ self.client.delete_messages(message_uri)
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response has to be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('204', resp['status'])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.delete_queue(cls.queue_name)
+ super(TestMessages, cls).tearDownClass()
diff --git a/tempest/api/telemetry/base.py b/tempest/api/telemetry/base.py
index c4614c6..2b422fd 100644
--- a/tempest/api/telemetry/base.py
+++ b/tempest/api/telemetry/base.py
@@ -10,9 +10,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.openstack.common import timeutils
import tempest.test
CONF = config.CONF
@@ -29,6 +32,12 @@
super(BaseTelemetryTest, cls).setUpClass()
os = cls.get_client_manager()
cls.telemetry_client = os.telemetry_client
+ cls.servers_client = os.servers_client
+ cls.flavors_client = os.flavors_client
+
+ cls.nova_notifications = ['memory', 'vcpus', 'disk.root.size',
+ 'disk.ephemeral.size']
+ cls.server_ids = []
cls.alarm_ids = []
@classmethod
@@ -41,11 +50,46 @@
return resp, body
@classmethod
- def tearDownClass(cls):
- for alarm_id in cls.alarm_ids:
+ def create_server(cls):
+ resp, body = cls.servers_client.create_server(
+ data_utils.rand_name('ceilometer-instance'),
+ CONF.compute.image_ref, CONF.compute.flavor_ref,
+ wait_until='ACTIVE')
+ if resp['status'] == '202':
+ cls.server_ids.append(body['id'])
+ return resp, body
+
+ @staticmethod
+ def cleanup_resources(method, list_of_ids):
+ for resource_id in list_of_ids:
try:
- cls.telemetry_client.delete_alarm(alarm_id)
+ method(resource_id)
except exceptions.NotFound:
pass
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cleanup_resources(cls.telemetry_client.delete_alarm, cls.alarm_ids)
+ cls.cleanup_resources(cls.servers_client.delete_server, cls.server_ids)
cls.clear_isolated_creds()
super(BaseTelemetryTest, cls).tearDownClass()
+
+ def await_samples(self, metric, query):
+ """
+ This method is to wait for sample to add it to database.
+ There are long time delays when using Postgresql (or Mysql)
+ database as ceilometer backend
+ """
+ timeout = CONF.compute.build_timeout
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout:
+ resp, body = self.telemetry_client.list_samples(metric, query)
+ self.assertEqual(resp.status, 200)
+ if body:
+ return resp, body
+ time.sleep(CONF.compute.build_interval)
+
+ raise exceptions.TimeoutException(
+ 'Sample for metric:%s with query:%s has not been added to the '
+ 'database within %d seconds' % (metric, query,
+ CONF.compute.build_timeout))
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
new file mode 100644
index 0000000..148f5a3
--- /dev/null
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -0,0 +1,47 @@
+# 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 testtools
+
+from tempest.api.telemetry import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class TelemetryNotificationAPITestJSON(base.BaseTelemetryTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ if CONF.telemetry.too_slow_to_test:
+ raise cls.skipException("Ceilometer feature for fast work mysql "
+ "is disabled")
+ super(TelemetryNotificationAPITestJSON, cls).setUpClass()
+
+ @test.attr(type="gate")
+ @testtools.skipIf(not CONF.service_available.nova,
+ "Nova is not available.")
+ def test_check_nova_notification(self):
+
+ resp, body = self.create_server()
+ self.assertEqual(resp.status, 202)
+
+ query = ('resource', 'eq', body['id'])
+
+ for metric in self.nova_notifications:
+ self.await_samples(metric, query)
+
+
+class TelemetryNotificationAPITestXML(TelemetryNotificationAPITestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index ee1d09a..3b8c214 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -118,14 +118,16 @@
'from the created Volume_type')
@test.attr(type='smoke')
- def test_volume_type_encryption_create_get(self):
- # Create/get encryption type.
+ def test_volume_type_encryption_create_get_delete(self):
+ # Create/get/delete encryption type.
provider = "LuksEncryptor"
control_location = "front-end"
name = data_utils.rand_name("volume-type-")
resp, body = self.client.create_volume_type(name)
self.assertEqual(200, resp.status)
self.addCleanup(self._delete_volume_type, body['id'])
+
+ # Create encryption type
resp, encryption_type = self.client.create_encryption_type(
body['id'], provider=provider,
control_location=control_location)
@@ -137,6 +139,8 @@
self.assertEqual(control_location, encryption_type['control_location'],
"The created encryption_type control_location is not "
"equal to the requested control_location")
+
+ # Get encryption type
resp, fetched_encryption_type = self.client.get_encryption_type(
encryption_type['volume_type_id'])
self.assertEqual(200, resp.status)
@@ -148,3 +152,15 @@
fetched_encryption_type['control_location'],
'The fetched encryption_type control_location is '
'different from the created encryption_type')
+
+ # Delete encryption type
+ resp, _ = self.client.delete_encryption_type(
+ encryption_type['volume_type_id'])
+ self.assertEqual(202, resp.status)
+ resource = {"id": encryption_type['volume_type_id'],
+ "type": "encryption-type"}
+ self.client.wait_for_resource_deletion(resource)
+ resp, deleted_encryption_type = self.client.get_encryption_type(
+ encryption_type['volume_type_id'])
+ self.assertEqual(200, resp.status)
+ self.assertEmpty(deleted_encryption_type)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index d3a052e..da421dc 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -29,9 +29,9 @@
super(ExtraSpecsNegativeTest, cls).setUpClass()
vol_type_name = data_utils.rand_name('Volume-type-')
cls.extra_specs = {"spec1": "val1"}
- resp, cls.volume_type = cls.client.create_volume_type(vol_type_name,
- extra_specs=
- cls.extra_specs)
+ resp, cls.volume_type = cls.client.create_volume_type(
+ vol_type_name,
+ extra_specs=cls.extra_specs)
@classmethod
def tearDownClass(cls):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 67d0203..c5be1f3 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -16,6 +16,7 @@
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -25,9 +26,11 @@
class BaseVolumeTest(tempest.test.BaseTestCase):
-
"""Base test case class for all Cinder API tests."""
+ _api_version = 2
+ _interface = 'json'
+
@classmethod
def setUpClass(cls):
cls.set_network_resources()
@@ -47,6 +50,28 @@
cls.snapshots = []
cls.volumes = []
+ if cls._api_version == 1:
+ if not CONF.volume_feature_enabled.api_v1:
+ msg = "Volume API v1 is disabled"
+ raise cls.skipException(msg)
+ cls.snapshots_client = cls.os.snapshots_client
+ cls.volumes_client = cls.os.volumes_client
+ cls.backups_client = cls.os.backups_client
+ cls.volume_services_client = cls.os.volume_services_client
+ cls.volumes_extension_client = cls.os.volumes_extension_client
+ cls.availability_zone_client = (
+ cls.os.volume_availability_zone_client)
+
+ elif cls._api_version == 2:
+ if not CONF.volume_feature_enabled.api_v2:
+ msg = "Volume API v2 is disabled"
+ raise cls.skipException(msg)
+ cls.volumes_client = cls.os.volumes_v2_client
+
+ else:
+ msg = ("Invalid Cinder API version (%s)" % cls._api_version)
+ raise exceptions.InvalidConfiguration(message=msg)
+
@classmethod
def tearDownClass(cls):
cls.clear_snapshots()
@@ -55,6 +80,22 @@
super(BaseVolumeTest, cls).tearDownClass()
@classmethod
+ def create_volume(cls, size=1, **kwargs):
+ """Wrapper utility that returns a test volume."""
+ vol_name = data_utils.rand_name('Volume')
+ if cls._api_version == 1:
+ resp, volume = cls.volumes_client.create_volume(
+ size, display_name=vol_name, **kwargs)
+ assert 200 == resp.status
+ elif cls._api_version == 2:
+ resp, volume = cls.volumes_client.create_volume(
+ size, name=vol_name, **kwargs)
+ assert 202 == resp.status
+ cls.volumes.append(volume)
+ cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ return volume
+
+ @classmethod
def create_snapshot(cls, volume_id=1, **kwargs):
"""Wrapper utility that returns a test snapshot."""
resp, snapshot = cls.snapshots_client.create_snapshot(volume_id,
@@ -98,30 +139,7 @@
class BaseVolumeV1Test(BaseVolumeTest):
- @classmethod
- def setUpClass(cls):
- if not CONF.volume_feature_enabled.api_v1:
- msg = "Volume API v1 not supported"
- raise cls.skipException(msg)
- super(BaseVolumeV1Test, cls).setUpClass()
- cls.snapshots_client = cls.os.snapshots_client
- cls.volumes_client = cls.os.volumes_client
- cls.backups_client = cls.os.backups_client
- cls.volume_services_client = cls.os.volume_services_client
- cls.volumes_extension_client = cls.os.volumes_extension_client
- cls.availability_zone_client = cls.os.volume_availability_zone_client
-
- @classmethod
- def create_volume(cls, size=1, **kwargs):
- """Wrapper utility that returns a test volume."""
- vol_name = data_utils.rand_name('Volume')
- resp, volume = cls.volumes_client.create_volume(size,
- display_name=vol_name,
- **kwargs)
- assert 200 == resp.status
- cls.volumes.append(volume)
- cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
- return volume
+ _api_version = 1
class BaseVolumeV1AdminTest(BaseVolumeV1Test):
@@ -144,25 +162,3 @@
cls.client = cls.os_adm.volume_types_client
cls.hosts_client = cls.os_adm.volume_hosts_client
cls.quotas_client = cls.os_adm.volume_quotas_client
-
-
-class BaseVolumeV2Test(BaseVolumeTest):
- @classmethod
- def setUpClass(cls):
- if not CONF.volume_feature_enabled.api_v2:
- msg = "Volume API v2 not supported"
- raise cls.skipException(msg)
- super(BaseVolumeV2Test, cls).setUpClass()
- cls.volumes_client = cls.os.volumes_v2_client
-
- @classmethod
- def create_volume(cls, size=1, **kwargs):
- """Wrapper utility that returns a test volume."""
- vol_name = data_utils.rand_name('Volume')
- resp, volume = cls.volumes_client.create_volume(size,
- name=vol_name,
- **kwargs)
- assert 202 == resp.status
- cls.volumes.append(volume)
- cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
- return volume
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index 1db7b7b..fe8f96e 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -22,6 +22,7 @@
"""
Tests Availability Zone API List
"""
+ _interface = 'json'
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 58da440..2745b95 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -39,7 +39,7 @@
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"
+ # specifically check "true" or "false"
if val in ['true', 'True', True]:
return True
else:
@@ -121,19 +121,19 @@
new_volume = {}
new_v_desc = data_utils.rand_name('@#$%^* description')
resp, new_volume = \
- self.client.create_volume(size=1,
- display_description=new_v_desc,
- availability_zone=
- volume['availability_zone'])
+ self.client.create_volume(
+ size=1,
+ display_description=new_v_desc,
+ availability_zone=volume['availability_zone'])
self.assertEqual(200, resp.status)
self.assertIn('id', new_volume)
self.addCleanup(self._delete_volume, new_volume['id'])
self.client.wait_for_volume_status(new_volume['id'], 'available')
resp, update_volume = \
- self.client.update_volume(new_volume['id'],
- display_name=volume['display_name'],
- display_description=
- volume['display_description'])
+ self.client.update_volume(
+ new_volume['id'],
+ display_name=volume['display_name'],
+ display_description=volume['display_description'])
self.assertEqual(200, resp.status)
# NOTE(jdg): Revert back to strict true/false checking
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index e2f7a38..b8a2faa 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -23,10 +23,8 @@
LOG = logging.getLogger(__name__)
-VOLUME_FIELDS = ('id', 'display_name')
-
-class VolumesListTest(base.BaseVolumeV1Test):
+class VolumesV2ListTestJSON(base.BaseVolumeTest):
"""
This test creates a number of 1G volumes. To run successfully,
@@ -36,7 +34,7 @@
VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
"""
- _interface = 'json'
+ VOLUME_FIELDS = ('id', 'name')
def assertVolumesIn(self, fetched_list, expected_list, fields=None):
if fields:
@@ -48,7 +46,7 @@
return
def str_vol(vol):
- return "%s:%s" % (vol['id'], vol['display_name'])
+ return "%s:%s" % (vol['id'], vol[self.name])
raw_msg = "Could not find volumes %s in expected list %s; fetched %s"
self.fail(raw_msg % ([str_vol(v) for v in missing_vols],
@@ -58,8 +56,9 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesListTest, cls).setUpClass()
+ super(VolumesV2ListTestJSON, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name = cls.VOLUME_FIELDS[1]
# Create 3 test volumes
cls.volume_list = []
@@ -77,7 +76,7 @@
for volid in cls.volume_id_list:
resp, _ = cls.client.delete_volume(volid)
cls.client.wait_for_resource_deletion(volid)
- super(VolumesListTest, cls).tearDownClass()
+ super(VolumesV2ListTestJSON, cls).tearDownClass()
def _list_by_param_value_and_assert(self, params, with_detail=False):
"""
@@ -92,16 +91,22 @@
self.assertEqual(200, resp.status)
# Validating params of fetched volumes
- for volume in fetched_vol_list:
- for key in params:
- msg = "Failed to list volumes %s by %s" % \
- ('details' if with_detail else '', key)
- if key == 'metadata':
- self.assertThat(volume[key].items(),
- matchers.ContainsAll(params[key].items()),
- msg)
- else:
- self.assertEqual(params[key], volume[key], msg)
+ # In v2, only list detail view includes items in params.
+ # In v1, list view and list detail view are same. So the
+ # following check should be run when 'with_detail' is True
+ # or v1 tests.
+ if with_detail or self._api_version == 1:
+ for volume in fetched_vol_list:
+ for key in params:
+ msg = "Failed to list volumes %s by %s" % \
+ ('details' if with_detail else '', key)
+ if key == 'metadata':
+ self.assertThat(
+ volume[key].items(),
+ matchers.ContainsAll(params[key].items()),
+ msg)
+ else:
+ self.assertEqual(params[key], volume[key], msg)
@test.attr(type='smoke')
def test_volume_list(self):
@@ -110,7 +115,7 @@
resp, fetched_list = self.client.list_volumes()
self.assertEqual(200, resp.status)
self.assertVolumesIn(fetched_list, self.volume_list,
- fields=VOLUME_FIELDS)
+ fields=self.VOLUME_FIELDS)
@test.attr(type='gate')
def test_volume_list_with_details(self):
@@ -123,32 +128,31 @@
@test.attr(type='gate')
def test_volume_list_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'display_name': volume['display_name']}
+ params = {self.name: volume[self.name]}
resp, fetched_vol = self.client.list_volumes(params)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
- self.assertEqual(fetched_vol[0]['display_name'],
- volume['display_name'])
+ self.assertEqual(fetched_vol[0][self.name],
+ volume[self.name])
@test.attr(type='gate')
def test_volume_list_details_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'display_name': volume['display_name']}
+ params = {self.name: volume[self.name]}
resp, fetched_vol = self.client.list_volumes_with_detail(params)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
- self.assertEqual(fetched_vol[0]['display_name'],
- volume['display_name'])
+ self.assertEqual(fetched_vol[0][self.name],
+ volume[self.name])
@test.attr(type='gate')
def test_volumes_list_by_status(self):
params = {'status': 'available'}
resp, fetched_list = self.client.list_volumes(params)
self.assertEqual(200, resp.status)
- for volume in fetched_list:
- self.assertEqual('available', volume['status'])
+ self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
- fields=VOLUME_FIELDS)
+ fields=self.VOLUME_FIELDS)
@test.attr(type='gate')
def test_volumes_list_details_by_status(self):
@@ -166,10 +170,9 @@
params = {'availability_zone': zone}
resp, fetched_list = self.client.list_volumes(params)
self.assertEqual(200, resp.status)
- for volume in fetched_list:
- self.assertEqual(zone, volume['availability_zone'])
+ self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
- fields=VOLUME_FIELDS)
+ fields=self.VOLUME_FIELDS)
@test.attr(type='gate')
def test_volumes_list_details_by_availability_zone(self):
@@ -198,7 +201,7 @@
def test_volume_list_param_display_name_and_status(self):
# Test to list volume when display name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'display_name': volume['display_name'],
+ params = {self.name: volume[self.name],
'status': 'available'}
self._list_by_param_value_and_assert(params)
@@ -206,10 +209,19 @@
def test_volume_list_with_detail_param_display_name_and_status(self):
# Test to list volume when name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'display_name': volume['display_name'],
+ params = {self.name: volume[self.name],
'status': 'available'}
self._list_by_param_value_and_assert(params, with_detail=True)
-class VolumeListTestXML(VolumesListTest):
+class VolumesV2ListTestXML(VolumesV2ListTestJSON):
+ _interface = 'xml'
+
+
+class VolumesV1ListTestJSON(VolumesV2ListTestJSON):
+ _api_version = 1
+ VOLUME_FIELDS = ('id', 'display_name')
+
+
+class VolumesV1ListTestXML(VolumesV1ListTestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index a8b0a8d..bc5b1dc 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -154,6 +154,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
@test.attr(type=['negative', 'gate'])
+ @test.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
srv_name = data_utils.rand_name('Instance-')
resp, server = self.servers_client.create_server(srv_name,
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 6294cd9..26316d2 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -63,6 +63,7 @@
self.assertEqual(params[key], snap[key], msg)
@test.attr(type='gate')
+ @test.services('compute')
def test_snapshot_create_with_volume_in_use(self):
# Create a snapshot when volume status is in-use
# Create a test instance
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index e90c957..7ca8599 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -13,22 +13,16 @@
# 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 operator
from tempest.api.volume import base
-from tempest.common.utils import data_utils
-from tempest.openstack.common import log as logging
from tempest import test
-from testtools import matchers
-
-LOG = logging.getLogger(__name__)
-
-VOLUME_FIELDS = ('id', 'name')
-class VolumesV2ListTestJSON(base.BaseVolumeV2Test):
+class VolumesV2ListTestJSON(base.BaseVolumeTest):
"""
+ volumes v2 specific tests.
+
This test creates a number of 1G volumes. To run successfully,
ensure that the backing file for the volume group that Nova uses
has space for at least 3 1G volumes!
@@ -36,25 +30,6 @@
VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc
"""
- _interface = 'json'
-
- def assertVolumesIn(self, fetched_list, expected_list, fields=None):
- if fields:
- expected_list = map(operator.itemgetter(*fields), expected_list)
- fetched_list = map(operator.itemgetter(*fields), fetched_list)
-
- missing_vols = [v for v in expected_list if v not in fetched_list]
- if len(missing_vols) == 0:
- return
-
- def str_vol(vol):
- return "%s:%s" % (vol['id'], vol['name'])
-
- raw_msg = "Could not find volumes %s in expected list %s; fetched %s"
- self.fail(raw_msg % ([str_vol(v) for v in missing_vols],
- [str_vol(v) for v in expected_list],
- [str_vol(v) for v in fetched_list]))
-
@classmethod
@test.safe_setup
def setUpClass(cls):
@@ -79,124 +54,6 @@
cls.client.wait_for_resource_deletion(volid)
super(VolumesV2ListTestJSON, cls).tearDownClass()
- def _list_by_param_value_and_assert(self, params, expected_list=None,
- with_detail=False):
- """
- Perform list or list_details action with given params
- and validates result.
- """
- if with_detail:
- resp, fetched_vol_list = \
- self.client.list_volumes_with_detail(params=params)
- else:
- resp, fetched_vol_list = self.client.list_volumes(params=params)
-
- self.assertEqual(200, resp.status)
- if expected_list is None:
- expected_list = self.volume_list
- self.assertVolumesIn(fetched_vol_list, expected_list,
- fields=VOLUME_FIELDS)
- # Validating params of fetched volumes
- if with_detail:
- for volume in fetched_vol_list:
- for key in params:
- msg = "Failed to list volumes %s by %s" % \
- ('details' if with_detail else '', key)
- if key == 'metadata':
- self.assertThat(volume[key].items(),
- matchers.ContainsAll(
- params[key].items()), msg)
- else:
- self.assertEqual(params[key], volume[key], msg)
-
- @test.attr(type='smoke')
- def test_volume_list(self):
- # Get a list of Volumes
- # Fetch all volumes
- resp, fetched_list = self.client.list_volumes()
- self.assertEqual(200, resp.status)
- self.assertVolumesIn(fetched_list, self.volume_list,
- fields=VOLUME_FIELDS)
-
- @test.attr(type='gate')
- def test_volume_list_with_details(self):
- # Get a list of Volumes with details
- # Fetch all Volumes
- resp, fetched_list = self.client.list_volumes_with_detail()
- self.assertEqual(200, resp.status)
- self.assertVolumesIn(fetched_list, self.volume_list)
-
- @test.attr(type='gate')
- def test_volume_list_by_name(self):
- volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'name': volume['name']}
- resp, fetched_vol = self.client.list_volumes(params)
- self.assertEqual(200, resp.status)
- self.assertEqual(1, len(fetched_vol), str(fetched_vol))
- self.assertEqual(fetched_vol[0]['name'], volume['name'])
-
- @test.attr(type='gate')
- def test_volume_list_details_by_name(self):
- volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'name': volume['name']}
- resp, fetched_vol = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
- self.assertEqual(1, len(fetched_vol), str(fetched_vol))
- self.assertEqual(fetched_vol[0]['name'], volume['name'])
-
- @test.attr(type='gate')
- def test_volumes_list_by_status(self):
- params = {'status': 'available'}
- self._list_by_param_value_and_assert(params)
-
- @test.attr(type='gate')
- def test_volumes_list_details_by_status(self):
- params = {'status': 'available'}
- self._list_by_param_value_and_assert(params, with_detail=True)
-
- @test.attr(type='gate')
- def test_volumes_list_by_availability_zone(self):
- volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- zone = volume['availability_zone']
- params = {'availability_zone': zone}
- self._list_by_param_value_and_assert(params)
-
- @test.attr(type='gate')
- def test_volumes_list_details_by_availability_zone(self):
- volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- zone = volume['availability_zone']
- params = {'availability_zone': zone}
- self._list_by_param_value_and_assert(params, with_detail=True)
-
- @test.attr(type='gate')
- def test_volume_list_with_param_metadata(self):
- # Test to list volumes when metadata param is given
- params = {'metadata': self.metadata}
- self._list_by_param_value_and_assert(params)
-
- @test.attr(type='gate')
- def test_volume_list_with_details_param_metadata(self):
- # Test to list volumes details when metadata param is given
- params = {'metadata': self.metadata}
- self._list_by_param_value_and_assert(params, with_detail=True)
-
- @test.attr(type='gate')
- def test_volume_list_param_display_name_and_status(self):
- # Test to list volume when display name and status param is given
- volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'name': volume['name'],
- 'status': 'available'}
- self._list_by_param_value_and_assert(params, expected_list=[volume])
-
- @test.attr(type='gate')
- def test_volume_list_with_details_param_display_name_and_status(self):
- # Test to list volume when name and status param is given
- volume = self.volume_list[data_utils.rand_int_id(0, 2)]
- params = {'name': volume['name'],
- 'status': 'available'}
- self._list_by_param_value_and_assert(params, expected_list=[volume],
- with_detail=True)
-
@test.attr(type='gate')
def test_volume_list_details_with_multiple_params(self):
# List volumes detail using combined condition
diff --git a/tempest/api_schema/compute/agents.py b/tempest/api_schema/compute/agents.py
index b04cf64..e5f3a8d 100644
--- a/tempest/api_schema/compute/agents.py
+++ b/tempest/api_schema/compute/agents.py
@@ -22,7 +22,7 @@
'items': {
'type': 'object',
'properties': {
- 'agent_id': {'type': ['integer', 'string']},
+ 'agent_id': {'type': 'integer'},
'hypervisor': {'type': 'string'},
'os': {'type': 'string'},
'architecture': {'type': 'string'},
@@ -38,3 +38,24 @@
'required': ['agents']
}
}
+
+common_create_agent = {
+ 'type': 'object',
+ 'properties': {
+ 'agent': {
+ 'type': 'object',
+ 'properties': {
+ 'agent_id': {'type': ['integer', 'string']},
+ 'hypervisor': {'type': 'string'},
+ 'os': {'type': 'string'},
+ 'architecture': {'type': 'string'},
+ 'version': {'type': 'string'},
+ 'url': {'type': 'string', 'format': 'uri'},
+ 'md5hash': {'type': 'string'}
+ },
+ 'required': ['agent_id', 'hypervisor', 'os', 'architecture',
+ 'version', 'url', 'md5hash']
+ }
+ },
+ 'required': ['agent']
+}
diff --git a/tempest/api_schema/compute/hypervisors.py b/tempest/api_schema/compute/hypervisors.py
index 630901e..e9e1bc9 100644
--- a/tempest/api_schema/compute/hypervisors.py
+++ b/tempest/api_schema/compute/hypervisors.py
@@ -24,7 +24,7 @@
'properties': {
'count': {'type': 'integer'},
'current_workload': {'type': 'integer'},
- 'disk_available_least': {'type': 'integer'},
+ 'disk_available_least': {'type': ['integer', 'null']},
'free_disk_gb': {'type': 'integer'},
'free_ram_mb': {'type': 'integer'},
'local_gb': {'type': 'integer'},
@@ -110,7 +110,7 @@
'properties': {
'cpu_info': {'type': 'string'},
'current_workload': {'type': 'integer'},
- 'disk_available_least': {'type': 'integer'},
+ 'disk_available_least': {'type': ['integer', 'null']},
'host_ip': {
'type': 'string',
'format': 'ip-address'
diff --git a/tempest/api_schema/compute/keypairs.py b/tempest/api_schema/compute/keypairs.py
index b8f905f..2ae410c 100644
--- a/tempest/api_schema/compute/keypairs.py
+++ b/tempest/api_schema/compute/keypairs.py
@@ -49,10 +49,7 @@
'fingerprint': {'type': 'string'},
'name': {'type': 'string'},
'public_key': {'type': 'string'},
- # NOTE: Now the type of 'user_id' is integer, but here
- # allows 'string' also because we will be able to change
- # it to 'uuid' in the future.
- 'user_id': {'type': ['integer', 'string']},
+ 'user_id': {'type': 'string'},
'private_key': {'type': 'string'}
},
# When create keypair API is being called with 'Public key'
diff --git a/tempest/api_schema/compute/migrations.py b/tempest/api_schema/compute/migrations.py
index 6723869..6549272 100644
--- a/tempest/api_schema/compute/migrations.py
+++ b/tempest/api_schema/compute/migrations.py
@@ -22,10 +22,7 @@
'items': {
'type': 'object',
'properties': {
- # NOTE: Now the type of 'id' is integer, but here
- # allows 'string' also because we will be able to
- # change it to 'uuid' in the future.
- 'id': {'type': ['integer', 'string']},
+ 'id': {'type': 'integer'},
'status': {'type': 'string'},
'instance_uuid': {'type': 'string'},
'source_node': {'type': 'string'},
@@ -33,12 +30,8 @@
'dest_node': {'type': 'string'},
'dest_compute': {'type': 'string'},
'dest_host': {'type': 'string'},
- 'old_instance_type_id': {
- 'type': ['integer', 'string']
- },
- 'new_instance_type_id': {
- 'type': ['integer', 'string']
- },
+ 'old_instance_type_id': {'type': 'integer'},
+ 'new_instance_type_id': {'type': 'integer'},
'created_at': {'type': 'string'},
'updated_at': {'type': ['string', 'null']}
},
diff --git a/tempest/api_schema/compute/servers.py b/tempest/api_schema/compute/servers.py
index 2002927..6b27f3d 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -48,47 +48,54 @@
}
}
-base_update_server = {
+common_show_server = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'required': ['id', 'links']
+ },
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'required': ['id', 'links']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ },
+ # NOTE(GMann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # So it is not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses']
+}
+
+base_update_get_server = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
- 'server': {
- 'type': 'object',
- 'properties': {
- 'id': {'type': ['integer', 'string']},
- 'name': {'type': 'string'},
- 'status': {'type': 'string'},
- 'image': {
- 'type': 'object',
- 'properties': {
- 'id': {'type': ['integer', 'string']},
- 'links': parameter_types.links
- },
- 'required': ['id', 'links']
- },
- 'flavor': {
- 'type': 'object',
- 'properties': {
- 'id': {'type': ['integer', 'string']},
- 'links': parameter_types.links
- },
- 'required': ['id', 'links']
- },
- 'user_id': {'type': 'string'},
- 'tenant_id': {'type': 'string'},
- 'created': {'type': 'string'},
- 'updated': {'type': 'string'},
- 'progress': {'type': 'integer'},
- 'metadata': {'type': 'object'},
- 'links': parameter_types.links,
- 'addresses': parameter_types.addresses,
- },
- 'required': ['id', 'name', 'status', 'image', 'flavor',
- 'user_id', 'tenant_id', 'created', 'updated',
- 'progress', 'metadata', 'links', 'addresses']
- }
- }
+ 'server': common_show_server
+ },
+ 'required': ['server']
}
}
@@ -114,6 +121,8 @@
list_server_metadata = copy.deepcopy(set_server_metadata)
+update_server_metadata = copy.deepcopy(set_server_metadata)
+
delete_server_metadata_item = {
'status_code': [204]
}
@@ -158,3 +167,31 @@
'required': ['output']
}
}
+
+common_instance_actions = {
+ 'type': 'object',
+ 'properties': {
+ 'action': {'type': 'string'},
+ 'request_id': {'type': 'string'},
+ 'user_id': {'type': 'string'},
+ 'project_id': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'message': {'type': ['string', 'null']}
+ },
+ 'required': ['action', 'request_id', 'user_id', 'project_id',
+ 'start_time', 'message']
+}
+
+base_list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': common_show_server
+ }
+ },
+ 'required': ['servers']
+ }
+}
diff --git a/tempest/api_schema/compute/services.py b/tempest/api_schema/compute/services.py
index 4c58013..eaba129 100644
--- a/tempest/api_schema/compute/services.py
+++ b/tempest/api_schema/compute/services.py
@@ -22,10 +22,7 @@
'items': {
'type': 'object',
'properties': {
- # NOTE: Now the type of 'id' is integer, but here
- # allows 'string' also because we will be able to
- # change it to 'uuid' in the future.
- 'id': {'type': ['integer', 'string']},
+ 'id': {'type': 'integer'},
'zone': {'type': 'string'},
'host': {'type': 'string'},
'state': {'type': 'string'},
diff --git a/tempest/api_schema/compute/v2/agents.py b/tempest/api_schema/compute/v2/agents.py
index 837731f..30f999f 100644
--- a/tempest/api_schema/compute/v2/agents.py
+++ b/tempest/api_schema/compute/v2/agents.py
@@ -12,6 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.api_schema.compute import agents
+
+create_agent = {
+ 'status_code': [200],
+ 'response_body': agents.common_create_agent
+}
+
delete_agent = {
'status_code': [200]
}
diff --git a/tempest/api_schema/compute/v2/floating_ips.py b/tempest/api_schema/compute/v2/floating_ips.py
index 3ea6320..def0a78 100644
--- a/tempest/api_schema/compute/v2/floating_ips.py
+++ b/tempest/api_schema/compute/v2/floating_ips.py
@@ -27,7 +27,7 @@
# able to change it to 'uuid' in the future.
'id': {'type': ['integer', 'string']},
'pool': {'type': ['string', 'null']},
- 'instance_id': {'type': ['integer', 'string', 'null']},
+ 'instance_id': {'type': ['string', 'null']},
'ip': {
'type': 'string',
'format': 'ip-address'
@@ -58,7 +58,7 @@
# 'uuid' in the future.
'id': {'type': ['integer', 'string']},
'pool': {'type': ['string', 'null']},
- 'instance_id': {'type': ['integer', 'string', 'null']},
+ 'instance_id': {'type': ['string', 'null']},
'ip': {
'type': 'string',
'format': 'ip-address'
@@ -98,3 +98,61 @@
add_remove_floating_ip = {
'status_code': [202]
}
+
+create_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_create': {
+ 'type': 'object',
+ 'properties': {
+ 'interface': {'type': ['string', 'null']},
+ 'ip_range': {'type': 'string'},
+ 'pool': {'type': ['string', 'null']},
+ },
+ 'required': ['interface', 'ip_range', 'pool']
+ }
+ },
+ 'required': ['floating_ips_bulk_create']
+ }
+}
+
+delete_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_delete': {'type': 'string'}
+ },
+ 'required': ['floating_ips_bulk_delete']
+ }
+}
+
+list_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip_info': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'address': {
+ 'type': 'string',
+ 'format': 'ip-address'
+ },
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'interface': {'type': ['string', 'null']},
+ 'pool': {'type': ['string', 'null']},
+ 'project_id': {'type': ['string', 'null']}
+ },
+ 'required': ['address', 'instance_uuid', 'interface',
+ 'pool', 'project_id']
+ }
+ }
+ },
+ 'required': ['floating_ip_info']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/images.py b/tempest/api_schema/compute/v2/images.py
index d121060..90737a2 100644
--- a/tempest/api_schema/compute/v2/images.py
+++ b/tempest/api_schema/compute/v2/images.py
@@ -30,10 +30,7 @@
'server': {
'type': 'object',
'properties': {
- # NOTE: Now the type of 'id' is integer, but here
- # allows 'string' also because we will be able to
- # change it to 'uuid' in the future.
- 'id': {'type': ['integer', 'string']},
+ 'id': {'type': 'string'},
'links': parameter_types.links
},
'required': ['id', 'links']
diff --git a/tempest/api_schema/compute/v2/keypairs.py b/tempest/api_schema/compute/v2/keypairs.py
index 9a025c3..32d8cca 100644
--- a/tempest/api_schema/compute/v2/keypairs.py
+++ b/tempest/api_schema/compute/v2/keypairs.py
@@ -25,10 +25,7 @@
'public_key': {'type': 'string'},
'name': {'type': 'string'},
'fingerprint': {'type': 'string'},
- # NOTE: Now the type of 'user_id' is integer, but here
- # allows 'string' also because we will be able to change
- # it to 'uuid' in the future.
- 'user_id': {'type': ['integer', 'string']},
+ 'user_id': {'type': 'string'},
'deleted': {'type': 'boolean'},
'created_at': {'type': 'string'},
'updated_at': {'type': ['string', 'null']},
diff --git a/tempest/api_schema/compute/v2/quota_classes.py b/tempest/api_schema/compute/v2/quota_classes.py
new file mode 100644
index 0000000..3464fb4
--- /dev/null
+++ b/tempest/api_schema/compute/v2/quota_classes.py
@@ -0,0 +1,31 @@
+# Copyright 2014 IBM Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.api_schema.compute.v2 import quotas
+
+# NOTE(mriedem): os-quota-class-sets responses are the same as os-quota-sets
+# except for the key in the response body is quota_class_set instead of
+# quota_set, so update this copy of the schema from os-quota-sets.
+quota_set = copy.deepcopy(quotas.quota_set)
+quota_set['response_body']['properties']['quota_class_set'] = (
+ quota_set['response_body']['properties'].pop('quota_set'))
+quota_set['response_body']['required'] = ['quota_class_set']
+
+quota_set_update = copy.deepcopy(quotas.quota_set_update)
+quota_set_update['response_body']['properties']['quota_class_set'] = (
+ quota_set_update['response_body']['properties'].pop('quota_set'))
+quota_set_update['response_body']['required'] = ['quota_class_set']
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index 3da3276..cd46f9b 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -25,10 +25,7 @@
'server': {
'type': 'object',
'properties': {
- # NOTE: Now the type of 'id' is uuid, but here allows
- # 'integer' also because old OpenStack uses 'integer'
- # as a server id.
- 'id': {'type': ['integer', 'string']},
+ 'id': {'type': 'string'},
'security_groups': {'type': 'array'},
'links': parameter_types.links,
'adminPass': {'type': 'string'},
@@ -46,7 +43,7 @@
}
}
-update_server = copy.deepcopy(servers.base_update_server)
+update_server = copy.deepcopy(servers.base_update_get_server)
update_server['response_body']['properties']['server']['properties'].update({
'hostId': {'type': 'string'},
'OS-DCF:diskConfig': {'type': 'string'},
@@ -60,6 +57,39 @@
'hostId'
)
+get_server = copy.deepcopy(servers.base_update_get_server)
+get_server['response_body']['properties']['server']['properties'].update({
+ 'key_name': {'type': ['string', 'null']},
+ 'hostId': {'type': 'string'},
+
+ # NOTE: Non-admin users also can see "OS-SRV-USG" and "OS-EXT-AZ"
+ # attributes.
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+
+ # NOTE: Admin users only can see "OS-EXT-STS" and "OS-EXT-SRV-ATTR"
+ # attributes.
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'os-extended-volumes:volumes_attached': {'type': 'array'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6,
+ 'config_drive': {'type': 'string'}
+})
+get_server['response_body']['properties']['server']['required'].append(
+ # NOTE: OS-SRV-USG, OS-EXT-AZ, OS-EXT-STS, OS-EXT-SRV-ATTR,
+ # os-extended-volumes, OS-DCF and accessIPv4/v6 are API
+ # extension, and some environments return a response without
+ # these attributes. So they are not 'required'.
+ 'hostId'
+)
+
list_virtual_interfaces = {
'status_code': [200],
'response_body': {
@@ -131,3 +161,96 @@
server_actions_confirm_resize = copy.deepcopy(
servers.server_actions_delete_password)
+
+list_addresses = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'addresses': parameter_types.addresses
+ },
+ 'required': ['addresses']
+ }
+}
+
+common_server_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'policies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ # 'members' attribute contains the array of instance's UUID of
+ # instances present in server group
+ 'members': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'metadata': {'type': 'object'}
+ },
+ 'required': ['id', 'name', 'policies', 'members', 'metadata']
+}
+
+create_get_server_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_group': common_server_group
+ },
+ 'required': ['server_group']
+ }
+}
+
+delete_server_group = {
+ 'status_code': [204]
+}
+
+list_server_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_groups': {
+ 'type': 'array',
+ 'items': common_server_group
+ }
+ },
+ 'required': ['server_groups']
+ }
+}
+
+instance_actions_object = copy.deepcopy(servers.common_instance_actions)
+instance_actions_object[
+ 'properties'].update({'instance_uuid': {'type': 'string'}})
+instance_actions_object['required'].extend(['instance_uuid'])
+
+list_instance_actions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceActions': {
+ 'type': 'array',
+ 'items': instance_actions_object
+ }
+ },
+ 'required': ['instanceActions']
+ }
+}
+
+list_servers_detail = copy.deepcopy(servers.base_list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'properties'].update({
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6
+ })
+# NOTE(GMann): OS-DCF:diskConfig and accessIPv4/v6 are API
+# extensions, and some environments return a response
+# without these attributes. So they are not 'required'.
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'required'].append('hostId')
diff --git a/tempest/api_schema/compute/v2/volumes.py b/tempest/api_schema/compute/v2/volumes.py
index 84a659c..1af951f 100644
--- a/tempest/api_schema/compute/v2/volumes.py
+++ b/tempest/api_schema/compute/v2/volumes.py
@@ -38,7 +38,7 @@
'id': {'type': 'string'},
'device': {'type': 'string'},
'volumeId': {'type': 'string'},
- 'serverId': {'type': ['integer', 'string']}
+ 'serverId': {'type': 'string'}
}
# NOTE- If volume is not attached to any server
# then, 'attachments' attributes comes as array
@@ -86,7 +86,7 @@
'id': {'type': 'string'},
'device': {'type': 'string'},
'volumeId': {'type': 'string'},
- 'serverId': {'type': ['integer', 'string']}
+ 'serverId': {'type': 'string'}
}
# NOTE- If volume is not attached to any server
# then, 'attachments' attributes comes as array
diff --git a/tempest/api_schema/compute/v3/agents.py b/tempest/api_schema/compute/v3/agents.py
index 63d1c46..597a089 100644
--- a/tempest/api_schema/compute/v3/agents.py
+++ b/tempest/api_schema/compute/v3/agents.py
@@ -12,6 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.api_schema.compute import agents
+
+create_agent = {
+ 'status_code': [201],
+ 'response_body': agents.common_create_agent
+}
+
delete_agent = {
'status_code': [204]
}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index 8f694f7..b6cb13b 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -25,10 +25,7 @@
'server': {
'type': 'object',
'properties': {
- # NOTE: Now the type of 'id' is uuid, but here allows
- # 'integer' also because old OpenStack uses 'integer'
- # as a server id.
- 'id': {'type': ['integer', 'string']},
+ 'id': {'type': 'string'},
'os-security-groups:security_groups': {'type': 'array'},
'links': parameter_types.links,
'admin_password': {'type': 'string'},
@@ -57,7 +54,7 @@
['type', 'mac_addr']
)
-update_server = copy.deepcopy(servers.base_update_server)
+update_server = copy.deepcopy(servers.base_update_get_server)
update_server['response_body']['properties']['server']['properties'].update({
'addresses': addresses_v3,
'host_id': {'type': 'string'},
@@ -71,6 +68,43 @@
'host_id'
)
+get_server = copy.deepcopy(servers.base_update_get_server)
+get_server['response_body']['properties']['server']['properties'].update({
+ 'key_name': {'type': ['string', 'null']},
+ 'host_id': {'type': 'string'},
+
+ # NOTE: Non-admin users also can see "os-server-usage" and
+ # "os-extended-availability-zone" attributes.
+ 'os-server-usage:launched_at': {'type': ['string', 'null']},
+ 'os-server-usage:terminated_at': {'type': ['string', 'null']},
+ 'os-extended-availability-zone:availability_zone': {'type': 'string'},
+
+ # NOTE: Admin users only can see "os-extended-status" and
+ # "os-extended-server-attributes" attributes.
+ 'os-extended-status:task_state': {'type': ['string', 'null']},
+ 'os-extended-status:vm_state': {'type': 'string'},
+ 'os-extended-status:power_state': {'type': 'integer'},
+ 'os-extended-status:locked_by': {'type': ['string', 'null']},
+ 'os-extended-server-attributes:host': {'type': ['string', 'null']},
+ 'os-extended-server-attributes:instance_name': {'type': 'string'},
+ 'os-extended-server-attributes:hypervisor_hostname': {
+ 'type': ['string', 'null']
+ },
+ 'os-extended-volumes:volumes_attached': {'type': 'array'},
+ 'os-pci:pci_devices': {'type': 'array'},
+ 'os-access-ips:access_ip_v4': parameter_types.access_ip_v4,
+ 'os-access-ips:access_ip_v6': parameter_types.access_ip_v6,
+ 'os-config-drive:config_drive': {'type': 'string'}
+})
+get_server['response_body']['properties']['server']['required'].append(
+ # NOTE: os-server-usage, os-extended-availability-zone,
+ # os-extended-status, os-extended-server-attributes,
+ # os-extended-volumes, os-pci, os-access-ips and
+ # os-config-driveare API extension, and some environments
+ # return a response without these attributes. So they are not 'required'.
+ 'host_id'
+)
+
attach_detach_volume = {
'status_code': [202]
}
@@ -84,3 +118,50 @@
server_actions_change_password = copy.deepcopy(
servers.server_actions_delete_password)
+
+list_addresses = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'addresses': addresses_v3
+ },
+ 'required': ['addresses']
+ }
+}
+
+update_server_metadata = copy.deepcopy(servers.update_server_metadata)
+# V3 API's response status_code is 201
+update_server_metadata['status_code'] = [201]
+
+server_actions_object = copy.deepcopy(servers.common_instance_actions)
+server_actions_object['properties'].update({'server_uuid': {'type': 'string'}})
+server_actions_object['required'].extend(['server_uuid'])
+
+list_server_actions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_actions': {
+ 'type': 'array',
+ 'items': server_actions_object
+ }
+ },
+ 'required': ['server_actions']
+ }
+}
+
+list_servers_detail = copy.deepcopy(servers.base_list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'properties'].update({
+ 'addresses': addresses_v3,
+ 'host_id': {'type': 'string'},
+ 'os-access-ips:access_ip_v4': parameter_types.access_ip_v4,
+ 'os-access-ips:access_ip_v6': parameter_types.access_ip_v6
+ })
+# NOTE(GMann): os-access-ips:access_ip_v4/v6 are API extension,
+# and some environments return a response without these
+# attributes. So they are not 'required'.
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'required'].append('host_id')
diff --git a/tempest/api_schema/queuing/v1/queues.py b/tempest/api_schema/queuing/v1/queues.py
index 4630e1c..f0b2691 100644
--- a/tempest/api_schema/queuing/v1/queues.py
+++ b/tempest/api_schema/queuing/v1/queues.py
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
list_link = {
'type': 'object',
'properties': {
@@ -58,6 +59,11 @@
}
}
+age = {
+ 'type': 'number',
+ 'minimum': 0
+}
+
message_link = {
'type': 'object',
'properties': {
@@ -65,7 +71,7 @@
'type': 'string',
'format': 'uri'
},
- 'age': {'type': 'number'},
+ 'age': age,
'created': {
'type': 'string',
'format': 'date-time'
@@ -96,3 +102,136 @@
'required': ['messages']
}
}
+
+resource_schema = {
+ 'type': 'array',
+ 'items': 'string',
+ 'minItems': 1
+}
+
+post_messages = {
+ 'status_code': [201],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'resources': resource_schema,
+ 'partial': {'type': 'boolean'}
+ }
+ },
+ 'required': ['resources', 'partial']
+}
+
+message_ttl = {
+ 'type': 'number',
+ 'minimum': 1
+}
+
+list_messages_links = {
+ 'type': 'array',
+ 'maxItems': 1,
+ 'minItems': 1,
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'rel': {'type': 'string'},
+ 'href': {'type': 'string'}
+ },
+ 'required': ['rel', 'href']
+ }
+}
+
+list_messages_response = {
+ 'type': 'array',
+ 'minItems': 1,
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {'type': 'string'},
+ 'ttl': message_ttl,
+ 'age': age,
+ 'body': {'type': 'object'}
+ },
+ 'required': ['href', 'ttl', 'age', 'body']
+ }
+}
+
+list_messages = {
+ 'status_code': [200, 204],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'links': list_messages_links,
+ 'messages': list_messages_response
+ }
+ },
+ 'required': ['links', 'messages']
+}
+
+single_message = {
+ 'type': 'object',
+ 'properties': {
+ 'href': {'type': 'string'},
+ 'ttl': message_ttl,
+ 'age': age,
+ 'body': {'type': 'object'}
+ },
+ 'required': ['href', 'ttl', 'age', 'body']
+}
+
+get_single_message = {
+ 'status_code': [200],
+ 'response_body': single_message
+}
+
+get_multiple_messages = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'array',
+ 'items': single_message,
+ 'minItems': 1
+ }
+}
+
+messages_claimed = {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'ttl': message_ttl,
+ 'age': {'type': 'number'},
+ 'body': {'type': 'object'}
+ },
+ 'required': ['href', 'ttl', 'age', 'body']
+}
+
+claim_messages = {
+ 'status_code': [201, 204],
+ 'response_body': {
+ 'type': 'array',
+ 'items': messages_claimed,
+ 'minItems': 1
+ }
+}
+
+claim_ttl = {
+ 'type': 'number',
+ 'minimum': 1
+}
+
+query_claim = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'age': {'type': 'number'},
+ 'ttl': claim_ttl,
+ 'messages': {
+ 'type': 'array',
+ 'minItems': 1
+ }
+ },
+ 'required': ['ttl', 'age', 'messages']
+ }
+}
diff --git a/tempest/auth.py b/tempest/auth.py
index 9c51edb..830dca9 100644
--- a/tempest/auth.py
+++ b/tempest/auth.py
@@ -213,7 +213,7 @@
# build authenticated request
# returns new request, it does not touch the original values
_headers = copy.deepcopy(headers) if headers is not None else {}
- _headers['X-Auth-Token'] = token
+ _headers['X-Auth-Token'] = str(token)
if url is None or url == "":
_url = base_url
else:
@@ -223,7 +223,7 @@
parts[2] = re.sub("/{2,}", "/", parts[2])
_url = urlparse.urlunparse(parts)
# no change to method or body
- return _url, _headers, body
+ return str(_url), _headers, body
def _auth_client(self):
raise NotImplementedError
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 6aa98c4..ba94c82 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -83,20 +83,27 @@
return self.cmd_with_auth(
'cinder', action, flags, params, admin, fail_ok)
+ def swift(self, action, flags='', params='', admin=True, fail_ok=False):
+ """Executes swift command for the given action."""
+ flags += ' --os-endpoint-type %s' % CONF.object_storage.endpoint_type
+ return self.cmd_with_auth(
+ 'swift', action, flags, params, admin, fail_ok)
+
def neutron(self, action, flags='', params='', admin=True, fail_ok=False):
"""Executes neutron command for the given action."""
flags += ' --endpoint-type %s' % CONF.network.endpoint_type
return self.cmd_with_auth(
'neutron', action, flags, params, admin, fail_ok)
- def sahara(self, action, flags='', params='', admin=True, fail_ok=False):
+ def sahara(self, action, flags='', params='', admin=True,
+ fail_ok=False, merge_stderr=True):
"""Executes sahara command for the given action."""
flags += ' --endpoint-type %s' % CONF.data_processing.endpoint_type
return self.cmd_with_auth(
- 'sahara', action, flags, params, admin, fail_ok)
+ 'sahara', action, flags, params, admin, fail_ok, merge_stderr)
def cmd_with_auth(self, cmd, action, flags='', params='',
- admin=True, fail_ok=False):
+ admin=True, fail_ok=False, merge_stderr=False):
"""Executes given command with auth attributes appended."""
# TODO(jogo) make admin=False work
creds = ('--os-username %s --os-tenant-name %s --os-password %s '
@@ -106,7 +113,7 @@
CONF.identity.admin_password,
CONF.identity.uri))
flags = creds + ' ' + flags
- return self.cmd(cmd, action, flags, params, fail_ok)
+ return self.cmd(cmd, action, flags, params, fail_ok, merge_stderr)
def cmd(self, cmd, action, flags='', params='', fail_ok=False,
merge_stderr=False):
@@ -114,7 +121,7 @@
cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
flags, action, params])
LOG.info("running: '%s'" % cmd)
- cmd = shlex.split(cmd)
+ cmd = shlex.split(cmd.encode('utf-8'))
result = ''
result_err = ''
stdout = subprocess.PIPE
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 723333b..946b89e 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -50,13 +50,20 @@
self.assertTableStruct(roles, ['Name', 'Value'])
def test_cinder_backup_list(self):
- self.cinder('backup-list')
+ backup_list = self.parser.listing(self.cinder('backup-list'))
+ self.assertTableStruct(backup_list, ['ID', 'Volume ID', 'Status',
+ 'Name', 'Size', 'Object Count',
+ 'Container'])
def test_cinder_extra_specs_list(self):
- self.cinder('extra-specs-list')
+ extra_specs_list = self.parser.listing(self.cinder('extra-specs-list'))
+ self.assertTableStruct(extra_specs_list, ['ID', 'Name', 'extra_specs'])
def test_cinder_volumes_list(self):
- self.cinder('list')
+ list = self.parser.listing(self.cinder('list'))
+ self.assertTableStruct(list, ['ID', 'Status', 'Name', 'Size',
+ 'Volume Type', 'Bootable',
+ 'Attached to'])
self.cinder('list', params='--all-tenants 1')
self.cinder('list', params='--all-tenants 0')
self.assertRaises(subprocess.CalledProcessError,
@@ -85,39 +92,60 @@
self.assertTableStruct(roles, ['Property', 'Value'])
def test_cinder_rate_limits(self):
- self.cinder('rate-limits')
+ rate_limits = self.parser.listing(self.cinder('rate-limits'))
+ self.assertTableStruct(rate_limits, ['Verb', 'URI', 'Value', 'Remain',
+ 'Unit', 'Next_Available'])
@testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
'Volume snapshot not available.')
def test_cinder_snapshot_list(self):
- self.cinder('snapshot-list')
+ snapshot_list = self.parser.listing(self.cinder('snapshot-list'))
+ self.assertTableStruct(snapshot_list, ['ID', 'Volume ID', 'Status',
+ 'Name', 'Size'])
def test_cinder_type_list(self):
- self.cinder('type-list')
+ type_list = self.parser.listing(self.cinder('type-list'))
+ self.assertTableStruct(type_list, ['ID', 'Name'])
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_cinder_credentials(self):
- self.cinder('credentials')
+ credentials = self.parser.listing(self.cinder('credentials'))
+ self.assertTableStruct(credentials, ['User Credentials', 'Value'])
def test_cinder_availability_zone_list(self):
- self.cinder('availability-zone-list')
+ zone_list = self.parser.listing(self.cinder('availability-zone-list'))
+ self.assertTableStruct(zone_list, ['Name', 'Status'])
def test_cinder_endpoints(self):
- self.cinder('endpoints')
+ endpoints = self.parser.listing(self.cinder('endpoints'))
+ self.assertTableStruct(endpoints, ['nova', 'Value'])
def test_cinder_service_list(self):
- self.cinder('service-list')
+ service_list = self.parser.listing(self.cinder('service-list'))
+ self.assertTableStruct(service_list, ['Binary', 'Host', 'Zone',
+ 'Status', 'State', 'Updated_at',
+ 'Disabled Reason'])
def test_cinder_transfer_list(self):
- self.cinder('transfer-list')
+ transfer_list = self.parser.listing(self.cinder('transfer-list'))
+ self.assertTableStruct(transfer_list, ['ID', 'Volume ID', 'Name'])
def test_cinder_bash_completion(self):
self.cinder('bash-completion')
+ def test_cinder_qos_list(self):
+ qos_list = self.parser.listing(self.cinder('qos-list'))
+ self.assertTableStruct(qos_list, ['ID', 'Name', 'Consumer', 'specs'])
+
+ def test_cinder_encryption_type_list(self):
+ encrypt_list = self.parser.listing(self.cinder('encryption-type-list'))
+ self.assertTableStruct(encrypt_list, ['Volume Type ID', 'Provider',
+ 'Cipher', 'Key Size',
+ 'Control Location'])
+
def test_admin_help(self):
help_text = self.cinder('help')
lines = help_text.split('\n')
@@ -136,7 +164,7 @@
'quota-show', 'type-list', 'snapshot-list'))
self.assertFalse(wanted_commands - commands)
- # Optional arguments:
+ # Optional arguments:
def test_cinder_version(self):
self.cinder('', flags='--version')
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 1efbede..dda65c1 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -117,6 +117,11 @@
def test_admin_bashcompletion(self):
self.keystone('bash-completion')
+ def test_admin_ec2_credentials_list(self):
+ creds = self.keystone('ec2-credentials-list')
+ creds = self.parser.listing(creds)
+ self.assertTableStruct(creds, ['tenant', 'access', 'secret'])
+
# Optional arguments:
def test_admin_version(self):
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index c1d58b5..49d079e 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -49,7 +49,8 @@
@test.attr(type='smoke')
def test_neutron_net_list(self):
- self.neutron('net-list')
+ net_list = self.parser.listing(self.neutron('net-list'))
+ self.assertTableStruct(net_list, ['id', 'name', 'subnets'])
@test.attr(type='smoke')
def test_neutron_ext_list(self):
@@ -111,11 +112,14 @@
@test.attr(type='smoke')
@test.requires_ext(extension='external-net', service='network')
def test_neutron_net_external_list(self):
- self.neutron('net-external-list')
+ net_ext_list = self.parser.listing(self.neutron('net-external-list'))
+ self.assertTableStruct(net_ext_list, ['id', 'name', 'subnets'])
@test.attr(type='smoke')
def test_neutron_port_list(self):
- self.neutron('port-list')
+ port_list = self.parser.listing(self.neutron('port-list'))
+ self.assertTableStruct(port_list, ['id', 'name', 'mac_address',
+ 'fixed_ips'])
@test.attr(type='smoke')
@test.requires_ext(extension='quotas', service='network')
@@ -125,7 +129,9 @@
@test.attr(type='smoke')
@test.requires_ext(extension='router', service='network')
def test_neutron_router_list(self):
- self.neutron('router-list')
+ router_list = self.parser.listing(self.neutron('router-list'))
+ self.assertTableStruct(router_list, ['id', 'name',
+ 'external_gateway_info'])
@test.attr(type='smoke')
@test.requires_ext(extension='security-group', service='network')
@@ -136,11 +142,18 @@
@test.attr(type='smoke')
@test.requires_ext(extension='security-group', service='network')
def test_neutron_security_group_rule_list(self):
- self.neutron('security-group-rule-list')
+ security_grp = self.parser.listing(self.neutron
+ ('security-group-rule-list'))
+ self.assertTableStruct(security_grp, ['id', 'security_group',
+ 'direction', 'protocol',
+ 'remote_ip_prefix',
+ 'remote_group'])
@test.attr(type='smoke')
def test_neutron_subnet_list(self):
- self.neutron('subnet-list')
+ subnet_list = self.parser.listing(self.neutron('subnet-list'))
+ self.assertTableStruct(subnet_list, ['id', 'name', 'cidr',
+ 'allocation_pools'])
@test.attr(type='smoke')
def test_neutron_help(self):
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index a3787ab..1c1ddf1 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -145,6 +145,9 @@
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
+ def test_admin_server_group_list(self):
+ self.nova('server-group-list')
+
def test_admin_servce_list(self):
self.nova('service-list')
diff --git a/tempest/cli/simple_read_only/test_sahara.py b/tempest/cli/simple_read_only/test_sahara.py
index 36cc324..f00dcae 100644
--- a/tempest/cli/simple_read_only/test_sahara.py
+++ b/tempest/cli/simple_read_only/test_sahara.py
@@ -12,8 +12,8 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
import logging
+import re
import subprocess
from tempest import cli
@@ -138,3 +138,30 @@
'cluster_id',
'status'
])
+
+ def test_sahara_bash_completion(self):
+ self.sahara('bash-completion')
+
+ # Optional arguments
+ def test_sahara_help(self):
+ help_text = self.sahara('help')
+ lines = help_text.split('\n')
+ self.assertFirstLineStartsWith(lines, 'usage: sahara')
+
+ 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(('cluster-create', 'data-source-create',
+ 'image-unregister', 'job-binary-create',
+ 'plugin-list', 'job-binary-create', 'help'))
+ self.assertFalse(wanted_commands - commands)
+
+ def test_sahara_version(self):
+ version = self.sahara('', flags='--version')
+ self.assertTrue(re.search('[0-9.]+', version))
diff --git a/tempest/cli/simple_read_only/test_swift.py b/tempest/cli/simple_read_only/test_swift.py
new file mode 100644
index 0000000..6d6caa7
--- /dev/null
+++ b/tempest/cli/simple_read_only/test_swift.py
@@ -0,0 +1,95 @@
+# Copyright 2014 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 re
+import subprocess
+
+import tempest.cli
+from tempest import config
+
+CONF = config.CONF
+
+
+class SimpleReadOnlySwiftClientTest(tempest.cli.ClientTestBase):
+ """Basic, read-only tests for Swift 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.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ if not CONF.service_available.swift:
+ msg = ("%s skipped as Swift is not available" % cls.__name__)
+ raise cls.skipException(msg)
+ super(SimpleReadOnlySwiftClientTest, cls).setUpClass()
+
+ def test_swift_fake_action(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ self.swift,
+ 'this-does-not-exist')
+
+ def test_swift_list(self):
+ self.swift('list')
+
+ def test_swift_stat(self):
+ output = self.swift('stat')
+ entries = ['Account', 'Containers', 'Objects', 'Bytes', 'Content-Type',
+ 'X-Timestamp', 'X-Trans-Id']
+ for entry in entries:
+ self.assertTrue(entry in output)
+
+ def test_swift_capabilities(self):
+ output = self.swift('capabilities')
+ entries = ['account_listing_limit', 'container_listing_limit',
+ 'max_file_size', 'Additional middleware']
+ for entry in entries:
+ self.assertTrue(entry in output)
+
+ def test_swift_help(self):
+ help_text = self.swift('', flags='--help')
+ lines = help_text.split('\n')
+ self.assertFirstLineStartsWith(lines, 'Usage: swift')
+
+ commands = []
+ cmds_start = lines.index('Positional arguments:')
+ cmds_end = lines.index('Examples:')
+ 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(('stat', 'list', 'delete',
+ 'download', 'post', 'upload'))
+ self.assertFalse(wanted_commands - commands)
+
+ # Optional arguments:
+
+ def test_swift_version(self):
+ self.swift('', flags='--version')
+
+ def test_swift_debug_list(self):
+ self.swift('list', flags='--debug')
+
+ def test_swift_retries_list(self):
+ self.swift('list', flags='--retries 3')
+
+ def test_swift_region_list(self):
+ region = CONF.object_storage.region
+ if not region:
+ region = CONF.identity.region
+ self.swift('list', flags='--os-region-name ' + region)
diff --git a/tempest/clients.py b/tempest/clients.py
index 7745d54..4e2205e 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -50,6 +50,7 @@
from tempest.services.compute.json.limits_client import LimitsClientJSON
from tempest.services.compute.json.migrations_client import \
MigrationsClientJSON
+from tempest.services.compute.json.quotas_client import QuotaClassesClientJSON
from tempest.services.compute.json.quotas_client import QuotasClientJSON
from tempest.services.compute.json.security_groups_client import \
SecurityGroupsClientJSON
@@ -105,6 +106,7 @@
InterfacesClientXML
from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML
from tempest.services.compute.xml.limits_client import LimitsClientXML
+from tempest.services.compute.xml.quotas_client import QuotaClassesClientXML
from tempest.services.compute.xml.quotas_client import QuotasClientXML
from tempest.services.compute.xml.security_groups_client \
import SecurityGroupsClientXML
@@ -129,6 +131,7 @@
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.region_client import RegionClientJSON
from tempest.services.identity.v3.json.service_client import \
ServiceClientJSON
from tempest.services.identity.v3.xml.credentials_client import \
@@ -138,6 +141,7 @@
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.region_client import RegionClientXML
from tempest.services.identity.v3.xml.service_client import \
ServiceClientXML
from tempest.services.identity.xml.identity_client import IdentityClientXML
@@ -218,6 +222,8 @@
self.images_client = ImagesClientXML(self.auth_provider)
self.keypairs_client = KeyPairsClientXML(self.auth_provider)
self.quotas_client = QuotasClientXML(self.auth_provider)
+ self.quota_classes_client = QuotaClassesClientXML(
+ self.auth_provider)
self.flavors_client = FlavorsClientXML(self.auth_provider)
self.extensions_client = ExtensionsClientXML(self.auth_provider)
self.volumes_extensions_client = VolumesExtensionsClientXML(
@@ -248,6 +254,7 @@
self.tenant_usages_client = TenantUsagesClientXML(
self.auth_provider)
self.policy_client = PolicyClientXML(self.auth_provider)
+ self.region_client = RegionClientXML(self.auth_provider)
self.hosts_client = HostsClientXML(self.auth_provider)
self.hypervisor_client = HypervisorClientXML(self.auth_provider)
self.network_client = NetworkClientXML(self.auth_provider)
@@ -285,6 +292,8 @@
self.keypairs_v3_client = KeyPairsV3ClientJSON(
self.auth_provider)
self.quotas_client = QuotasClientJSON(self.auth_provider)
+ self.quota_classes_client = QuotaClassesClientJSON(
+ self.auth_provider)
self.quotas_v3_client = QuotasV3ClientJSON(self.auth_provider)
self.flavors_client = FlavorsClientJSON(self.auth_provider)
self.flavors_v3_client = FlavorsV3ClientJSON(self.auth_provider)
@@ -334,6 +343,7 @@
self.migrations_v3_client = MigrationsV3ClientJSON(
self.auth_provider)
self.policy_client = PolicyClientJSON(self.auth_provider)
+ self.region_client = RegionClientJSON(self.auth_provider)
self.hosts_client = HostsClientJSON(self.auth_provider)
self.hypervisor_v3_client = HypervisorV3ClientJSON(
self.auth_provider)
@@ -452,6 +462,7 @@
HEATCLIENT_VERSION = '1'
IRONICCLIENT_VERSION = '1'
SAHARACLIENT_VERSION = '1.1'
+ CEILOMETERCLIENT_VERSION = '2'
def __init__(self, credentials):
# FIXME(andreaf) Auth provider for client_type 'official' is
@@ -472,6 +483,8 @@
credentials)
self.data_processing_client = self._get_data_processing_client(
credentials)
+ self.ceilometer_client = self._get_ceilometer_client(
+ credentials)
def _get_roles(self):
admin_credentials = auth.get_default_credentials('identity_admin')
@@ -692,3 +705,34 @@
auth_url=auth_url)
return client
+
+ def _get_ceilometer_client(self, credentials):
+ if not CONF.service_available.ceilometer:
+ return None
+
+ import ceilometerclient.client
+
+ keystone = self._get_identity_client(credentials)
+ region = CONF.identity.region
+
+ endpoint_type = CONF.telemetry.endpoint_type
+ service_type = CONF.telemetry.catalog_type
+ auth_url = CONF.identity.uri
+
+ try:
+ keystone.service_catalog.url_for(
+ attr='region',
+ filter_value=region,
+ service_type=service_type,
+ endpoint_type=endpoint_type)
+ except keystoneclient.exceptions.EndpointNotFound:
+ return None
+ else:
+ return ceilometerclient.client.get_client(
+ self.CEILOMETERCLIENT_VERSION,
+ os_username=credentials.username,
+ os_password=credentials.password,
+ os_tenant_name=credentials.tenant_name,
+ os_auth_url=auth_url,
+ os_service_type=service_type,
+ os_endpoint_type=endpoint_type)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 20ee63e..0b72b1c 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -35,6 +35,7 @@
from tempest.services.image.v2.json import image_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
+from tempest.services.volume.json import volumes_client
OPTS = {}
USERS = {}
@@ -60,6 +61,7 @@
self.containers = container_client.ContainerClient(_auth)
self.images = image_client.ImageClientV2JSON(_auth)
self.flavors = flavors_client.FlavorsClientJSON(_auth)
+ self.volumes = volumes_client.VolumesClientJSON(_auth)
def load_resources(fname):
@@ -167,7 +169,7 @@
def collect_users(users):
global USERS
- LOG.info("Creating users")
+ LOG.info("Collecting users")
admin = keystone_admin()
for u in users:
tenant = admin.identity.get_tenant_by_name(u['tenant'])
@@ -190,6 +192,9 @@
self.check_users()
self.check_objects()
self.check_servers()
+ # TODO(sdague): Volumes not yet working, bring it back once the
+ # code is self testing.
+ # self.check_volumes()
def check_users(self):
"""Check that the users we expect to exist, do.
@@ -197,6 +202,7 @@
We don't use the resource list for this because we need to validate
that things like tenantId didn't drift across versions.
"""
+ LOG.info("checking users")
for name, user in self.users.iteritems():
client = keystone_admin()
_, found = client.identity.get_user(user['id'])
@@ -212,6 +218,7 @@
def check_objects(self):
"""Check that the objects created are still there."""
+ LOG.info("checking objects")
for obj in self.res['objects']:
client = client_for_user(obj['owner'])
r, contents = client.objects.get_object(
@@ -221,6 +228,7 @@
def check_servers(self):
"""Check that the servers are still up and running."""
+ LOG.info("checking servers")
for server in self.res['servers']:
client = client_for_user(server['owner'])
found = _get_server_by_name(client, server['name'])
@@ -235,6 +243,22 @@
"Server %s is not pingable at %s" % (
server['name'], addr))
+ def check_volumes(self):
+ """Check that the volumes are still there and attached."""
+ LOG.info("checking volumes")
+ for volume in self.res['volumes']:
+ client = client_for_user(volume['owner'])
+ found = _get_volume_by_name(client, volume['name'])
+ self.assertIsNotNone(
+ found,
+ "Couldn't find expected volume %s" % volume['name'])
+
+ # Verify that a volume's attachment retrieved
+ server_id = _get_server_by_name(client, volume['server'])['id']
+ attachment = self.client.get_attachment_from_volume(volume)
+ self.assertEqual(volume['id'], attachment['volume_id'])
+ self.assertEqual(server_id, attachment['server_id'])
+
#######################
#
@@ -266,6 +290,12 @@
#######################
+def _resolve_image(image, imgtype):
+ name = image[imgtype]
+ fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
+ return name, fname
+
+
def create_images(images):
for image in images:
client = client_for_user(image['owner'])
@@ -279,20 +309,23 @@
# special handling for 3 part image
extras = {}
if image['format'] == 'ami':
+ name, fname = _resolve_image(image, 'aki')
r, aki = client.images.create_image(
- 'javelin_' + image['aki'], 'aki', 'aki')
- client.images.store_image(aki.get('id'), open(image['aki'], 'r'))
+ 'javelin_' + name, 'aki', 'aki')
+ client.images.store_image(aki.get('id'), open(fname, 'r'))
extras['kernel_id'] = aki.get('id')
+ name, fname = _resolve_image(image, 'ari')
r, ari = client.images.create_image(
- 'javelin_' + image['ari'], 'ari', 'ari')
- client.images.store_image(ari.get('id'), open(image['ari'], 'r'))
+ 'javelin_' + name, 'ari', 'ari')
+ client.images.store_image(ari.get('id'), open(fname, 'r'))
extras['ramdisk_id'] = ari.get('id')
+ _, fname = _resolve_image(image, 'file')
r, body = client.images.create_image(
image['name'], image['format'], image['format'], **extras)
image_id = body.get('id')
- client.images.store_image(image_id, open(image['file'], 'r'))
+ client.images.store_image(image_id, open(fname, 'r'))
#######################
@@ -339,6 +372,40 @@
#######################
#
+# VOLUMES
+#
+#######################
+
+def _get_volume_by_name(client, name):
+ r, body = client.volumes.list_volumes()
+ for volume in body['volumes']:
+ if name == volume['name']:
+ return volume
+ return None
+
+
+def create_volumes(volumes):
+ for volume in volumes:
+ client = client_for_user(volume['owner'])
+
+ # only create a volume if the name isn't here
+ r, body = client.volumes.list_volumes()
+ if any(item['name'] == volume['name'] for item in body):
+ continue
+
+ client.volumes.create_volume(volume['name'], volume['size'])
+
+
+def attach_volumes(volumes):
+ for volume in volumes:
+ client = client_for_user(volume['owner'])
+
+ server_id = _get_server_by_name(client, volume['server'])['id']
+ client.volumes.attach_volume(volume['name'], server_id)
+
+
+#######################
+#
# MAIN LOGIC
#
#######################
@@ -355,6 +422,10 @@
create_objects(RES['objects'])
create_images(RES['images'])
create_servers(RES['servers'])
+ # TODO(sdague): volumes definition doesn't work yet, bring it
+ # back once we're actually executing the code
+ # create_volumes(RES['volumes'])
+ # attach_volumes(RES['volumes'])
def get_options():
@@ -369,6 +440,11 @@
required=True,
metavar='resourcefile.yaml',
help='Resources definition yaml file')
+ parser.add_argument(
+ '-d', '--devstack-base',
+ required=True,
+ metavar='/opt/stack/old',
+ help='Devstack base directory for retrieving artifacts')
# auth bits, letting us also just source the devstack openrc
parser.add_argument('--os-username',
metavar='<auth-user-name>',
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
index f7cb8a9..3450e1f 100644
--- a/tempest/cmd/resources.yaml
+++ b/tempest/cmd/resources.yaml
@@ -27,14 +27,20 @@
images:
- name: javelin_cirros
owner: javelin
+ imgdir: files/images/cirros-0.3.2-x86_64-uec
file: cirros-0.3.2-x86_64-blank.img
format: ami
aki: cirros-0.3.2-x86_64-vmlinuz
ari: cirros-0.3.2-x86_64-initrd
volumes:
- - assegai:
- - owner: javelin
- - gb: 1
+ - name: assegai
+ server: peltast
+ owner: javelin
+ size: 1
+ - name: pifpouf
+ server: hoplite
+ owner: javelin
+ size: 2
servers:
- name: peltast
owner: javelin
diff --git a/tempest/stress/run_stress.py b/tempest/cmd/run_stress.py
similarity index 96%
rename from tempest/stress/run_stress.py
rename to tempest/cmd/run_stress.py
index c7c17c0..07f3f66 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -51,7 +51,7 @@
except Exception:
next
if 'stress' in attrs:
- if filter_attr is not None and not filter_attr in attrs:
+ if filter_attr is not None and filter_attr not in attrs:
continue
class_setup_per = getattr(test_func, "st_class_setup_per")
@@ -70,7 +70,29 @@
return tests
-def main(ns):
+parser = argparse.ArgumentParser(description='Run stress tests')
+parser.add_argument('-d', '--duration', default=300, type=int,
+ help="Duration of test in secs")
+parser.add_argument('-s', '--serial', action='store_true',
+ help="Trigger running tests serially")
+parser.add_argument('-S', '--stop', action='store_true',
+ default=False, help="Stop on first error")
+parser.add_argument('-n', '--number', type=int,
+ help="How often an action is executed for each process")
+group = parser.add_mutually_exclusive_group(required=True)
+group.add_argument('-a', '--all', action='store_true',
+ help="Execute all stress tests")
+parser.add_argument('-T', '--type',
+ help="Filters tests of a certain type (e.g. gate)")
+parser.add_argument('-i', '--call-inherited', action='store_true',
+ default=False,
+ help="Call also inherited function with stress attribute")
+group.add_argument('-t', "--tests", nargs='?',
+ help="Name of the file with test description")
+
+
+def main():
+ ns = parser.parse_args()
result = 0
if not ns.all:
tests = json.load(open(ns.tests, 'r'))
@@ -97,29 +119,9 @@
return result
-parser = argparse.ArgumentParser(description='Run stress tests')
-parser.add_argument('-d', '--duration', default=300, type=int,
- help="Duration of test in secs")
-parser.add_argument('-s', '--serial', action='store_true',
- help="Trigger running tests serially")
-parser.add_argument('-S', '--stop', action='store_true',
- default=False, help="Stop on first error")
-parser.add_argument('-n', '--number', type=int,
- help="How often an action is executed for each process")
-group = parser.add_mutually_exclusive_group(required=True)
-group.add_argument('-a', '--all', action='store_true',
- help="Execute all stress tests")
-parser.add_argument('-T', '--type',
- help="Filters tests of a certain type (e.g. gate)")
-parser.add_argument('-i', '--call-inherited', action='store_true',
- default=False,
- help="Call also inherited function with stress attribute")
-group.add_argument('-t', "--tests", nargs='?',
- help="Name of the file with test description")
-
if __name__ == "__main__":
try:
- sys.exit(main(parser.parse_args()))
+ sys.exit(main())
except Exception:
LOG.exception("Failure in the stress test framework")
sys.exit(1)
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 7b2e60b..0834cff 100755
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -21,7 +21,7 @@
import urlparse
import httplib2
-from six.moves import configparser
+from six import moves
from tempest import clients
from tempest import config
@@ -46,7 +46,7 @@
def change_option(option, group, value):
- config_parse = configparser.SafeConfigParser()
+ config_parse = moves.configparser.SafeConfigParser()
config_parse.optionxform = str
config_parse.readfp(CONF_FILE)
if not config_parse.has_section(group):
@@ -160,11 +160,10 @@
extensions_client = get_extension_client(os, service)
__, resp = extensions_client.list_extensions()
if isinstance(resp, dict):
- # Neutron's extension 'name' field has is not a single word (it has
- # spaces in the string) Since that can't be used for list option the
- # api_extension option in the network-feature-enabled group uses alias
- # instead of name.
- if service == 'neutron':
+ # For both Nova and Neutron we use the alias name rather than the
+ # 'name' field because the alias is considered to be the canonical
+ # name.
+ if service in ['nova', 'nova_v3', 'neutron']:
extensions = map(lambda x: x['alias'], resp['extensions'])
elif service == 'swift':
# Remove Swift general information from extensions list
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
index 6580c65..6583475 100644
--- a/tempest/common/commands.py
+++ b/tempest/common/commands.py
@@ -25,7 +25,7 @@
def sudo_cmd_call(cmd):
- args = shlex.split(cmd)
+ args = shlex.split(cmd.encode('utf-8'))
subprocess_args = {'stdout': subprocess.PIPE,
'stderr': subprocess.STDOUT}
proc = subprocess.Popen(['/usr/bin/sudo', '-n'] + args,
@@ -50,7 +50,7 @@
def iptables_raw(table):
- return sudo_cmd_call("iptables -v -S -t " + table)
+ return sudo_cmd_call("iptables --line-numbers -L -nv -t " + table)
def ip_ns_list():
@@ -75,3 +75,22 @@
def ovs_db_dump():
return sudo_cmd_call("ovsdb-client dump")
+
+
+def copy_file_to_host(file_from, dest, host, username, pkey):
+ dest = "%s@%s:%s" % (username, host, dest)
+ cmd = "scp -v -o UserKnownHostsFile=/dev/null " \
+ "-o StrictHostKeyChecking=no " \
+ "-i %(pkey)s %(file1)s %(dest)s" % {'pkey': pkey,
+ 'file1': file_from,
+ 'dest': dest}
+ args = shlex.split(cmd.encode('utf-8'))
+ subprocess_args = {'stdout': subprocess.PIPE,
+ 'stderr': subprocess.STDOUT}
+ proc = subprocess.Popen(args, **subprocess_args)
+ stdout, stderr = proc.communicate()
+ if proc.returncode != 0:
+ LOG.error(("Command {0} returned with exit status {1},"
+ "output {2}, error {3}").format(cmd, proc.returncode,
+ stdout, stderr))
+ return stdout
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index 9358851..55aca5a 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -160,6 +160,9 @@
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
+ if kwargs['headers']['Content-Type'] != 'application/json':
+ msg = "Only application/json content-type is supported."
+ raise exc.InvalidContentType(msg)
if 'body' in kwargs:
kwargs['body'] = json.dumps(kwargs['body'])
@@ -173,7 +176,8 @@
except ValueError:
LOG.error('Could not decode response body as JSON')
else:
- body = None
+ msg = "Only json/application content-type is supported."
+ raise exc.InvalidContentType(msg)
return resp, body
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index b4618ed..208f42f 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -57,7 +57,7 @@
def _create_tenant(self, name, description):
if self.tempest_client:
- resp, tenant = self.identity_admin_client.create_tenant(
+ _, tenant = self.identity_admin_client.create_tenant(
name=name, description=description)
else:
tenant = self.identity_admin_client.tenants.create(
@@ -67,7 +67,7 @@
def _get_tenant_by_name(self, name):
if self.tempest_client:
- resp, tenant = self.identity_admin_client.get_tenant_by_name(name)
+ _, tenant = self.identity_admin_client.get_tenant_by_name(name)
else:
tenants = self.identity_admin_client.tenants.list()
for ten in tenants:
@@ -80,10 +80,10 @@
def _create_user(self, username, password, tenant, email):
if self.tempest_client:
- resp, user = self.identity_admin_client.create_user(username,
- password,
- tenant['id'],
- email)
+ _, user = self.identity_admin_client.create_user(username,
+ password,
+ tenant['id'],
+ email)
else:
user = self.identity_admin_client.users.create(username, password,
email,
@@ -92,7 +92,7 @@
def _get_user(self, tenant, username):
if self.tempest_client:
- resp, user = self.identity_admin_client.get_user_by_username(
+ _, user = self.identity_admin_client.get_user_by_username(
tenant['id'],
username)
else:
@@ -101,17 +101,28 @@
def _list_roles(self):
if self.tempest_client:
- resp, roles = self.identity_admin_client.list_roles()
+ _, roles = self.identity_admin_client.list_roles()
else:
roles = self.identity_admin_client.roles.list()
return roles
- def _assign_user_role(self, tenant, user, role):
+ def _assign_user_role(self, tenant, user, role_name):
+ role = None
+ try:
+ roles = self._list_roles()
+ if self.tempest_client:
+ role = next(r for r in roles if r['name'] == role_name)
+ else:
+ role = next(r for r in roles if r.name == role_name)
+ except StopIteration:
+ msg = 'No "%s" role found' % role_name
+ raise exceptions.NotFound(msg)
if self.tempest_client:
- self.identity_admin_client.assign_user_role(tenant, user, role)
+ self.identity_admin_client.assign_user_role(tenant['id'],
+ user['id'], role['id'])
else:
- self.identity_admin_client.roles.add_user_role(user,
- role, tenant=tenant)
+ self.identity_admin_client.roles.add_user_role(user.id, role.id,
+ tenant.id)
def _delete_user(self, user):
if self.tempest_client:
@@ -149,22 +160,11 @@
email = data_utils.rand_name(root) + suffix + "@example.com"
user = self._create_user(username, self.password,
tenant, email)
+ # NOTE(andrey-mp): user needs this role to create containers in swift
+ swift_operator_role = CONF.object_storage.operator_role
+ self._assign_user_role(tenant, user, swift_operator_role)
if admin:
- role = None
- try:
- roles = self._list_roles()
- admin_role = CONF.identity.admin_role
- if self.tempest_client:
- role = next(r for r in roles if r['name'] == admin_role)
- else:
- role = next(r for r in roles if r.name == admin_role)
- except StopIteration:
- msg = "No admin role found"
- raise exceptions.NotFound(msg)
- if self.tempest_client:
- self._assign_user_role(tenant['id'], user['id'], role['id'])
- else:
- self._assign_user_role(tenant.id, user.id, role.id)
+ self._assign_user_role(tenant, user, CONF.identity.admin_role)
return self._get_credentials(user, tenant)
def _get_credentials(self, user, tenant):
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 3c527f5..9e0f4d3 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -18,6 +18,7 @@
import json
from lxml import etree
import re
+import string
import time
import jsonschema
@@ -191,17 +192,25 @@
"""
self._skip_path = False
- def expected_success(self, expected_code, read_code):
+ @classmethod
+ def expected_success(cls, expected_code, read_code):
assert_msg = ("This function only allowed to use for HTTP status"
"codes which explicitly defined in the RFC 2616. {0}"
" is not a defined Success Code!").format(expected_code)
- assert expected_code in HTTP_SUCCESS, assert_msg
+ if isinstance(expected_code, list):
+ for code in expected_code:
+ assert code in HTTP_SUCCESS, assert_msg
+ else:
+ assert expected_code in HTTP_SUCCESS, assert_msg
# NOTE(afazekas): the http status code above 400 is processed by
# the _error_checker method
- if read_code < 400 and read_code != expected_code:
- pattern = """Unexpected http success status code {0},
- The expected status code is {1}"""
+ if read_code < 400:
+ pattern = """Unexpected http success status code {0},
+ The expected status code is {1}"""
+ if ((not isinstance(expected_code, list) and
+ (read_code != expected_code)) or (isinstance(expected_code,
+ list) and (read_code not in expected_code))):
details = pattern.format(read_code, expected_code)
raise exceptions.InvalidHttpSuccessCode(details)
@@ -287,9 +296,11 @@
req_url,
secs,
str(req_headers),
- str(req_body)[:2048],
+ filter(lambda x: x in string.printable,
+ str(req_body)[:2048]),
str(resp),
- str(resp_body)[:2048]),
+ filter(lambda x: x in string.printable,
+ str(resp_body)[:2048])),
extra=extra)
def _parse_resp(self, body):
@@ -361,7 +372,7 @@
# The warning is normal for SHOULD/SHOULD NOT case
# Likely it will cause an error
- if not resp_body and resp.status >= 400:
+ if method != 'HEAD' and not resp_body and resp.status >= 400:
self.LOG.warning("status >= 400 response with empty body")
def _request(self, method, url, headers=None, body=None):
@@ -555,11 +566,7 @@
# declared in the V3 API and so we should be able to export this in
# the response schema. For now we'll ignore it.
if resp.status in HTTP_SUCCESS:
- response_code = schema['status_code']
- if resp.status not in response_code:
- msg = ("The status code(%s) is different than the expected "
- "one(%s)") % (resp.status, response_code)
- raise exceptions.InvalidHttpSuccessCode(msg)
+ cls.expected_success(schema['status_code'], resp.status)
# Check the body of a response
body_schema = schema.get('response_body')
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 95b6833..57a14a2 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -57,11 +57,6 @@
actual_hostname = self.exec_command("hostname").rstrip()
return expected_hostname == actual_hostname
- def get_files(self, path):
- # Return a list of comma separated files
- command = "ls -m " + path
- return self.exec_command(command).rstrip('\n').split(', ')
-
def get_ram_size_in_mb(self):
output = self.exec_command('free -m | grep Mem')
if output:
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d52ed7c..d8474a0 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -22,6 +22,16 @@
LOG = logging.getLogger(__name__)
+def _console_dump(client, server_id):
+ try:
+ resp, output = client.get_console_output(server_id, None)
+ LOG.debug("Console Output for Server %s:\n%s" % (
+ server_id, output))
+ except exceptions.NotFound:
+ LOG.debug("Server %s: doesn't have a console" % server_id)
+ pass
+
+
# NOTE(afazekas): This function needs to know a token and a subject.
def wait_for_server_status(client, server_id, status, ready_wait=True,
extra_timeout=0, raise_on_error=True):
@@ -71,7 +81,9 @@
'/'.join((old_status, str(old_task_state))),
'/'.join((server_status, str(task_state))),
time.time() - start_time)
+
if (server_status == 'ERROR') and raise_on_error:
+ _console_dump(client, server_id)
raise exceptions.BuildErrorException(server_id=server_id)
timed_out = int(time.time()) - start_time >= timeout
@@ -87,9 +99,11 @@
'timeout': timeout})
message += ' Current status: %s.' % server_status
message += ' Current task state: %s.' % task_state
+
caller = misc_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
+ _console_dump(client, server_id)
raise exceptions.TimeoutException(message)
old_status = server_status
old_task_state = task_state
diff --git a/tempest/config.py b/tempest/config.py
index 1049f67..0796d98 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -247,7 +247,11 @@
'for removing from a host. -1 never offload, 0 offload '
'when shelved. This time should be the same as the time '
'of nova.conf, and some tests will run for as long as the '
- 'time.')
+ 'time.'),
+ cfg.StrOpt('floating_ip_range',
+ default='10.0.0.0/29',
+ help='Unallocated floating IP range, which will be used to '
+ 'test the floating IP bulk feature for CRUD operation.')
]
compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
@@ -255,7 +259,7 @@
ComputeFeaturesGroup = [
cfg.BoolOpt('api_v3',
- default=True,
+ default=False,
help="If false, skip all nova v3 tests."),
cfg.BoolOpt('disk_config',
default=True,
@@ -263,11 +267,13 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled compute extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Each extension should be specified with alias name'),
cfg.ListOpt('api_v3_extensions',
default=['all'],
help='A list of enabled v3 extensions with a special entry all'
- ' which indicates every extension is enabled'),
+ ' which indicates every extension is enabled. '
+ 'Each extension should be specified with alias name'),
cfg.BoolOpt('change_password',
default=False,
help="Does the test environment support changing the admin "
@@ -304,7 +310,11 @@
cfg.BoolOpt('rdp_console',
default=False,
help='Enable RDP console. This configuration value should '
- 'be same as [nova.rdp]->enabled in nova.conf')
+ 'be same as [nova.rdp]->enabled in nova.conf'),
+ cfg.BoolOpt('rescue',
+ default=True,
+ help='Does the test environment support instance rescue '
+ 'mode?')
]
@@ -415,6 +425,10 @@
default=1,
help="Time in seconds between network operation status "
"checks."),
+ cfg.ListOpt('dns_servers',
+ default=["8.8.8.8", "8.8.4.4"],
+ help="List of dns servers whichs hould be used"
+ " for subnet creation")
]
network_feature_group = cfg.OptGroup(name='network-feature-enabled',
@@ -428,6 +442,12 @@
default=['all'],
help='A list of enabled network extensions with a special '
'entry all which indicates every extension is enabled'),
+ cfg.BoolOpt('ipv6_subnet_attributes',
+ default=False,
+ help="Allow the execution of IPv6 subnet tests that use "
+ "the extended IPv6 attributes ipv6_ra_mode "
+ "and ipv6_address_mode"
+ )
]
queuing_group = cfg.OptGroup(name='queuing',
@@ -441,6 +461,28 @@
default=20,
help='The maximum number of queue records per page when '
'listing queues'),
+ cfg.IntOpt('max_queue_metadata',
+ default=65536,
+ help='The maximum metadata size for a queue'),
+ cfg.IntOpt('max_messages_per_page',
+ default=20,
+ help='The maximum number of queue message per page when '
+ 'listing (or) posting messages'),
+ cfg.IntOpt('max_message_size',
+ default=262144,
+ help='The maximum size of a message body'),
+ cfg.IntOpt('max_messages_per_claim',
+ default=20,
+ help='The maximum number of messages per claim'),
+ cfg.IntOpt('max_message_ttl',
+ default=1209600,
+ help='The maximum ttl for a message'),
+ cfg.IntOpt('max_claim_ttl',
+ default=43200,
+ help='The maximum ttl for a claim'),
+ cfg.IntOpt('max_claim_grace',
+ default=43200,
+ help='The maximum grace period for a claim'),
]
volume_group = cfg.OptGroup(name='volume',
@@ -638,6 +680,10 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the telemetry service."),
+ cfg.BoolOpt('too_slow_to_test',
+ default=True,
+ help="This variable is used as flag to enable "
+ "notification tests")
]
@@ -896,6 +942,9 @@
cfg.BoolOpt('driver_enabled',
default=False,
help="Whether the Ironic nova-compute driver is enabled"),
+ cfg.StrOpt('driver',
+ default='fake',
+ help="Driver name which Ironic uses"),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'admin', 'internal',
@@ -906,14 +955,14 @@
default=300,
help="Timeout for Ironic node to completely provision"),
cfg.IntOpt('association_timeout',
- default=10,
+ default=30,
help="Timeout for association of Nova instance and Ironic "
"node"),
cfg.IntOpt('power_timeout',
- default=20,
+ default=60,
help="Timeout for Ironic power transitions."),
cfg.IntOpt('unprovision_timeout',
- default=20,
+ default=60,
help="Timeout for unprovisioning an Ironic node.")
]
@@ -1070,8 +1119,21 @@
class TempestConfigProxy(object):
_config = None
+ _extra_log_defaults = [
+ 'keystoneclient.session=INFO',
+ 'paramiko.transport=INFO',
+ 'requests.packages.urllib3.connectionpool=WARN'
+ ]
+
+ def _fix_log_levels(self):
+ """Tweak the oslo log defaults."""
+ for opt in logging.log_opts:
+ if opt.dest == 'default_log_levels':
+ opt.default.extend(self._extra_log_defaults)
+
def __getattr__(self, attr):
if not self._config:
+ self._fix_log_levels()
self._config = TempestConfigPrivate()
return getattr(self._config, attr)
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 183d422..93329bc 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -50,7 +50,7 @@
T104: Scenario tests require a services decorator
"""
- if 'tempest/scenario/test_' in filename:
+ if 'tempest/scenario/' in filename and '/test_' in filename:
if TEST_DEFINITION.match(physical_line):
if not SCENARIO_DECORATOR.match(previous_logical):
return (physical_line.find('def'),
@@ -105,9 +105,24 @@
"T107: service tag should not be in path")
+def no_official_client_manager_in_api_tests(physical_line, filename):
+ """Check that the OfficialClientManager isn't used in the api tests
+
+ The api tests should not use the official clients.
+
+ T108: Can not use OfficialClientManager in the API tests
+ """
+ if 'tempest/api' in filename:
+ if 'OfficialClientManager' in physical_line:
+ return (physical_line.find('OfficialClientManager'),
+ 'T108: OfficialClientManager can not be used in the api '
+ 'tests')
+
+
def factory(register):
register(import_no_clients_in_api)
register(scenario_tests_need_service_tags)
register(no_setupclass_for_unit_tests)
register(no_vi_headers)
register(service_tags_not_in_module_path)
+ register(no_official_client_manager_in_api_tests)
diff --git a/tempest/openstack/common/config/generator.py b/tempest/openstack/common/config/generator.py
index 8156cc5..664200e 100644
--- a/tempest/openstack/common/config/generator.py
+++ b/tempest/openstack/common/config/generator.py
@@ -150,7 +150,7 @@
def _is_in_group(opt, group):
- "Check if opt is in group."
+ """Check if opt is in group."""
for value in group._opts.values():
# NOTE(llu): Temporary workaround for bug #1262148, wait until
# newly released oslo.config support '==' operator.
@@ -159,7 +159,7 @@
return False
-def _guess_groups(opt, mod_obj):
+def _guess_groups(opt):
# is it in the DEFAULT group?
if _is_in_group(opt, cfg.CONF):
return 'DEFAULT'
@@ -193,7 +193,7 @@
ret = {}
for opt in opts:
- ret.setdefault(_guess_groups(opt, obj), []).append(opt)
+ ret.setdefault(_guess_groups(opt), []).append(opt)
return ret.items()
@@ -223,6 +223,8 @@
def _sanitize_default(name, value):
"""Set up a reasonably sensible default for pybasedir, my_ip and host."""
+ hostname = socket.gethostname()
+ fqdn = socket.getfqdn()
if value.startswith(sys.prefix):
# NOTE(jd) Don't use os.path.join, because it is likely to think the
# second part is an absolute pathname and therefore drop the first
@@ -234,8 +236,13 @@
return value.replace(BASEDIR, '')
elif value == _get_my_ip():
return '10.0.0.1'
- elif value in (socket.gethostname(), socket.getfqdn()) and 'host' in name:
- return 'tempest'
+ elif value in (hostname, fqdn):
+ if 'host' in name:
+ return 'tempest'
+ elif value.endswith(hostname):
+ return value.replace(hostname, 'tempest')
+ elif value.endswith(fqdn):
+ return value.replace(fqdn, 'tempest')
elif value.strip() != value:
return '"%s"' % value
return value
@@ -246,7 +253,6 @@
if not opt_help:
sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
opt_help = ""
- opt_type = None
try:
opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
except (ValueError, AttributeError) as err:
diff --git a/tempest/openstack/common/gettextutils.py b/tempest/openstack/common/gettextutils.py
index 17f66f7..872d58e 100644
--- a/tempest/openstack/common/gettextutils.py
+++ b/tempest/openstack/common/gettextutils.py
@@ -23,7 +23,6 @@
"""
import copy
-import functools
import gettext
import locale
from logging import handlers
@@ -32,24 +31,115 @@
from babel import localedata
import six
-_localedir = os.environ.get('tempest'.upper() + '_LOCALEDIR')
-_t = gettext.translation('tempest', localedir=_localedir, fallback=True)
-
-# We use separate translation catalogs for each log level, so set up a
-# mapping between the log level name and the translator. The domain
-# for the log level is project_name + "-log-" + log_level so messages
-# for each level end up in their own catalog.
-_t_log_levels = dict(
- (level, gettext.translation('tempest' + '-log-' + level,
- localedir=_localedir,
- fallback=True))
- for level in ['info', 'warning', 'error', 'critical']
-)
-
_AVAILABLE_LANGUAGES = {}
+
+# FIXME(dhellmann): Remove this when moving to oslo.i18n.
USE_LAZY = False
+class TranslatorFactory(object):
+ """Create translator functions
+ """
+
+ def __init__(self, domain, localedir=None):
+ """Establish a set of translation functions for the domain.
+
+ :param domain: Name of translation domain,
+ specifying a message catalog.
+ :type domain: str
+ :param lazy: Delays translation until a message is emitted.
+ Defaults to False.
+ :type lazy: Boolean
+ :param localedir: Directory with translation catalogs.
+ :type localedir: str
+ """
+ self.domain = domain
+ if localedir is None:
+ localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
+ self.localedir = localedir
+
+ def _make_translation_func(self, domain=None):
+ """Return a new translation function ready for use.
+
+ Takes into account whether or not lazy translation is being
+ done.
+
+ The domain can be specified to override the default from the
+ factory, but the localedir from the factory is always used
+ because we assume the log-level translation catalogs are
+ installed in the same directory as the main application
+ catalog.
+
+ """
+ if domain is None:
+ domain = self.domain
+ t = gettext.translation(domain,
+ localedir=self.localedir,
+ fallback=True)
+ # Use the appropriate method of the translation object based
+ # on the python version.
+ m = t.gettext if six.PY3 else t.ugettext
+
+ def f(msg):
+ """oslo.i18n.gettextutils translation function."""
+ if USE_LAZY:
+ return Message(msg, domain=domain)
+ return m(msg)
+ return f
+
+ @property
+ def primary(self):
+ "The default translation function."
+ return self._make_translation_func()
+
+ def _make_log_translation_func(self, level):
+ return self._make_translation_func(self.domain + '-log-' + level)
+
+ @property
+ def log_info(self):
+ "Translate info-level log messages."
+ return self._make_log_translation_func('info')
+
+ @property
+ def log_warning(self):
+ "Translate warning-level log messages."
+ return self._make_log_translation_func('warning')
+
+ @property
+ def log_error(self):
+ "Translate error-level log messages."
+ return self._make_log_translation_func('error')
+
+ @property
+ def log_critical(self):
+ "Translate critical-level log messages."
+ return self._make_log_translation_func('critical')
+
+
+# NOTE(dhellmann): When this module moves out of the incubator into
+# oslo.i18n, these global variables can be moved to an integration
+# module within each application.
+
+# Create the global translation functions.
+_translators = TranslatorFactory('tempest')
+
+# The primary translation function using the well-known name "_"
+_ = _translators.primary
+
+# Translators for log levels.
+#
+# The abbreviated names are meant to reflect the usual use of a short
+# name like '_'. The "L" is for "log" and the other letter comes from
+# the level.
+_LI = _translators.log_info
+_LW = _translators.log_warning
+_LE = _translators.log_error
+_LC = _translators.log_critical
+
+# NOTE(dhellmann): End of globals that will move to the application's
+# integration module.
+
+
def enable_lazy():
"""Convenience function for configuring _() to use lazy gettext
@@ -62,38 +152,7 @@
USE_LAZY = True
-def _(msg):
- if USE_LAZY:
- return Message(msg, domain='tempest')
- else:
- if six.PY3:
- return _t.gettext(msg)
- return _t.ugettext(msg)
-
-
-def _log_translation(msg, level):
- """Build a single translation of a log message
- """
- if USE_LAZY:
- return Message(msg, domain='tempest' + '-log-' + level)
- else:
- translator = _t_log_levels[level]
- if six.PY3:
- return translator.gettext(msg)
- return translator.ugettext(msg)
-
-# Translators for log levels.
-#
-# The abbreviated names are meant to reflect the usual use of a short
-# name like '_'. The "L" is for "log" and the other letter comes from
-# the level.
-_LI = functools.partial(_log_translation, level='info')
-_LW = functools.partial(_log_translation, level='warning')
-_LE = functools.partial(_log_translation, level='error')
-_LC = functools.partial(_log_translation, level='critical')
-
-
-def install(domain, lazy=False):
+def install(domain):
"""Install a _() function using the given translation domain.
Given a translation domain, install a _() function using gettext's
@@ -104,43 +163,14 @@
a translation-domain-specific environment variable (e.g.
NOVA_LOCALEDIR).
+ Note that to enable lazy translation, enable_lazy must be
+ called.
+
:param domain: the translation domain
- :param lazy: indicates whether or not to install the lazy _() function.
- The lazy _() introduces a way to do deferred translation
- of messages by installing a _ that builds Message objects,
- instead of strings, which can then be lazily translated into
- any available locale.
"""
- if lazy:
- # NOTE(mrodden): Lazy gettext functionality.
- #
- # The following introduces a deferred way to do translations on
- # messages in OpenStack. We override the standard _() function
- # and % (format string) operation to build Message objects that can
- # later be translated when we have more information.
- def _lazy_gettext(msg):
- """Create and return a Message object.
-
- Lazy gettext function for a given domain, it is a factory method
- for a project/module to get a lazy gettext function for its own
- translation domain (i.e. nova, glance, cinder, etc.)
-
- Message encapsulates a string so that we can translate
- it later when needed.
- """
- return Message(msg, domain=domain)
-
- from six import moves
- moves.builtins.__dict__['_'] = _lazy_gettext
- else:
- localedir = '%s_LOCALEDIR' % domain.upper()
- if six.PY3:
- gettext.install(domain,
- localedir=os.environ.get(localedir))
- else:
- gettext.install(domain,
- localedir=os.environ.get(localedir),
- unicode=True)
+ from six import moves
+ tf = TranslatorFactory(domain)
+ moves.builtins.__dict__['_'] = tf.primary
class Message(six.text_type):
@@ -274,13 +304,14 @@
def __radd__(self, other):
return self.__add__(other)
- def __str__(self):
- # NOTE(luisg): Logging in python 2.6 tries to str() log records,
- # and it expects specifically a UnicodeError in order to proceed.
- msg = _('Message objects do not support str() because they may '
- 'contain non-ascii characters. '
- 'Please use unicode() or translate() instead.')
- raise UnicodeError(msg)
+ if six.PY2:
+ def __str__(self):
+ # NOTE(luisg): Logging in python 2.6 tries to str() log records,
+ # and it expects specifically a UnicodeError in order to proceed.
+ msg = _('Message objects do not support str() because they may '
+ 'contain non-ascii characters. '
+ 'Please use unicode() or translate() instead.')
+ raise UnicodeError(msg)
def get_available_languages(domain):
@@ -323,8 +354,8 @@
'zh_Hant_HK': 'zh_HK',
'zh_Hant': 'zh_TW',
'fil': 'tl_PH'}
- for (locale, alias) in six.iteritems(aliases):
- if locale in language_list and alias not in language_list:
+ for (locale_, alias) in six.iteritems(aliases):
+ if locale_ in language_list and alias not in language_list:
language_list.append(alias)
_AVAILABLE_LANGUAGES[domain] = language_list
diff --git a/tempest/openstack/common/importutils.py b/tempest/openstack/common/importutils.py
index 6c0d3b2..d5dd22f 100644
--- a/tempest/openstack/common/importutils.py
+++ b/tempest/openstack/common/importutils.py
@@ -24,10 +24,10 @@
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
+ __import__(mod_str)
try:
- __import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
- except (ValueError, AttributeError):
+ except AttributeError:
raise ImportError('Class %s cannot be found (%s)' %
(class_str,
traceback.format_exception(*sys.exc_info())))
diff --git a/tempest/openstack/common/jsonutils.py b/tempest/openstack/common/jsonutils.py
index 53c0ad4..cb83557 100644
--- a/tempest/openstack/common/jsonutils.py
+++ b/tempest/openstack/common/jsonutils.py
@@ -31,25 +31,29 @@
'''
+import codecs
import datetime
import functools
import inspect
import itertools
-import json
-try:
- import xmlrpclib
-except ImportError:
- # NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3
- # however the function and object call signatures
- # remained the same. This whole try/except block should
- # be removed and replaced with a call to six.moves once
- # six 1.4.2 is released. See http://bit.ly/1bqrVzu
- import xmlrpc.client as xmlrpclib
+import sys
+
+if sys.version_info < (2, 7):
+ # On Python <= 2.6, json module is not C boosted, so try to use
+ # simplejson module if available
+ try:
+ import simplejson as json
+ except ImportError:
+ import json
+else:
+ import json
import six
+import six.moves.xmlrpc_client as xmlrpclib
from tempest.openstack.common import gettextutils
from tempest.openstack.common import importutils
+from tempest.openstack.common import strutils
from tempest.openstack.common import timeutils
netaddr = importutils.try_import("netaddr")
@@ -164,12 +168,16 @@
return json.dumps(value, default=default, **kwargs)
-def loads(s):
- return json.loads(s)
+def dump(obj, fp, *args, **kwargs):
+ return json.dump(obj, fp, *args, **kwargs)
-def load(s):
- return json.load(s)
+def loads(s, encoding='utf-8', **kwargs):
+ return json.loads(strutils.safe_decode(s, encoding), **kwargs)
+
+
+def load(fp, encoding='utf-8', **kwargs):
+ return json.load(codecs.getreader(encoding)(fp), **kwargs)
try:
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 7bebfdb..44102c0 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""Openstack logging handler.
+"""OpenStack logging handler.
This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object
@@ -33,7 +33,6 @@
import logging.config
import logging.handlers
import os
-import re
import sys
import traceback
@@ -45,27 +44,13 @@
from tempest.openstack.common import importutils
from tempest.openstack.common import jsonutils
from tempest.openstack.common import local
+# NOTE(flaper87): Pls, remove when graduating this module
+# from the incubator.
+from tempest.openstack.common.strutils import mask_password # noqa
_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
-_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
-
-# NOTE(ldbragst): Let's build a list of regex objects using the list of
-# _SANITIZE_KEYS we already have. This way, we only have to add the new key
-# to the list of _SANITIZE_KEYS and we can generate regular expressions
-# for XML and JSON automatically.
-_SANITIZE_PATTERNS = []
-_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
- r'(<%(key)s>).*?(</%(key)s>)',
- r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
- r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])']
-
-for key in _SANITIZE_KEYS:
- for pattern in _FORMAT_PATTERNS:
- reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
- _SANITIZE_PATTERNS.append(reg_ex)
-
common_cli_opts = [
cfg.BoolOpt('debug',
@@ -84,14 +69,11 @@
cfg.StrOpt('log-config-append',
metavar='PATH',
deprecated_name='log-config',
- help='The name of logging configuration file. It does not '
- 'disable existing loggers, but just appends specified '
- 'logging configuration to any other existing logging '
- 'options. Please see the Python logging module '
- 'documentation for details on logging configuration '
- 'files.'),
+ help='The name of a logging configuration file. This file '
+ 'is appended to any existing logging configuration '
+ 'files. For details about logging configuration files, '
+ 'see the Python logging module documentation.'),
cfg.StrOpt('log-format',
- default=None,
metavar='FORMAT',
help='DEPRECATED. '
'A logging.Formatter log message format string which may '
@@ -103,7 +85,7 @@
default=_DEFAULT_LOG_DATE_FORMAT,
metavar='DATE_FORMAT',
help='Format string for %%(asctime)s in log records. '
- 'Default: %(default)s'),
+ 'Default: %(default)s .'),
cfg.StrOpt('log-file',
metavar='PATH',
deprecated_name='logfile',
@@ -112,67 +94,76 @@
cfg.StrOpt('log-dir',
deprecated_name='logdir',
help='(Optional) The base directory used for relative '
- '--log-file paths'),
+ '--log-file paths.'),
cfg.BoolOpt('use-syslog',
default=False,
- help='Use syslog for logging.'),
+ help='Use syslog for logging. '
+ 'Existing syslog format is DEPRECATED during I, '
+ 'and will change in J to honor RFC5424.'),
+ cfg.BoolOpt('use-syslog-rfc-format',
+ # TODO(bogdando) remove or use True after existing
+ # syslog format deprecation in J
+ default=False,
+ help='(Optional) Enables or disables syslog rfc5424 format '
+ 'for logging. If enabled, prefixes the MSG part of the '
+ 'syslog message with APP-NAME (RFC5424). The '
+ 'format without the APP-NAME is deprecated in I, '
+ 'and will be removed in J.'),
cfg.StrOpt('syslog-log-facility',
default='LOG_USER',
- help='syslog facility to receive log lines')
+ help='Syslog facility to receive log lines.')
]
generic_log_opts = [
cfg.BoolOpt('use_stderr',
default=True,
- help='Log output to standard error')
+ help='Log output to standard error.')
]
+DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'boto=WARN',
+ 'qpid=WARN', 'sqlalchemy=WARN', 'suds=INFO',
+ 'oslo.messaging=INFO', 'iso8601=WARN',
+ 'requests.packages.urllib3.connectionpool=WARN',
+ 'urllib3.connectionpool=WARN']
+
log_opts = [
cfg.StrOpt('logging_context_format_string',
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [%(request_id)s %(user_identity)s] '
'%(instance)s%(message)s',
- help='format string to use for log messages with context'),
+ help='Format string to use for log messages with context.'),
cfg.StrOpt('logging_default_format_string',
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [-] %(instance)s%(message)s',
- help='format string to use for log messages without context'),
+ help='Format string to use for log messages without context.'),
cfg.StrOpt('logging_debug_format_suffix',
default='%(funcName)s %(pathname)s:%(lineno)d',
- help='data to append to log format when level is DEBUG'),
+ help='Data to append to log format when level is DEBUG.'),
cfg.StrOpt('logging_exception_prefix',
default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s '
'%(instance)s',
- help='prefix each line of exception output with this format'),
+ help='Prefix each line of exception output with this format.'),
cfg.ListOpt('default_log_levels',
- default=[
- 'amqp=WARN',
- 'amqplib=WARN',
- 'boto=WARN',
- 'qpid=WARN',
- 'sqlalchemy=WARN',
- 'suds=INFO',
- 'iso8601=WARN',
- ],
- help='list of logger=LEVEL pairs'),
+ default=DEFAULT_LOG_LEVELS,
+ help='List of logger=LEVEL pairs.'),
cfg.BoolOpt('publish_errors',
default=False,
- help='publish error events'),
+ help='Enables or disables publication of error events.'),
cfg.BoolOpt('fatal_deprecations',
default=False,
- help='make deprecations fatal'),
+ help='Enables or disables fatal status of deprecations.'),
# NOTE(mikal): there are two options here because sometimes we are handed
# a full instance (and could include more information), and other times we
# are just handed a UUID for the instance.
cfg.StrOpt('instance_format',
default='[instance: %(uuid)s] ',
- help='If an instance is passed with the log message, format '
- 'it like this'),
+ help='The format for an instance that is passed with the log '
+ 'message.'),
cfg.StrOpt('instance_uuid_format',
default='[instance: %(uuid)s] ',
- help='If an instance UUID is passed with the log message, '
- 'format it like this'),
+ help='The format for an instance UUID that is passed with the '
+ 'log message.'),
]
CONF = cfg.CONF
@@ -231,40 +222,6 @@
return None
-def mask_password(message, secret="***"):
- """Replace password with 'secret' in message.
-
- :param message: The string which includes security information.
- :param secret: value with which to replace passwords.
- :returns: The unicode value of message with the password fields masked.
-
- For example:
-
- >>> mask_password("'adminPass' : 'aaaaa'")
- "'adminPass' : '***'"
- >>> mask_password("'admin_pass' : 'aaaaa'")
- "'admin_pass' : '***'"
- >>> mask_password('"password" : "aaaaa"')
- '"password" : "***"'
- >>> mask_password("'original_password' : 'aaaaa'")
- "'original_password' : '***'"
- >>> mask_password("u'original_password' : u'aaaaa'")
- "u'original_password' : u'***'"
- """
- message = six.text_type(message)
-
- # NOTE(ldbragst): Check to see if anything in message contains any key
- # specified in _SANITIZE_KEYS, if not then just return the message since
- # we don't have to mask any passwords.
- if not any(key in message for key in _SANITIZE_KEYS):
- return message
-
- secret = r'\g<1>' + secret + r'\g<2>'
- for pattern in _SANITIZE_PATTERNS:
- message = re.sub(pattern, secret, message)
- return message
-
-
class BaseLoggerAdapter(logging.LoggerAdapter):
def audit(self, msg, *args, **kwargs):
@@ -282,6 +239,11 @@
def logger(self):
if not self._logger:
self._logger = getLogger(self.name, self.version)
+ if six.PY3:
+ # In Python 3, the code fails because the 'manager' attribute
+ # cannot be found when using a LoggerAdapter as the
+ # underlying logger. Work around this issue.
+ self._logger.manager = self._logger.logger.manager
return self._logger
@@ -292,18 +254,39 @@
self.logger = logger
self.project = project_name
self.version = version_string
+ self._deprecated_messages_sent = dict()
@property
def handlers(self):
return self.logger.handlers
def deprecated(self, msg, *args, **kwargs):
+ """Call this method when a deprecated feature is used.
+
+ If the system is configured for fatal deprecations then the message
+ is logged at the 'critical' level and :class:`DeprecatedConfig` will
+ be raised.
+
+ Otherwise, the message will be logged (once) at the 'warn' level.
+
+ :raises: :class:`DeprecatedConfig` if the system is configured for
+ fatal deprecations.
+
+ """
stdmsg = _("Deprecated: %s") % msg
if CONF.fatal_deprecations:
self.critical(stdmsg, *args, **kwargs)
raise DeprecatedConfig(msg=stdmsg)
- else:
- self.warn(stdmsg, *args, **kwargs)
+
+ # Using a list because a tuple with dict can't be stored in a set.
+ sent_args = self._deprecated_messages_sent.setdefault(msg, list())
+
+ if args in sent_args:
+ # Already logged this message, so don't log it again.
+ return
+
+ sent_args.append(args)
+ self.warn(stdmsg, *args, **kwargs)
def process(self, msg, kwargs):
# NOTE(mrodden): catch any Message/other object and
@@ -324,7 +307,7 @@
extra.update(_dictify_context(context))
instance = kwargs.pop('instance', None)
- instance_uuid = (extra.get('instance_uuid', None) or
+ instance_uuid = (extra.get('instance_uuid') or
kwargs.pop('instance_uuid', None))
instance_extra = ''
if instance:
@@ -390,10 +373,10 @@
def _create_logging_excepthook(product_name):
def logging_excepthook(exc_type, value, tb):
- extra = {}
- if CONF.verbose:
- extra['exc_info'] = (exc_type, value, tb)
- getLogger(product_name).critical(str(value), **extra)
+ extra = {'exc_info': (exc_type, value, tb)}
+ getLogger(product_name).critical(
+ "".join(traceback.format_exception_only(exc_type, value)),
+ **extra)
return logging_excepthook
@@ -414,23 +397,31 @@
try:
logging.config.fileConfig(log_config_append,
disable_existing_loggers=False)
- except moves.configparser.Error as exc:
- raise LogConfigError(log_config_append, str(exc))
+ except (moves.configparser.Error, KeyError) as exc:
+ raise LogConfigError(log_config_append, six.text_type(exc))
-def setup(product_name):
+def setup(product_name, version='unknown'):
"""Setup logging."""
if CONF.log_config_append:
_load_log_config(CONF.log_config_append)
else:
- _setup_logging_from_conf()
+ _setup_logging_from_conf(product_name, version)
sys.excepthook = _create_logging_excepthook(product_name)
-def set_defaults(logging_context_format_string):
- cfg.set_defaults(log_opts,
- logging_context_format_string=
- logging_context_format_string)
+def set_defaults(logging_context_format_string,
+ default_log_levels=None):
+ # Just in case the caller is not setting the
+ # default_log_level. This is insurance because
+ # we introduced the default_log_level parameter
+ # later in a backwards in-compatible change
+ if default_log_levels is None:
+ default_log_levels = DEFAULT_LOG_LEVELS
+ cfg.set_defaults(
+ log_opts,
+ logging_context_format_string=logging_context_format_string,
+ default_log_levels=default_log_levels)
def _find_facility_from_conf():
@@ -457,15 +448,38 @@
return facility
-def _setup_logging_from_conf():
+class RFCSysLogHandler(logging.handlers.SysLogHandler):
+ def __init__(self, *args, **kwargs):
+ self.binary_name = _get_binary_name()
+ # Do not use super() unless type(logging.handlers.SysLogHandler)
+ # is 'type' (Python 2.7).
+ # Use old style calls, if the type is 'classobj' (Python 2.6)
+ logging.handlers.SysLogHandler.__init__(self, *args, **kwargs)
+
+ def format(self, record):
+ # Do not use super() unless type(logging.handlers.SysLogHandler)
+ # is 'type' (Python 2.7).
+ # Use old style calls, if the type is 'classobj' (Python 2.6)
+ msg = logging.handlers.SysLogHandler.format(self, record)
+ msg = self.binary_name + ' ' + msg
+ return msg
+
+
+def _setup_logging_from_conf(project, version):
log_root = getLogger(None).logger
for handler in log_root.handlers:
log_root.removeHandler(handler)
if CONF.use_syslog:
facility = _find_facility_from_conf()
- syslog = logging.handlers.SysLogHandler(address='/dev/log',
- facility=facility)
+ # TODO(bogdando) use the format provided by RFCSysLogHandler
+ # after existing syslog format deprecation in J
+ if CONF.use_syslog_rfc_format:
+ syslog = RFCSysLogHandler(address='/dev/log',
+ facility=facility)
+ else:
+ syslog = logging.handlers.SysLogHandler(address='/dev/log',
+ facility=facility)
log_root.addHandler(syslog)
logpath = _get_log_file_path()
@@ -484,9 +498,14 @@
log_root.addHandler(streamlog)
if CONF.publish_errors:
- handler = importutils.import_object(
- "tempest.openstack.common.log_handler.PublishErrorsHandler",
- logging.ERROR)
+ try:
+ handler = importutils.import_object(
+ "tempest.openstack.common.log_handler.PublishErrorsHandler",
+ logging.ERROR)
+ except ImportError:
+ handler = importutils.import_object(
+ "oslo.messaging.notify.log_handler.PublishErrorsHandler",
+ logging.ERROR)
log_root.addHandler(handler)
datefmt = CONF.log_date_format
@@ -499,7 +518,9 @@
log_root.info('Deprecated: log_format is now deprecated and will '
'be removed in the next release')
else:
- handler.setFormatter(ContextFormatter(datefmt=datefmt))
+ handler.setFormatter(ContextFormatter(project=project,
+ version=version,
+ datefmt=datefmt))
if CONF.debug:
log_root.setLevel(logging.DEBUG)
@@ -510,9 +531,15 @@
for pair in CONF.default_log_levels:
mod, _sep, level_name = pair.partition('=')
- level = logging.getLevelName(level_name)
logger = logging.getLogger(mod)
- logger.setLevel(level)
+ # NOTE(AAzza) in python2.6 Logger.setLevel doesn't convert string name
+ # to integer code.
+ if sys.version_info < (2, 7):
+ level = logging.getLevelName(level_name)
+ logger.setLevel(level)
+ else:
+ logger.setLevel(level_name)
+
_loggers = {}
@@ -543,7 +570,7 @@
self.level = level
def write(self, msg):
- self.logger.log(self.level, msg)
+ self.logger.log(self.level, msg.rstrip())
class ContextFormatter(logging.Formatter):
@@ -557,27 +584,64 @@
For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter
+ If available, uses the context value stored in TLS - local.store.context
+
"""
+ def __init__(self, *args, **kwargs):
+ """Initialize ContextFormatter instance
+
+ Takes additional keyword arguments which can be used in the message
+ format string.
+
+ :keyword project: project name
+ :type project: string
+ :keyword version: project version
+ :type version: string
+
+ """
+
+ self.project = kwargs.pop('project', 'unknown')
+ self.version = kwargs.pop('version', 'unknown')
+
+ logging.Formatter.__init__(self, *args, **kwargs)
+
def format(self, record):
"""Uses contextstring if request_id is set, otherwise default."""
- # NOTE(sdague): default the fancier formating params
+
+ # store project info
+ record.project = self.project
+ record.version = self.version
+
+ # store request info
+ context = getattr(local.store, 'context', None)
+ if context:
+ d = _dictify_context(context)
+ for k, v in d.items():
+ setattr(record, k, v)
+
+ # NOTE(sdague): default the fancier formatting params
# to an empty string so we don't throw an exception if
# they get used
- for key in ('instance', 'color'):
+ for key in ('instance', 'color', 'user_identity'):
if key not in record.__dict__:
record.__dict__[key] = ''
- if record.__dict__.get('request_id', None):
- self._fmt = CONF.logging_context_format_string
+ if record.__dict__.get('request_id'):
+ fmt = CONF.logging_context_format_string
else:
- self._fmt = CONF.logging_default_format_string
+ fmt = CONF.logging_default_format_string
if (record.levelno == logging.DEBUG and
CONF.logging_debug_format_suffix):
- self._fmt += " " + CONF.logging_debug_format_suffix
+ fmt += " " + CONF.logging_debug_format_suffix
- # Cache this on the record, Logger will respect our formated copy
+ if sys.version_info < (3, 2):
+ self._fmt = fmt
+ else:
+ self._style = logging.PercentStyle(fmt)
+ self._fmt = self._style._fmt
+ # Cache this on the record, Logger will respect our formatted copy
if record.exc_info:
record.exc_text = self.formatException(record.exc_info, record)
return logging.Formatter.format(self, record)
diff --git a/tempest/openstack/common/strutils.py b/tempest/openstack/common/strutils.py
new file mode 100644
index 0000000..605cc02
--- /dev/null
+++ b/tempest/openstack/common/strutils.py
@@ -0,0 +1,295 @@
+# Copyright 2011 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.
+
+"""
+System-level utilities and helper functions.
+"""
+
+import math
+import re
+import sys
+import unicodedata
+
+import six
+
+from tempest.openstack.common.gettextutils import _
+
+
+UNIT_PREFIX_EXPONENT = {
+ 'k': 1,
+ 'K': 1,
+ 'Ki': 1,
+ 'M': 2,
+ 'Mi': 2,
+ 'G': 3,
+ 'Gi': 3,
+ 'T': 4,
+ 'Ti': 4,
+}
+UNIT_SYSTEM_INFO = {
+ 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
+ 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
+}
+
+TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
+FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
+
+SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
+SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
+
+
+# NOTE(flaper87): The following 3 globals are used by `mask_password`
+_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
+
+# NOTE(ldbragst): Let's build a list of regex objects using the list of
+# _SANITIZE_KEYS we already have. This way, we only have to add the new key
+# to the list of _SANITIZE_KEYS and we can generate regular expressions
+# for XML and JSON automatically.
+_SANITIZE_PATTERNS = []
+_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
+ r'(<%(key)s>).*?(</%(key)s>)',
+ r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
+ r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
+ r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?[\'"])'
+ '.*?([\'"])',
+ r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)']
+
+for key in _SANITIZE_KEYS:
+ for pattern in _FORMAT_PATTERNS:
+ reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
+ _SANITIZE_PATTERNS.append(reg_ex)
+
+
+def int_from_bool_as_string(subject):
+ """Interpret a string as a boolean and return either 1 or 0.
+
+ Any string value in:
+
+ ('True', 'true', 'On', 'on', '1')
+
+ is interpreted as a boolean True.
+
+ Useful for JSON-decoded stuff and config file parsing
+ """
+ return bool_from_string(subject) and 1 or 0
+
+
+def bool_from_string(subject, strict=False, default=False):
+ """Interpret a string as a boolean.
+
+ A case-insensitive match is performed such that strings matching 't',
+ 'true', 'on', 'y', 'yes', or '1' are considered True and, when
+ `strict=False`, anything else returns the value specified by 'default'.
+
+ Useful for JSON-decoded stuff and config file parsing.
+
+ If `strict=True`, unrecognized values, including None, will raise a
+ ValueError which is useful when parsing values passed in from an API call.
+ Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
+ """
+ if not isinstance(subject, six.string_types):
+ subject = six.text_type(subject)
+
+ lowered = subject.strip().lower()
+
+ if lowered in TRUE_STRINGS:
+ return True
+ elif lowered in FALSE_STRINGS:
+ return False
+ elif strict:
+ acceptable = ', '.join(
+ "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
+ msg = _("Unrecognized value '%(val)s', acceptable values are:"
+ " %(acceptable)s") % {'val': subject,
+ 'acceptable': acceptable}
+ raise ValueError(msg)
+ else:
+ return default
+
+
+def safe_decode(text, incoming=None, errors='strict'):
+ """Decodes incoming text/bytes string using `incoming` if they're not
+ already unicode.
+
+ :param incoming: Text's current encoding
+ :param errors: Errors handling policy. See here for valid
+ values http://docs.python.org/2/library/codecs.html
+ :returns: text or a unicode `incoming` encoded
+ representation of it.
+ :raises TypeError: If text is not an instance of str
+ """
+ if not isinstance(text, (six.string_types, six.binary_type)):
+ raise TypeError("%s can't be decoded" % type(text))
+
+ if isinstance(text, six.text_type):
+ return text
+
+ if not incoming:
+ incoming = (sys.stdin.encoding or
+ sys.getdefaultencoding())
+
+ try:
+ return text.decode(incoming, errors)
+ except UnicodeDecodeError:
+ # Note(flaper87) If we get here, it means that
+ # sys.stdin.encoding / sys.getdefaultencoding
+ # didn't return a suitable encoding to decode
+ # text. This happens mostly when global LANG
+ # var is not set correctly and there's no
+ # default encoding. In this case, most likely
+ # python will use ASCII or ANSI encoders as
+ # default encodings but they won't be capable
+ # of decoding non-ASCII characters.
+ #
+ # Also, UTF-8 is being used since it's an ASCII
+ # extension.
+ return text.decode('utf-8', errors)
+
+
+def safe_encode(text, incoming=None,
+ encoding='utf-8', errors='strict'):
+ """Encodes incoming text/bytes string using `encoding`.
+
+ If incoming is not specified, text is expected to be encoded with
+ current python's default encoding. (`sys.getdefaultencoding`)
+
+ :param incoming: Text's current encoding
+ :param encoding: Expected encoding for text (Default UTF-8)
+ :param errors: Errors handling policy. See here for valid
+ values http://docs.python.org/2/library/codecs.html
+ :returns: text or a bytestring `encoding` encoded
+ representation of it.
+ :raises TypeError: If text is not an instance of str
+ """
+ if not isinstance(text, (six.string_types, six.binary_type)):
+ raise TypeError("%s can't be encoded" % type(text))
+
+ if not incoming:
+ incoming = (sys.stdin.encoding or
+ sys.getdefaultencoding())
+
+ if isinstance(text, six.text_type):
+ return text.encode(encoding, errors)
+ elif text and encoding != incoming:
+ # Decode text before encoding it with `encoding`
+ text = safe_decode(text, incoming, errors)
+ return text.encode(encoding, errors)
+ else:
+ return text
+
+
+def string_to_bytes(text, unit_system='IEC', return_int=False):
+ """Converts a string into an float representation of bytes.
+
+ The units supported for IEC ::
+
+ Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
+ KB, KiB, MB, MiB, GB, GiB, TB, TiB
+
+ The units supported for SI ::
+
+ kb(it), Mb(it), Gb(it), Tb(it)
+ kB, MB, GB, TB
+
+ Note that the SI unit system does not support capital letter 'K'
+
+ :param text: String input for bytes size conversion.
+ :param unit_system: Unit system for byte size conversion.
+ :param return_int: If True, returns integer representation of text
+ in bytes. (default: decimal)
+ :returns: Numerical representation of text in bytes.
+ :raises ValueError: If text has an invalid value.
+
+ """
+ try:
+ base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
+ except KeyError:
+ msg = _('Invalid unit system: "%s"') % unit_system
+ raise ValueError(msg)
+ match = reg_ex.match(text)
+ if match:
+ magnitude = float(match.group(1))
+ unit_prefix = match.group(2)
+ if match.group(3) in ['b', 'bit']:
+ magnitude /= 8
+ else:
+ msg = _('Invalid string format: %s') % text
+ raise ValueError(msg)
+ if not unit_prefix:
+ res = magnitude
+ else:
+ res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
+ if return_int:
+ return int(math.ceil(res))
+ return res
+
+
+def to_slug(value, incoming=None, errors="strict"):
+ """Normalize string.
+
+ Convert to lowercase, remove non-word characters, and convert spaces
+ to hyphens.
+
+ Inspired by Django's `slugify` filter.
+
+ :param value: Text to slugify
+ :param incoming: Text's current encoding
+ :param errors: Errors handling policy. See here for valid
+ values http://docs.python.org/2/library/codecs.html
+ :returns: slugified unicode representation of `value`
+ :raises TypeError: If text is not an instance of str
+ """
+ value = safe_decode(value, incoming, errors)
+ # NOTE(aababilov): no need to use safe_(encode|decode) here:
+ # encodings are always "ascii", error handling is always "ignore"
+ # and types are always known (first: unicode; second: str)
+ value = unicodedata.normalize("NFKD", value).encode(
+ "ascii", "ignore").decode("ascii")
+ value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
+ return SLUGIFY_HYPHENATE_RE.sub("-", value)
+
+
+def mask_password(message, secret="***"):
+ """Replace password with 'secret' in message.
+
+ :param message: The string which includes security information.
+ :param secret: value with which to replace passwords.
+ :returns: The unicode value of message with the password fields masked.
+
+ For example:
+
+ >>> mask_password("'adminPass' : 'aaaaa'")
+ "'adminPass' : '***'"
+ >>> mask_password("'admin_pass' : 'aaaaa'")
+ "'admin_pass' : '***'"
+ >>> mask_password('"password" : "aaaaa"')
+ '"password" : "***"'
+ >>> mask_password("'original_password' : 'aaaaa'")
+ "'original_password' : '***'"
+ >>> mask_password("u'original_password' : u'aaaaa'")
+ "u'original_password' : u'***'"
+ """
+ message = six.text_type(message)
+
+ # NOTE(ldbragst): Check to see if anything in message contains any key
+ # specified in _SANITIZE_KEYS, if not then just return the message since
+ # we don't have to mask any passwords.
+ if not any(key in message for key in _SANITIZE_KEYS):
+ return message
+
+ secret = r'\g<1>' + secret + r'\g<2>'
+ for pattern in _SANITIZE_PATTERNS:
+ message = re.sub(pattern, secret, message)
+ return message
diff --git a/tempest/openstack/common/timeutils.py b/tempest/openstack/common/timeutils.py
index d5ed81d..c48da95 100644
--- a/tempest/openstack/common/timeutils.py
+++ b/tempest/openstack/common/timeutils.py
@@ -114,7 +114,7 @@
def iso8601_from_timestamp(timestamp):
- """Returns a iso8601 formated date from timestamp."""
+ """Returns an iso8601 formatted date from timestamp."""
return isotime(datetime.datetime.utcfromtimestamp(timestamp))
@@ -134,7 +134,7 @@
def advance_time_delta(timedelta):
"""Advance overridden time using a datetime.timedelta."""
- assert(not utcnow.override_time is None)
+ assert utcnow.override_time is not None
try:
for dt in utcnow.override_time:
dt += timedelta
diff --git a/tempest/openstack/common/versionutils.py b/tempest/openstack/common/versionutils.py
new file mode 100644
index 0000000..131046e
--- /dev/null
+++ b/tempest/openstack/common/versionutils.py
@@ -0,0 +1,148 @@
+# Copyright (c) 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.
+
+"""
+Helpers for comparing version strings.
+"""
+
+import functools
+import pkg_resources
+
+from tempest.openstack.common.gettextutils import _
+from tempest.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+class deprecated(object):
+ """A decorator to mark callables as deprecated.
+
+ This decorator logs a deprecation message when the callable it decorates is
+ used. The message will include the release where the callable was
+ deprecated, the release where it may be removed and possibly an optional
+ replacement.
+
+ Examples:
+
+ 1. Specifying the required deprecated release
+
+ >>> @deprecated(as_of=deprecated.ICEHOUSE)
+ ... def a(): pass
+
+ 2. Specifying a replacement:
+
+ >>> @deprecated(as_of=deprecated.ICEHOUSE, in_favor_of='f()')
+ ... def b(): pass
+
+ 3. Specifying the release where the functionality may be removed:
+
+ >>> @deprecated(as_of=deprecated.ICEHOUSE, remove_in=+1)
+ ... def c(): pass
+
+ """
+
+ FOLSOM = 'F'
+ GRIZZLY = 'G'
+ HAVANA = 'H'
+ ICEHOUSE = 'I'
+
+ _RELEASES = {
+ 'F': 'Folsom',
+ 'G': 'Grizzly',
+ 'H': 'Havana',
+ 'I': 'Icehouse',
+ }
+
+ _deprecated_msg_with_alternative = _(
+ '%(what)s is deprecated as of %(as_of)s in favor of '
+ '%(in_favor_of)s and may be removed in %(remove_in)s.')
+
+ _deprecated_msg_no_alternative = _(
+ '%(what)s is deprecated as of %(as_of)s and may be '
+ 'removed in %(remove_in)s. It will not be superseded.')
+
+ def __init__(self, as_of, in_favor_of=None, remove_in=2, what=None):
+ """Initialize decorator
+
+ :param as_of: the release deprecating the callable. Constants
+ are define in this class for convenience.
+ :param in_favor_of: the replacement for the callable (optional)
+ :param remove_in: an integer specifying how many releases to wait
+ before removing (default: 2)
+ :param what: name of the thing being deprecated (default: the
+ callable's name)
+
+ """
+ self.as_of = as_of
+ self.in_favor_of = in_favor_of
+ self.remove_in = remove_in
+ self.what = what
+
+ def __call__(self, func):
+ if not self.what:
+ self.what = func.__name__ + '()'
+
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ msg, details = self._build_message()
+ LOG.deprecated(msg, details)
+ return func(*args, **kwargs)
+ return wrapped
+
+ def _get_safe_to_remove_release(self, release):
+ # TODO(dstanek): this method will have to be reimplemented once
+ # when we get to the X release because once we get to the Y
+ # release, what is Y+2?
+ new_release = chr(ord(release) + self.remove_in)
+ if new_release in self._RELEASES:
+ return self._RELEASES[new_release]
+ else:
+ return new_release
+
+ def _build_message(self):
+ details = dict(what=self.what,
+ as_of=self._RELEASES[self.as_of],
+ remove_in=self._get_safe_to_remove_release(self.as_of))
+
+ if self.in_favor_of:
+ details['in_favor_of'] = self.in_favor_of
+ msg = self._deprecated_msg_with_alternative
+ else:
+ msg = self._deprecated_msg_no_alternative
+ return msg, details
+
+
+def is_compatible(requested_version, current_version, same_major=True):
+ """Determine whether `requested_version` is satisfied by
+ `current_version`; in other words, `current_version` is >=
+ `requested_version`.
+
+ :param requested_version: version to check for compatibility
+ :param current_version: version to check against
+ :param same_major: if True, the major version must be identical between
+ `requested_version` and `current_version`. This is used when a
+ major-version difference indicates incompatibility between the two
+ versions. Since this is the common-case in practice, the default is
+ True.
+ :returns: True if compatible, False if not
+ """
+ requested_parts = pkg_resources.parse_version(requested_version)
+ current_parts = pkg_resources.parse_version(current_version)
+
+ if same_major and (requested_parts[0] != current_parts[0]):
+ return False
+
+ return current_parts >= requested_parts
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index f83a4b8..ca79325 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -21,6 +21,8 @@
import subprocess
import time
+from cinderclient import exceptions as cinder_exceptions
+import glanceclient
from heatclient import exc as heat_exceptions
import netaddr
from neutronclient.common import exceptions as exc
@@ -29,6 +31,7 @@
from tempest.api.network import common as net_common
from tempest import auth
from tempest import clients
+from tempest.common import debug
from tempest.common import isolated_creds
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
@@ -81,8 +84,7 @@
cls.object_storage_client = cls.manager.object_storage_client
cls.orchestration_client = cls.manager.orchestration_client
cls.data_processing_client = cls.manager.data_processing_client
- cls.resource_keys = {}
- cls.os_resources = []
+ cls.ceilometer_client = cls.manager.ceilometer_client
@classmethod
def _get_credentials(cls, get_creds, ctype):
@@ -107,72 +109,85 @@
return cls._get_credentials(cls.isolated_creds.get_admin_creds,
'identity_admin')
- @staticmethod
- def cleanup_resource(resource, test_name):
+ def setUp(self):
+ super(OfficialClientTest, self).setUp()
+ self.cleanup_waits = []
+ # NOTE(mtreinish) This is safe to do in setUp instead of setUp class
+ # because scenario tests in the same test class should not share
+ # resources. If resources were shared between test cases then it
+ # should be a single scenario test instead of multiples.
- LOG.debug("Deleting %r from shared resources of %s" %
- (resource, test_name))
+ # NOTE(yfried): this list is cleaned at the end of test_methods and
+ # not at the end of the class
+ self.addCleanup(self._wait_for_cleanups)
+
+ @staticmethod
+ def not_found_exception(exception):
+ """
+ @return: True if exception is of NotFound type
+ """
+ NOT_FOUND_LIST = ['NotFound', 'HTTPNotFound']
+ return (exception.__class__.__name__ in NOT_FOUND_LIST
+ or
+ hasattr(exception, 'status_code') and
+ exception.status_code == 404)
+
+ def delete_wrapper(self, thing):
+ """Ignores NotFound exceptions for delete operations.
+
+ @param thing: object with delete() method.
+ OpenStack resources are assumed to have a delete() method which
+ destroys the resource
+ """
+
try:
- # OpenStack resources are assumed to have a delete()
- # method which destroys the resource...
- resource.delete()
+ thing.delete()
except Exception as e:
# If the resource is already missing, mission accomplished.
- # - Status code tolerated as a workaround for bug 1247568
- # - HTTPNotFound tolerated as this is currently raised when
- # attempting to delete an already-deleted heat stack.
- if (e.__class__.__name__ in ('NotFound', 'HTTPNotFound') or
- (hasattr(e, 'status_code') and e.status_code == 404)):
- return
- raise
-
- def is_deletion_complete():
- # Deletion testing is only required for objects whose
- # existence cannot be checked via retrieval.
- if isinstance(resource, dict):
- return True
- try:
- resource.get()
- except Exception as e:
- # Clients are expected to return an exception
- # called 'NotFound' if retrieval fails.
- if e.__class__.__name__ == 'NotFound':
- return True
+ if not self.not_found_exception(e):
raise
- return False
- # Block until resource deletion has completed or timed-out
- tempest.test.call_until_true(is_deletion_complete, 10, 1)
+ def _wait_for_cleanups(self):
+ """To handle async delete actions, a list of waits is added
+ which will be iterated over as the last step of clearing the
+ cleanup queue. That way all the delete calls are made up front
+ and the tests won't succeed unless the deletes are eventually
+ successful. This is the same basic approach used in the api tests to
+ limit cleanup execution time except here it is multi-resource,
+ because of the nature of the scenario tests.
+ """
+ for wait in self.cleanup_waits:
+ self.delete_timeout(**wait)
- @classmethod
- def tearDownClass(cls):
- # NOTE(jaypipes): Because scenario tests are typically run in a
- # specific order, and because test methods in scenario tests
- # generally create resources in a particular order, we destroy
- # resources in the reverse order in which resources are added to
- # the scenario test class object
- while cls.os_resources:
- thing = cls.os_resources.pop()
- cls.cleanup_resource(thing, cls.__name__)
- cls.isolated_creds.clear_isolated_creds()
- super(OfficialClientTest, cls).tearDownClass()
+ def addCleanup_with_wait(self, things, thing_id,
+ error_status='ERROR',
+ exc_type=nova_exceptions.NotFound,
+ cleanup_callable=None, cleanup_args=[],
+ cleanup_kwargs={}):
+ """Adds wait for ansyc resource deletion at the end of cleanups
- @classmethod
- def set_resource(cls, key, thing):
- LOG.debug("Adding %r to shared resources of %s" %
- (thing, cls.__name__))
- cls.resource_keys[key] = thing
- cls.os_resources.append(thing)
-
- @classmethod
- def get_resource(cls, key):
- return cls.resource_keys[key]
-
- @classmethod
- def remove_resource(cls, key):
- thing = cls.resource_keys[key]
- cls.os_resources.remove(thing)
- del cls.resource_keys[key]
+ @param things: type of the resource to delete
+ @param thing_id:
+ @param error_status: see manager.delete_timeout()
+ @param exc_type: see manager.delete_timeout()
+ @param cleanup_callable: method to load pass to self.addCleanup with
+ the following *cleanup_args, **cleanup_kwargs.
+ usually a delete method. if not used, will try to use:
+ things.delete(thing_id)
+ """
+ if cleanup_callable is None:
+ LOG.debug("no delete method passed. using {rclass}.delete({id}) as"
+ " default".format(rclass=things, id=thing_id))
+ self.addCleanup(things.delete, thing_id)
+ else:
+ self.addCleanup(cleanup_callable, *cleanup_args, **cleanup_kwargs)
+ wait_dict = {
+ 'things': things,
+ 'thing_id': thing_id,
+ 'error_status': error_status,
+ 'not_found_exception': exc_type,
+ }
+ self.cleanup_waits.append(wait_dict)
def status_timeout(self, things, thing_id, expected_status,
error_status='ERROR',
@@ -224,8 +239,11 @@
except not_found_exception:
if allow_notfound:
return True
- else:
- raise
+ raise
+ except Exception as e:
+ if allow_notfound and self.not_found_exception(e):
+ return True
+ raise
new_status = thing.status
@@ -285,12 +303,39 @@
for ruleset in rulesets:
sg_rule = client.security_group_rules.create(secgroup_id,
**ruleset)
- self.set_resource(sg_rule.id, sg_rule)
+ self.addCleanup(self.delete_wrapper, sg_rule)
rules.append(sg_rule)
return rules
+ def _create_security_group_nova(self, client=None,
+ namestart='secgroup-smoke-'):
+ if client is None:
+ client = self.compute_client
+ # Create security group
+ sg_name = data_utils.rand_name(namestart)
+ sg_desc = sg_name + " description"
+ secgroup = client.security_groups.create(sg_name, sg_desc)
+ self.assertEqual(secgroup.name, sg_name)
+ self.assertEqual(secgroup.description, sg_desc)
+ self.addCleanup(self.delete_wrapper, secgroup)
+
+ # Add rules to the security group
+ self._create_loginable_secgroup_rule_nova(client, secgroup.id)
+
+ return secgroup
+
def create_server(self, client=None, name=None, image=None, flavor=None,
- wait=True, create_kwargs={}):
+ wait_on_boot=True, wait_on_delete=True,
+ create_kwargs={}):
+ """Creates VM instance.
+
+ @param client: compute client to create the instance
+ @param image: image from which to create the instance
+ @param wait_on_boot: wait for status ACTIVE before continue
+ @param wait_on_delete: force synchronous delete on cleanup
+ @param create_kwargs: additional details for instance creation
+ @return: client.server object
+ """
if client is None:
client = self.compute_client
if name is None:
@@ -324,19 +369,25 @@
name, image, flavor)
server = client.servers.create(name, image, flavor, **create_kwargs)
self.assertEqual(server.name, name)
- self.set_resource(name, server)
- if wait:
+ if wait_on_delete:
+ self.addCleanup(self.delete_timeout,
+ self.compute_client.servers,
+ server.id)
+ self.addCleanup_with_wait(self.compute_client.servers, server.id,
+ cleanup_callable=self.delete_wrapper,
+ cleanup_args=[server])
+ if wait_on_boot:
self.status_timeout(client.servers, server.id, 'ACTIVE')
# The instance retrieved on creation is missing network
# details, necessitating retrieval after it becomes active to
# ensure correct details.
server = client.servers.get(server.id)
- self.set_resource(name, server)
LOG.debug("Created server: %s", server)
return server
def create_volume(self, client=None, size=1, name=None,
- snapshot_id=None, imageRef=None):
+ snapshot_id=None, imageRef=None, volume_type=None,
+ wait_on_delete=True):
if client is None:
client = self.volume_client
if name is None:
@@ -344,8 +395,14 @@
LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
volume = client.volumes.create(size=size, display_name=name,
snapshot_id=snapshot_id,
- imageRef=imageRef)
- self.set_resource(name, volume)
+ imageRef=imageRef,
+ volume_type=volume_type)
+ if wait_on_delete:
+ self.addCleanup(self.delete_timeout,
+ self.volume_client.volumes,
+ volume.id)
+ self.addCleanup_with_wait(self.volume_client.volumes, volume.id,
+ exc_type=cinder_exceptions.NotFound)
self.assertEqual(name, volume.display_name)
self.status_timeout(client.volumes, volume.id, 'available')
LOG.debug("Created volume: %s", volume)
@@ -361,7 +418,8 @@
name = data_utils.rand_name('scenario-snapshot-')
LOG.debug("Creating a snapshot image for server: %s", server.name)
image_id = compute_client.servers.create_image(server, name)
- self.addCleanup(image_client.images.delete, image_id)
+ self.addCleanup_with_wait(self.image_client.images, image_id,
+ exc_type=glanceclient.exc.HTTPNotFound)
self.status_timeout(image_client.images, image_id, 'active')
snapshot_image = image_client.images.get(image_id)
self.assertEqual(name, snapshot_image.name)
@@ -376,7 +434,7 @@
name = data_utils.rand_name('scenario-keypair-')
keypair = client.keypairs.create(name)
self.assertEqual(keypair.name, name)
- self.set_resource(name, keypair)
+ self.addCleanup(self.delete_wrapper, keypair)
return keypair
def get_remote_client(self, server_or_ip, username=None, private_key=None):
@@ -389,7 +447,16 @@
username = CONF.scenario.ssh_user
if private_key is None:
private_key = self.keypair.private_key
- return remote_client.RemoteClient(ip, username, pkey=private_key)
+ linux_client = remote_client.RemoteClient(ip, username,
+ pkey=private_key)
+ try:
+ linux_client.validate_authentication()
+ except exceptions.SSHTimeout:
+ LOG.exception('ssh connection to %s failed' % ip)
+ debug.log_net_debug()
+ raise
+
+ return linux_client
def _log_console_output(self, servers=None):
if not servers:
@@ -411,7 +478,7 @@
'name': name,
'container_format': fmt,
'disk_format': fmt,
- 'is_public': 'True',
+ 'is_public': 'False',
}
params.update(properties)
image = self.image_client.images.create(**params)
@@ -447,6 +514,30 @@
LOG.debug("image:%s" % self.image)
+# power/provision states as of icehouse
+class BaremetalPowerStates(object):
+ """Possible power states of an Ironic node."""
+ POWER_ON = 'power on'
+ POWER_OFF = 'power off'
+ REBOOT = 'rebooting'
+ SUSPEND = 'suspended'
+
+
+class BaremetalProvisionStates(object):
+ """Possible provision states of an Ironic node."""
+ NOSTATE = None
+ INIT = 'initializing'
+ ACTIVE = 'active'
+ BUILDING = 'building'
+ DEPLOYWAIT = 'wait call-back'
+ DEPLOYING = 'deploying'
+ DEPLOYFAIL = 'deploy failed'
+ DEPLOYDONE = 'deploy complete'
+ DELETING = 'deleting'
+ DELETED = 'deleted'
+ ERROR = 'error'
+
+
class BaremetalScenarioTest(OfficialClientTest):
@classmethod
def setUpClass(cls):
@@ -522,6 +613,126 @@
ports.append(self.baremetal_client.port.get(port.uuid))
return ports
+ def add_keypair(self):
+ self.keypair = self.create_keypair()
+
+ def verify_connectivity(self, ip=None):
+ if ip:
+ dest = self.get_remote_client(ip)
+ else:
+ dest = self.get_remote_client(self.instance)
+ dest.validate_authentication()
+
+ def boot_instance(self):
+ create_kwargs = {
+ 'key_name': self.keypair.id
+ }
+ self.instance = self.create_server(
+ wait_on_boot=False, create_kwargs=create_kwargs)
+
+ self.addCleanup_with_wait(self.compute_client.servers,
+ self.instance.id,
+ cleanup_callable=self.delete_wrapper,
+ cleanup_args=[self.instance])
+
+ self.wait_node(self.instance.id)
+ self.node = self.get_node(instance_id=self.instance.id)
+
+ self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_ON)
+
+ self.wait_provisioning_state(
+ self.node.uuid,
+ [BaremetalProvisionStates.DEPLOYWAIT,
+ BaremetalProvisionStates.ACTIVE],
+ timeout=15)
+
+ self.wait_provisioning_state(self.node.uuid,
+ BaremetalProvisionStates.ACTIVE,
+ timeout=CONF.baremetal.active_timeout)
+
+ self.status_timeout(
+ self.compute_client.servers, self.instance.id, 'ACTIVE')
+
+ self.node = self.get_node(instance_id=self.instance.id)
+ self.instance = self.compute_client.servers.get(self.instance.id)
+
+ def terminate_instance(self):
+ self.instance.delete()
+ self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
+ self.wait_provisioning_state(
+ self.node.uuid,
+ BaremetalProvisionStates.NOSTATE,
+ timeout=CONF.baremetal.unprovision_timeout)
+
+
+class EncryptionScenarioTest(OfficialClientTest):
+ """
+ Base class for encryption scenario tests
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(EncryptionScenarioTest, cls).setUpClass()
+
+ # use admin credentials to create encrypted volume types
+ admin_creds = cls.admin_credentials()
+ manager = clients.OfficialClientManager(credentials=admin_creds)
+ cls.admin_volume_client = manager.volume_client
+
+ def _wait_for_volume_status(self, status):
+ self.status_timeout(
+ self.volume_client.volumes, self.volume.id, status)
+
+ def nova_boot(self):
+ self.keypair = self.create_keypair()
+ create_kwargs = {'key_name': self.keypair.name}
+ self.server = self.create_server(self.compute_client,
+ image=self.image,
+ create_kwargs=create_kwargs)
+
+ def create_volume_type(self, client=None, name=None):
+ if not client:
+ client = self.admin_volume_client
+ if not name:
+ name = 'generic'
+ randomized_name = data_utils.rand_name('scenario-type-' + name + '-')
+ LOG.debug("Creating a volume type: %s", randomized_name)
+ volume_type = client.volume_types.create(randomized_name)
+ self.addCleanup(client.volume_types.delete, volume_type.id)
+ return volume_type
+
+ def create_encryption_type(self, client=None, type_id=None, provider=None,
+ key_size=None, cipher=None,
+ control_location=None):
+ if not client:
+ client = self.admin_volume_client
+ if not type_id:
+ volume_type = self.create_volume_type()
+ type_id = volume_type.id
+ LOG.debug("Creating an encryption type for volume type: %s", type_id)
+ client.volume_encryption_types.create(type_id,
+ {'provider': provider,
+ 'key_size': key_size,
+ 'cipher': cipher,
+ 'control_location':
+ control_location})
+
+ def nova_volume_attach(self):
+ attach_volume_client = self.compute_client.volumes.create_server_volume
+ volume = attach_volume_client(self.server.id,
+ self.volume.id,
+ '/dev/vdb')
+ self.assertEqual(self.volume.id, volume.id)
+ self._wait_for_volume_status('in-use')
+
+ def nova_volume_detach(self):
+ detach_volume_client = self.compute_client.volumes.delete_server_volume
+ detach_volume_client(self.server.id, self.volume.id)
+ self._wait_for_volume_status('available')
+
+ volume = self.volume_client.volumes.get(self.volume.id)
+ self.assertEqual('available', volume.status)
+
class NetworkScenarioTest(OfficialClientTest):
"""
@@ -560,7 +771,7 @@
network = net_common.DeletableNetwork(client=self.network_client,
**result['network'])
self.assertEqual(network.name, name)
- self.set_resource(name, network)
+ self.addCleanup(self.delete_wrapper, network)
return network
def _list_networks(self, **kwargs):
@@ -636,7 +847,7 @@
subnet = net_common.DeletableSubnet(client=self.network_client,
**result['subnet'])
self.assertEqual(subnet.cidr, str_cidr)
- self.set_resource(data_utils.rand_name(namestart), subnet)
+ self.addCleanup(self.delete_wrapper, subnet)
return subnet
def _create_port(self, network, namestart='port-quotatest-'):
@@ -649,7 +860,7 @@
self.assertIsNotNone(result, 'Unable to allocate port')
port = net_common.DeletablePort(client=self.network_client,
**result['port'])
- self.set_resource(name, port)
+ self.addCleanup(self.delete_wrapper, port)
return port
def _get_server_port_id(self, server, ip_addr=None):
@@ -672,7 +883,7 @@
floating_ip = net_common.DeletableFloatingIp(
client=self.network_client,
**result['floatingip'])
- self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
+ self.addCleanup(self.delete_wrapper, floating_ip)
return floating_ip
def _associate_floating_ip(self, floating_ip, server):
@@ -717,7 +928,7 @@
pool = net_common.DeletablePool(client=self.network_client,
**resp['pool'])
self.assertEqual(pool['name'], name)
- self.set_resource(name, pool)
+ self.addCleanup(self.delete_wrapper, pool)
return pool
def _create_member(self, address, protocol_port, pool_id):
@@ -732,7 +943,7 @@
resp = self.network_client.create_member(body)
member = net_common.DeletableMember(client=self.network_client,
**resp['member'])
- self.set_resource(data_utils.rand_name('member-'), member)
+ self.addCleanup(self.delete_wrapper, member)
return member
def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
@@ -751,7 +962,7 @@
vip = net_common.DeletableVip(client=self.network_client,
**resp['vip'])
self.assertEqual(vip['name'], name)
- self.set_resource(name, vip)
+ self.addCleanup(self.delete_wrapper, vip)
return vip
def _check_vm_connectivity(self, ip_address,
@@ -778,9 +989,56 @@
msg=msg)
if should_connect:
# no need to check ssh for negative connectivity
- linux_client = self.get_remote_client(ip_address, username,
- private_key)
- linux_client.validate_authentication()
+ self.get_remote_client(ip_address, username, private_key)
+
+ def _check_public_network_connectivity(self, ip_address, username,
+ private_key, should_connect=True,
+ msg=None, servers=None):
+ # The target login is assumed to have been configured for
+ # key-based authentication by cloud-init.
+ LOG.debug('checking network connections to IP %s with user: %s' %
+ (ip_address, username))
+ try:
+ self._check_vm_connectivity(ip_address,
+ username,
+ private_key,
+ should_connect=should_connect)
+ except Exception as e:
+ ex_msg = 'Public network connectivity check failed'
+ if msg:
+ ex_msg += ": " + msg
+ LOG.exception(ex_msg)
+ self._log_console_output(servers)
+ # network debug is called as part of ssh init
+ if not isinstance(e, exceptions.SSHTimeout):
+ debug.log_net_debug()
+ raise
+
+ def _check_tenant_network_connectivity(self, server,
+ username,
+ private_key,
+ should_connect=True,
+ servers_for_debug=None):
+ if not CONF.network.tenant_networks_reachable:
+ msg = 'Tenant networks not configured to be reachable.'
+ LOG.info(msg)
+ return
+ # The target login is assumed to have been configured for
+ # key-based authentication by cloud-init.
+ try:
+ for net_name, ip_addresses in server.networks.iteritems():
+ for ip_address in ip_addresses:
+ self._check_vm_connectivity(ip_address,
+ username,
+ private_key,
+ should_connect=should_connect)
+ except Exception as e:
+ LOG.exception('Tenant network connectivity check failed')
+ self._log_console_output(servers_for_debug)
+ # network debug is called as part of ssh init
+ if not isinstance(e, exceptions.SSHTimeout):
+ debug.log_net_debug()
+ raise
def _check_remote_connectivity(self, source, dest, should_succeed=True):
"""
@@ -804,24 +1062,6 @@
CONF.compute.ping_timeout,
1)
- def _create_security_group_nova(self, client=None,
- namestart='secgroup-smoke-',
- tenant_id=None):
- if client is None:
- client = self.compute_client
- # Create security group
- sg_name = data_utils.rand_name(namestart)
- sg_desc = sg_name + " description"
- secgroup = client.security_groups.create(sg_name, sg_desc)
- self.assertEqual(secgroup.name, sg_name)
- self.assertEqual(secgroup.description, sg_desc)
- self.set_resource(sg_name, secgroup)
-
- # Add rules to the security group
- self._create_loginable_secgroup_rule_nova(client, secgroup.id)
-
- return secgroup
-
def _create_security_group_neutron(self, tenant_id, client=None,
namestart='secgroup-smoke-'):
if client is None:
@@ -864,7 +1104,7 @@
self.assertEqual(secgroup.name, sg_name)
self.assertEqual(tenant_id, secgroup.tenant_id)
self.assertEqual(secgroup.description, sg_desc)
- self.set_resource(sg_name, secgroup)
+ self.addCleanup(self.delete_wrapper, secgroup)
return secgroup
def _default_security_group(self, tenant_id, client=None):
@@ -923,7 +1163,7 @@
client=client,
**sg_rule['security_group_rule']
)
- self.set_resource(sg_rule.id, sg_rule)
+ self.addCleanup(self.delete_wrapper, sg_rule)
self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
self.assertEqual(secgroup.id, sg_rule.security_group_id)
@@ -1022,7 +1262,7 @@
router = net_common.DeletableRouter(client=self.network_client,
**result['router'])
self.assertEqual(router.name, name)
- self.set_resource(name, router)
+ self.addCleanup(self.delete_wrapper, router)
return router
def _create_networks(self, tenant_id=None):
diff --git a/tempest/scenario/orchestration/test_autoscaling.py b/tempest/scenario/orchestration/test_autoscaling.py
index 82ba3c5..aa7b6f8 100644
--- a/tempest/scenario/orchestration/test_autoscaling.py
+++ b/tempest/scenario/orchestration/test_autoscaling.py
@@ -59,7 +59,7 @@
# if a keypair was set, do not delete the stack on exit to allow
# for manual post-mortums
if not CONF.orchestration.keypair_name:
- self.set_resource('stack', self.stack)
+ self.addCleanup(self.client.stacks.delete, self.stack)
@test.skip_because(bug="1257575")
@test.attr(type='slow')
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 6817c48..0059619 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -42,13 +42,12 @@
availability_zone = kwargs['availability_zone']
self.assertEqual(aggregate.name, aggregate_name)
self.assertEqual(aggregate.availability_zone, availability_zone)
- self.set_resource(aggregate.id, aggregate)
+ self.addCleanup(self._delete_aggregate, aggregate)
LOG.debug("Aggregate %s created." % (aggregate.name))
return aggregate
def _delete_aggregate(self, aggregate):
self.compute_client.aggregates.delete(aggregate.id)
- self.remove_resource(aggregate.id)
LOG.debug("Aggregate %s deleted. " % (aggregate.name))
def _get_host_name(self):
@@ -60,6 +59,7 @@
def _add_host(self, aggregate_name, host):
aggregate = self.compute_client.aggregates.add_host(aggregate_name,
host)
+ self.addCleanup(self._remove_host, aggregate, host)
self.assertIn(host, aggregate.hosts)
LOG.debug("Host %s added to Aggregate %s." % (host, aggregate.name))
@@ -128,6 +128,3 @@
metadata.update(additional_metadata)
self._check_aggregate_details(aggregate, aggregate.name, az, [host],
metadata)
-
- self._remove_host(aggregate, host)
- self._delete_aggregate(aggregate)
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index c53aa83..f197c15 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -23,31 +23,7 @@
LOG = logging.getLogger(__name__)
-# power/provision states as of icehouse
-class PowerStates(object):
- """Possible power states of an Ironic node."""
- POWER_ON = 'power on'
- POWER_OFF = 'power off'
- REBOOT = 'rebooting'
- SUSPEND = 'suspended'
-
-
-class ProvisionStates(object):
- """Possible provision states of an Ironic node."""
- NOSTATE = None
- INIT = 'initializing'
- ACTIVE = 'active'
- BUILDING = 'building'
- DEPLOYWAIT = 'wait call-back'
- DEPLOYING = 'deploying'
- DEPLOYFAIL = 'deploy failed'
- DEPLOYDONE = 'deploy complete'
- DELETING = 'deleting'
- DELETED = 'deleted'
- ERROR = 'error'
-
-
-class BaremetalBasicOptsPXESSH(manager.BaremetalScenarioTest):
+class BaremetalBasicOpsPXESSH(manager.BaremetalScenarioTest):
"""
This smoke test tests the pxe_ssh Ironic driver. It follows this basic
set of operations:
@@ -55,8 +31,6 @@
* Boots an instance using the keypair
* Monitors the associated Ironic node for power and
expected state transitions
- * Validates Ironic node's driver_info has been properly
- updated
* Validates Ironic node's port data has been properly updated
* Verifies SSH connectivity using created keypair via fixed IP
* Associates a floating ip
@@ -65,32 +39,11 @@
* Monitors the associated Ironic node for power and
expected state transitions
"""
- def add_keypair(self):
- self.keypair = self.create_keypair()
-
def add_floating_ip(self):
floating_ip = self.compute_client.floating_ips.create()
self.instance.add_floating_ip(floating_ip)
return floating_ip.ip
- def verify_connectivity(self, ip=None):
- if ip:
- dest = self.get_remote_client(ip)
- else:
- dest = self.get_remote_client(self.instance)
- dest.validate_authentication()
-
- def validate_driver_info(self):
- f_id = self.instance.flavor['id']
- flavor_extra = self.compute_client.flavors.get(f_id).get_keys()
- driver_info = self.node.driver_info
- self.assertEqual(driver_info['pxe_deploy_kernel'],
- flavor_extra['baremetal:deploy_kernel_id'])
- self.assertEqual(driver_info['pxe_deploy_ramdisk'],
- flavor_extra['baremetal:deploy_ramdisk_id'])
- self.assertEqual(driver_info['pxe_image_source'],
- self.instance.image['id'])
-
def validate_ports(self):
for port in self.get_ports(self.node.uuid):
n_port_id = port.extra['vif_port_id']
@@ -98,48 +51,10 @@
self.assertEqual(n_port['device_id'], self.instance.id)
self.assertEqual(n_port['mac_address'], port.address)
- def boot_instance(self):
- create_kwargs = {
- 'key_name': self.keypair.id
- }
- self.instance = self.create_server(
- wait=False, create_kwargs=create_kwargs)
-
- self.set_resource('instance', self.instance)
-
- self.wait_node(self.instance.id)
- self.node = self.get_node(instance_id=self.instance.id)
-
- self.wait_power_state(self.node.uuid, PowerStates.POWER_ON)
-
- self.wait_provisioning_state(
- self.node.uuid,
- [ProvisionStates.DEPLOYWAIT, ProvisionStates.ACTIVE],
- timeout=15)
-
- self.wait_provisioning_state(self.node.uuid, ProvisionStates.ACTIVE,
- timeout=CONF.baremetal.active_timeout)
-
- self.status_timeout(
- self.compute_client.servers, self.instance.id, 'ACTIVE')
-
- self.node = self.get_node(instance_id=self.instance.id)
- self.instance = self.compute_client.servers.get(self.instance.id)
-
- def terminate_instance(self):
- self.instance.delete()
- self.remove_resource('instance')
- self.wait_power_state(self.node.uuid, PowerStates.POWER_OFF)
- self.wait_provisioning_state(
- self.node.uuid,
- ProvisionStates.NOSTATE,
- timeout=CONF.baremetal.unprovision_timeout)
-
@test.services('baremetal', 'compute', 'image', 'network')
def test_baremetal_server_ops(self):
self.add_keypair()
self.boot_instance()
- self.validate_driver_info()
self.validate_ports()
self.verify_connectivity()
floating_ip = self.add_floating_ip()
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
new file mode 100644
index 0000000..366cd93
--- /dev/null
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2014 The Johns Hopkins University/Applied Physics Laboratory
+# 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.scenario import manager
+from tempest import test
+
+
+class TestEncryptedCinderVolumes(manager.EncryptionScenarioTest):
+
+ """
+ This test is for verifying the functionality of encrypted cinder volumes.
+
+ For both LUKS and cryptsetup encryption types, this test performs
+ the following:
+ * Creates an image in Glance
+ * Boots an instance from the image
+ * Creates an encryption type (as admin)
+ * Creates a volume of that encryption type (as a regular user)
+ * Attaches and detaches the encrypted volume to the instance
+ """
+
+ def launch_instance(self):
+ self.glance_image_create()
+ self.nova_boot()
+
+ def create_encrypted_volume(self, encryption_provider):
+ volume_type = self.create_volume_type(name='luks')
+ self.create_encryption_type(type_id=volume_type.id,
+ provider=encryption_provider,
+ key_size=512,
+ cipher='aes-xts-plain64',
+ control_location='front-end')
+ self.volume = self.create_volume(volume_type=volume_type.name)
+
+ def attach_detach_volume(self):
+ self.nova_volume_attach()
+ self.nova_volume_detach()
+
+ @test.services('compute', 'volume', 'image')
+ def test_encrypted_cinder_volumes_luks(self):
+ self.launch_instance()
+ self.create_encrypted_volume('nova.volume.encryptors.'
+ 'luks.LuksEncryptor')
+ self.attach_detach_volume()
+
+ @test.services('compute', 'volume', 'image')
+ def test_encrypted_cinder_volumes_cryptsetup(self):
+ self.launch_instance()
+ self.create_encrypted_volume('nova.volume.encryptors.'
+ 'cryptsetup.CryptsetupEncryptor')
+ self.attach_detach_volume()
\ No newline at end of file
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index ed5743c..15cf13b 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -60,7 +60,13 @@
# needed because of bug 1199788
self.servers = [x for x in client.servers.list() if name in x.name]
for server in self.servers:
- self.set_resource(server.name, server)
+ # after deleting all servers - wait for all servers to clear
+ # before cleanup continues
+ self.addCleanup(self.delete_timeout,
+ self.compute_client.servers,
+ server.id)
+ for server in self.servers:
+ self.addCleanup_with_wait(self.compute_client.servers, server.id)
self._wait_for_server_status('ACTIVE')
def _large_ops_scenario(self):
diff --git a/tempest/scenario/test_load_balancer_basic.py b/tempest/scenario/test_load_balancer_basic.py
index 9152220..8191984 100644
--- a/tempest/scenario/test_load_balancer_basic.py
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+
+import tempfile
import time
-import urllib
+import urllib2
from tempest.api.network import common as net_common
+from tempest.common import commands
from tempest import config
from tempest import exceptions
from tempest.scenario import manager
@@ -71,15 +74,11 @@
self.server_fixed_ips = {}
self._create_security_group()
- def cleanup_wrapper(self, resource):
- self.cleanup_resource(resource, self.__class__.__name__)
-
def _create_security_group(self):
self.security_group = self._create_security_group_neutron(
tenant_id=self.tenant_id)
self._create_security_group_rules_for_port(self.port1)
self._create_security_group_rules_for_port(self.port2)
- self.addCleanup(self.cleanup_wrapper, self.security_group)
def _create_security_group_rules_for_port(self, port):
rule = {
@@ -96,7 +95,6 @@
def _create_server(self, name):
keypair = self.create_keypair(name='keypair-%s' % name)
- self.addCleanup(self.cleanup_wrapper, keypair)
security_groups = [self.security_group.name]
net = self._list_networks(tenant_id=self.tenant_id)[0]
create_kwargs = {
@@ -108,14 +106,12 @@
}
server = self.create_server(name=name,
create_kwargs=create_kwargs)
- self.addCleanup(self.cleanup_wrapper, server)
self.servers_keypairs[server.id] = keypair
if (config.network.public_network_id and not
config.network.tenant_networks_reachable):
public_network_id = config.network.public_network_id
floating_ip = self._create_floating_ip(
server, public_network_id)
- self.addCleanup(self.cleanup_wrapper, floating_ip)
self.floating_ips[floating_ip] = server
self.server_ips[server.id] = floating_ip.floating_ip_address
else:
@@ -135,71 +131,57 @@
1. SSH to the instance
2. Start two http backends listening on ports 80 and 88 respectively
- In case there are two instances, each backend is created on a separate
- instance.
-
- The backends are the inetd services. To start them we need to edit
- /etc/inetd.conf in the following way:
- www stream tcp nowait root /bin/sh sh /home/cirros/script_name
-
- Where /home/cirros/script_name is a path to a script which
- echoes the responses:
- echo -e 'HTTP/1.0 200 OK\r\n\r\nserver_name
-
- If we want the server to listen on port 88, then we use
- "kerberos" instead of "www".
"""
for server_id, ip in self.server_ips.iteritems():
private_key = self.servers_keypairs[server_id].private_key
server_name = self.compute_client.servers.get(server_id).name
+ username = config.scenario.ssh_user
ssh_client = self.get_remote_client(
server_or_ip=ip,
private_key=private_key)
- ssh_client.validate_authentication()
- # Create service for inetd
- create_script = """sudo sh -c "echo -e \\"echo -e 'HTTP/1.0 """ \
- """200 OK\\\\\\r\\\\\\n\\\\\\r\\\\\\n""" \
- """%(server)s'\\" >>/home/cirros/%(script)s\""""
- cmd = create_script % {
- 'server': server_name,
- 'script': 'script1'}
- ssh_client.exec_command(cmd)
- # Configure inetd
- configure_inetd = """sudo sh -c "echo -e \\"%(service)s """ \
- """stream tcp nowait root /bin/sh sh """ \
- """/home/cirros/%(script)s\\" >> """ \
- """/etc/inetd.conf\""""
- # "www" stands for port 80
- cmd = configure_inetd % {'service': 'www',
- 'script': 'script1'}
+ # Write a backend's response into a file
+ resp = """echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 7\r\n""" \
+ """Connection: close\r\nContent-Type: text/html; """ \
+ """charset=UTF-8\r\n\r\n%s"; cat >/dev/null"""
+
+ with tempfile.NamedTemporaryFile() as script:
+ script.write(resp % server_name)
+ script.flush()
+ with tempfile.NamedTemporaryFile() as key:
+ key.write(private_key)
+ key.flush()
+ commands.copy_file_to_host(script.name,
+ "/tmp/script1",
+ ip,
+ username, key.name)
+
+ # Start netcat
+ start_server = """sudo nc -ll -p %(port)s -e sh """ \
+ """/tmp/%(script)s &"""
+ cmd = start_server % {'port': self.port1,
+ 'script': 'script1'}
ssh_client.exec_command(cmd)
if len(self.server_ips) == 1:
- cmd = create_script % {'server': 'server2',
- 'script': 'script2'}
+ with tempfile.NamedTemporaryFile() as script:
+ script.write(resp % 'server2')
+ script.flush()
+ with tempfile.NamedTemporaryFile() as key:
+ key.write(private_key)
+ key.flush()
+ commands.copy_file_to_host(script.name,
+ "/tmp/script2", ip,
+ username, key.name)
+ cmd = start_server % {'port': self.port2,
+ 'script': 'script2'}
ssh_client.exec_command(cmd)
- # "kerberos" stands for port 88
- cmd = configure_inetd % {'service': 'kerberos',
- 'script': 'script2'}
- ssh_client.exec_command(cmd)
-
- # Get PIDs of inetd
- pids = ssh_client.get_pids('inetd')
- if pids != ['']:
- # If there are any inetd processes, reload them
- kill_cmd = "sudo kill -HUP %s" % ' '.join(pids)
- ssh_client.exec_command(kill_cmd)
- else:
- # In other case start inetd
- start_inetd = "sudo /usr/sbin/inetd /etc/inetd.conf"
- ssh_client.exec_command(start_inetd)
def _check_connection(self, check_ip, port=80):
def try_connect(ip, port):
try:
- resp = urllib.urlopen("http://{0}:{1}/".format(ip, port))
+ resp = urllib2.urlopen("http://{0}:{1}/".format(ip, port))
if resp.getcode() == 200:
return True
return False
@@ -222,7 +204,6 @@
lb_method='ROUND_ROBIN',
protocol='HTTP',
subnet_id=self.subnet.id)
- self.addCleanup(self.cleanup_wrapper, self.pool)
self.assertTrue(self.pool)
def _create_members(self):
@@ -238,17 +219,14 @@
member1 = self._create_member(address=ip,
protocol_port=self.port1,
pool_id=self.pool.id)
- self.addCleanup(self.cleanup_wrapper, member1)
member2 = self._create_member(address=ip,
protocol_port=self.port2,
pool_id=self.pool.id)
- self.addCleanup(self.cleanup_wrapper, member2)
self.members.extend([member1, member2])
else:
member = self._create_member(address=ip,
protocol_port=self.port1,
pool_id=self.pool.id)
- self.addCleanup(self.cleanup_wrapper, member)
self.members.append(member)
self.assertTrue(self.members)
@@ -257,7 +235,6 @@
port_id = vip.port_id
floating_ip = self._create_floating_ip(vip, public_network_id,
port_id=port_id)
- self.addCleanup(self.cleanup_wrapper, floating_ip)
self.floating_ips.setdefault(vip.id, [])
self.floating_ips[vip.id].append(floating_ip)
@@ -268,7 +245,6 @@
protocol_port=80,
subnet_id=self.subnet.id,
pool_id=self.pool.id)
- self.addCleanup(self.cleanup_wrapper, self.vip)
self.status_timeout(NeutronRetriever(self.network_client,
self.network_client.vip_path,
net_common.DeletableVip),
@@ -282,29 +258,36 @@
else:
self.vip_ip = self.vip.address
+ # Currently the ovs-agent is not enforcing security groups on the
+ # vip port - see https://bugs.launchpad.net/neutron/+bug/1163569
+ # However the linuxbridge-agent does, and it is necessary to add a
+ # security group with a rule that allows tcp port 80 to the vip port.
+ body = {'port': {'security_groups': [self.security_group.id]}}
+ self.network_client.update_port(self.vip.port_id, body)
+
def _check_load_balancing(self):
"""
- 1. Send 100 requests on the floating ip associated with the VIP
+ 1. Send 10 requests on the floating ip associated with the VIP
2. Check that the requests are shared between
the two servers and that both of them get equal portions
of the requests
"""
self._check_connection(self.vip_ip)
- resp = self._send_requests(self.vip_ip)
- self.assertEqual(set(["server1\n", "server2\n"]), set(resp))
- self.assertEqual(50, resp.count("server1\n"))
- self.assertEqual(50, resp.count("server2\n"))
+ self._send_requests(self.vip_ip, set(["server1", "server2"]))
- def _send_requests(self, vip_ip):
- resp = []
- for count in range(100):
- resp.append(
- urllib.urlopen(
- "http://{0}/".format(vip_ip)).read())
- return resp
+ def _send_requests(self, vip_ip, expected, num_req=10):
+ count = 0
+ while count < num_req:
+ resp = []
+ for i in range(len(self.members)):
+ resp.append(
+ urllib2.urlopen(
+ "http://{0}/".format(vip_ip)).read())
+ count += 1
+ self.assertEqual(expected,
+ set(resp))
- @test.skip_because(bug='1295165')
@test.attr(type='smoke')
@test.services('compute', 'network')
def test_load_balancer_basic(self):
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 24d2677..29fdc74 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -85,7 +85,7 @@
def nova_floating_ip_create(self):
self.floating_ip = self.compute_client.floating_ips.create()
- self.addCleanup(self.floating_ip.delete)
+ self.addCleanup(self.delete_wrapper, self.floating_ip)
def nova_floating_ip_add(self):
self.server.add_floating_ip(self.floating_ip)
@@ -93,11 +93,12 @@
def ssh_to_server(self):
try:
self.linux_client = self.get_remote_client(self.floating_ip.ip)
- self.linux_client.validate_authentication()
- except Exception:
+ except Exception as e:
LOG.exception('ssh to server failed')
self._log_console_output()
- debug.log_net_debug()
+ # network debug is called as part of ssh init
+ if not isinstance(e, test.exceptions.SSHTimeout):
+ debug.log_net_debug()
raise
def check_partitions(self):
@@ -112,6 +113,11 @@
volume = self.volume_client.volumes.get(self.volume.id)
self.assertEqual('available', volume.status)
+ def create_and_add_security_group(self):
+ secgroup = self._create_security_group_nova()
+ self.server.add_security_group(secgroup.name)
+ self.addCleanup(self.server.remove_security_group, secgroup.name)
+
@test.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
self.glance_image_create()
@@ -128,7 +134,7 @@
self.nova_floating_ip_create()
self.nova_floating_ip_add()
- self._create_loginable_secgroup_rule_nova()
+ self.create_and_add_security_group()
self.ssh_to_server()
self.nova_reboot()
self.ssh_to_server()
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 9b435bd..431de9a 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -15,7 +15,6 @@
import testtools
-from tempest.common import debug
from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
@@ -51,23 +50,15 @@
cls.enabled = False
raise cls.skipException(msg)
- def cleanup_wrapper(self, resource):
- self.cleanup_resource(resource, self.__class__.__name__)
-
def setUp(self):
super(TestNetworkAdvancedServerOps, self).setUp()
key_name = data_utils.rand_name('keypair-smoke-')
self.keypair = self.create_keypair(name=key_name)
- self.addCleanup(self.cleanup_wrapper, self.keypair)
security_group =\
self._create_security_group_neutron(tenant_id=self.tenant_id)
- self.addCleanup(self.cleanup_wrapper, security_group)
network = self._create_network(self.tenant_id)
- self.addCleanup(self.cleanup_wrapper, network)
router = self._get_router(self.tenant_id)
- self.addCleanup(self.cleanup_wrapper, router)
subnet = self._create_subnet(network)
- self.addCleanup(self.cleanup_wrapper, subnet)
subnet.add_to_router(router.id)
public_network_id = CONF.network.public_network_id
create_kwargs = {
@@ -80,61 +71,19 @@
server_name = data_utils.rand_name('server-smoke-%d-')
self.server = self.create_server(name=server_name,
create_kwargs=create_kwargs)
- self.addCleanup(self.cleanup_wrapper, self.server)
self.floating_ip = self._create_floating_ip(self.server,
public_network_id)
- self.addCleanup(self.cleanup_wrapper, self.floating_ip)
- self._wait_server_status_and_check_network_connectivity()
-
- def _check_tenant_network_connectivity(self, server,
- username,
- private_key,
- should_connect=True):
- if not CONF.network.tenant_networks_reachable:
- msg = 'Tenant networks not configured to be reachable.'
- LOG.info(msg)
- return
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
- try:
- for net_name, ip_addresses in server.networks.iteritems():
- for ip_address in ip_addresses:
- self._check_vm_connectivity(ip_address,
- username,
- private_key,
- should_connect=should_connect)
- except Exception:
- LOG.exception('Tenant network connectivity check failed')
- self._log_console_output(servers=[server])
- debug.log_ip_ns()
- raise
-
- def _check_public_network_connectivity(self, floating_ip,
- username,
- private_key,
- should_connect=True):
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
- try:
- self._check_vm_connectivity(floating_ip, username, private_key,
- should_connect=should_connect)
- except Exception:
- LOG.exception("Public network connectivity check failed")
- debug.log_ip_ns()
- raise
def _check_network_connectivity(self, should_connect=True):
username = CONF.compute.image_ssh_user
private_key = self.keypair.private_key
- self._check_tenant_network_connectivity(self.server,
- username,
- private_key,
- should_connect=should_connect)
+ self._check_tenant_network_connectivity(
+ self.server, username, private_key, should_connect=should_connect,
+ servers_for_debug=[self.server])
floating_ip = self.floating_ip.floating_ip_address
- self._check_public_network_connectivity(floating_ip,
- username,
- private_key,
- should_connect=should_connect)
+ self._check_public_network_connectivity(floating_ip, username,
+ private_key, should_connect,
+ servers=[self.server])
def _wait_server_status_and_check_network_connectivity(self):
self.status_timeout(self.compute_client.servers, self.server.id,
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index d5ab3d3..7dc817d 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -87,6 +87,8 @@
@classmethod
def setUpClass(cls):
+ # Create no network resources for these tests.
+ cls.set_network_resources()
super(TestNetworkBasicOps, cls).setUpClass()
for ext in ['router', 'security-group']:
if not test.is_extension_enabled(ext, 'network'):
@@ -94,17 +96,11 @@
raise cls.skipException(msg)
cls.check_preconditions()
- def cleanup_wrapper(self, resource):
- self.cleanup_resource(resource, self.__class__.__name__)
-
def setUp(self):
super(TestNetworkBasicOps, self).setUp()
self.security_group = \
self._create_security_group_neutron(tenant_id=self.tenant_id)
- self.addCleanup(self.cleanup_wrapper, self.security_group)
self.network, self.subnet, self.router = self._create_networks()
- for r in [self.network, self.router, self.subnet]:
- self.addCleanup(self.cleanup_wrapper, r)
self.check_networks()
self.servers = {}
name = data_utils.rand_name('server-smoke')
@@ -142,7 +138,6 @@
def _create_server(self, name, network):
keypair = self.create_keypair(name='keypair-%s' % name)
- self.addCleanup(self.cleanup_wrapper, keypair)
security_groups = [self.security_group.name]
create_kwargs = {
'nics': [
@@ -152,60 +147,35 @@
'security_groups': security_groups,
}
server = self.create_server(name=name, create_kwargs=create_kwargs)
- self.addCleanup(self.cleanup_wrapper, server)
return dict(server=server, keypair=keypair)
def _check_tenant_network_connectivity(self):
- if not CONF.network.tenant_networks_reachable:
- msg = 'Tenant networks not configured to be reachable.'
- LOG.info(msg)
- return
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
ssh_login = CONF.compute.image_ssh_user
- try:
- for server, key in self.servers.iteritems():
- for net_name, ip_addresses in server.networks.iteritems():
- for ip_address in ip_addresses:
- self._check_vm_connectivity(ip_address, ssh_login,
- key.private_key)
- except Exception:
- LOG.exception('Tenant connectivity check failed')
- self._log_console_output(servers=self.servers.keys())
- debug.log_net_debug()
- raise
+ for server, key in self.servers.iteritems():
+ # call the common method in the parent class
+ super(TestNetworkBasicOps, self).\
+ _check_tenant_network_connectivity(
+ server, ssh_login, key.private_key,
+ servers_for_debug=self.servers.keys())
def _create_and_associate_floating_ips(self):
public_network_id = CONF.network.public_network_id
for server in self.servers.keys():
floating_ip = self._create_floating_ip(server, public_network_id)
self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server)
- self.addCleanup(self.cleanup_wrapper, floating_ip)
def _check_public_network_connectivity(self, should_connect=True,
msg=None):
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
ssh_login = CONF.compute.image_ssh_user
- LOG.debug('checking network connections')
floating_ip, server = self.floating_ip_tuple
ip_address = floating_ip.floating_ip_address
private_key = None
if should_connect:
private_key = self.servers[server].private_key
- try:
- self._check_vm_connectivity(ip_address,
- ssh_login,
- private_key,
- should_connect=should_connect)
- except Exception:
- ex_msg = 'Public network connectivity check failed'
- if msg:
- ex_msg += ": " + msg
- LOG.exception(ex_msg)
- self._log_console_output(servers=self.servers.keys())
- debug.log_net_debug()
- raise
+ # call the common method in the parent class
+ super(TestNetworkBasicOps, self)._check_public_network_connectivity(
+ ip_address, ssh_login, private_key, should_connect, msg,
+ self.servers.keys())
def _disassociate_floating_ips(self):
floating_ip, server = self.floating_ip_tuple
@@ -225,11 +195,9 @@
def _create_new_network(self):
self.new_net = self._create_network(self.tenant_id)
- self.addCleanup(self.cleanup_wrapper, self.new_net)
self.new_subnet = self._create_subnet(
network=self.new_net,
gateway_ip=None)
- self.addCleanup(self.cleanup_wrapper, self.new_subnet)
def _hotplug_server(self):
old_floating_ip, server = self.floating_ip_tuple
@@ -247,7 +215,10 @@
port_id=None,
fixed_ip=None)
# move server to the head of the cleanup list
- self.addCleanup(self.cleanup_wrapper, server)
+ self.addCleanup(self.delete_timeout,
+ self.compute_client.servers,
+ server.id)
+ self.addCleanup(self.delete_wrapper, server)
def check_ports():
port_list = [port for port in
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 4616b82..8058b3d 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -134,6 +134,8 @@
@classmethod
def setUpClass(cls):
+ # Create no network resources for these tests.
+ cls.set_network_resources()
super(TestSecurityGroupsBasicOps, cls).setUpClass()
cls.alt_creds = cls.alt_credentials()
cls.alt_manager = clients.OfficialClientManager(cls.alt_creds)
@@ -163,7 +165,6 @@
def _create_tenant_keypairs(self, tenant_id):
keypair = self.create_keypair(
name=data_utils.rand_name('keypair-smoke-'))
- self.addCleanup(self.cleanup_wrapper, keypair)
self.tenants[tenant_id].keypair = keypair
def _create_tenant_security_groups(self, tenant):
@@ -171,14 +172,12 @@
namestart='secgroup_access-',
tenant_id=tenant.creds.tenant_id
)
- self.addCleanup(self.cleanup_wrapper, access_sg)
# don't use default secgroup since it allows in-tenant traffic
def_sg = self._create_empty_security_group(
namestart='secgroup_general-',
tenant_id=tenant.creds.tenant_id
)
- self.addCleanup(self.cleanup_wrapper, def_sg)
tenant.security_groups.update(access=access_sg, default=def_sg)
ssh_rule = dict(
protocol='tcp',
@@ -186,9 +185,7 @@
port_range_max=22,
direction='ingress',
)
- rule = self._create_security_group_rule(secgroup=access_sg,
- **ssh_rule)
- self.addCleanup(self.cleanup_wrapper, rule)
+ self._create_security_group_rule(secgroup=access_sg, **ssh_rule)
def _verify_network_details(self, tenant):
# Checks that we see the newly created network/subnet/router via
@@ -236,7 +233,6 @@
'tenant_id': tenant.creds.tenant_id
}
server = self.create_server(name=name, create_kwargs=create_kwargs)
- self.addCleanup(self.cleanup_wrapper, server)
return server
def _create_tenant_servers(self, tenant, num=1):
@@ -267,13 +263,10 @@
def _assign_floating_ips(self, server):
public_network_id = CONF.network.public_network_id
floating_ip = self._create_floating_ip(server, public_network_id)
- self.addCleanup(self.cleanup_wrapper, floating_ip)
self.floating_ips.setdefault(server, floating_ip)
def _create_tenant_network(self, tenant):
network, subnet, router = self._create_networks(tenant.creds.tenant_id)
- for r in [network, router, subnet]:
- self.addCleanup(self.cleanup_wrapper, r)
tenant.set_network(network, subnet, router)
def _set_compute_context(self, tenant):
@@ -334,6 +327,8 @@
self.assertTrue(self._check_remote_connectivity(access_point, ip,
should_succeed),
msg)
+ except test.exceptions.SSHTimeout:
+ raise
except Exception:
debug.log_net_debug()
raise
@@ -351,11 +346,10 @@
remote_group_id=tenant.security_groups['default'].id,
direction='ingress'
)
- rule = self._create_security_group_rule(
+ self._create_security_group_rule(
secgroup=tenant.security_groups['default'],
**ruleset
)
- self.addCleanup(self.cleanup_wrapper, rule)
access_point_ssh = self._connect_to_access_point(tenant)
for server in tenant.servers:
self._check_connectivity(access_point=access_point_ssh,
@@ -381,11 +375,10 @@
protocol='icmp',
direction='ingress'
)
- rule_s2d = self._create_security_group_rule(
+ self._create_security_group_rule(
secgroup=dest_tenant.security_groups['default'],
**ruleset
)
- self.addCleanup(self.cleanup_wrapper, rule_s2d)
access_point_ssh = self._connect_to_access_point(source_tenant)
ip = self._get_server_ip(dest_tenant.access_point,
floating=self.floating_ip_access)
@@ -395,11 +388,10 @@
self._test_cross_tenant_block(dest_tenant, source_tenant)
# allow reverse traffic and check
- rule_d2s = self._create_security_group_rule(
+ self._create_security_group_rule(
secgroup=source_tenant.security_groups['default'],
**ruleset
)
- self.addCleanup(self.cleanup_wrapper, rule_d2s)
access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
ip = self._get_server_ip(source_tenant.access_point,
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 13e00a5..38686d9 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
from tempest.scenario import manager
@@ -68,47 +67,30 @@
def add_keypair(self):
self.keypair = self.create_keypair()
- def create_security_group(self):
- sg_name = data_utils.rand_name('secgroup-smoke')
- sg_desc = sg_name + " description"
- self.secgroup = self.compute_client.security_groups.create(sg_name,
- sg_desc)
- self.assertEqual(self.secgroup.name, sg_name)
- self.assertEqual(self.secgroup.description, sg_desc)
- self.set_resource('secgroup', self.secgroup)
-
- # Add rules to the security group
- self._create_loginable_secgroup_rule_nova(secgroup_id=self.secgroup.id)
-
def boot_instance(self):
# Create server with image and flavor from input scenario
+ security_groups = [self.security_group.name]
create_kwargs = {
- 'key_name': self.keypair.id
+ 'key_name': self.keypair.id,
+ 'security_groups': security_groups
}
- instance = self.create_server(image=self.image_ref,
- flavor=self.flavor_ref,
- create_kwargs=create_kwargs)
- self.set_resource('instance', instance)
-
- def terminate_instance(self):
- instance = self.get_resource('instance')
- instance.delete()
- self.remove_resource('instance')
+ self.instance = self.create_server(image=self.image_ref,
+ flavor=self.flavor_ref,
+ create_kwargs=create_kwargs)
def verify_ssh(self):
if self.run_ssh:
# Obtain a floating IP
floating_ip = self.compute_client.floating_ips.create()
+ self.addCleanup(self.delete_wrapper, floating_ip)
# Attach a floating IP
- instance = self.get_resource('instance')
- instance.add_floating_ip(floating_ip)
+ self.instance.add_floating_ip(floating_ip)
# Check ssh
try:
- linux_client = self.get_remote_client(
+ self.get_remote_client(
server_or_ip=floating_ip.ip,
username=self.image_utils.ssh_user(self.image_ref),
private_key=self.keypair.private_key)
- linux_client.validate_authentication()
except Exception:
LOG.exception('ssh to server failed')
self._log_console_output()
@@ -117,7 +99,7 @@
@test.services('compute', 'network')
def test_server_basicops(self):
self.add_keypair()
- self.create_security_group()
+ self.security_group = self._create_security_group_nova()
self.boot_instance()
self.verify_ssh()
- self.terminate_instance()
+ self.instance.delete()
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 562020a..7dd662d 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -35,8 +35,10 @@
"""
def _boot_image(self, image_id):
+ security_groups = [self.security_group.name]
create_kwargs = {
- 'key_name': self.keypair.name
+ 'key_name': self.keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image=image_id, create_kwargs=create_kwargs)
@@ -47,8 +49,9 @@
try:
return self.get_remote_client(server_or_ip)
except Exception:
- LOG.exception()
+ LOG.exception('Initializing SSH connection failed')
self._log_console_output()
+ raise
def _write_timestamp(self, server_or_ip):
ssh_client = self._ssh_to_server(server_or_ip)
@@ -62,7 +65,7 @@
def _create_floating_ip(self):
floating_ip = self.compute_client.floating_ips.create()
- self.addCleanup(floating_ip.delete)
+ self.addCleanup(self.delete_wrapper, floating_ip)
return floating_ip
def _set_floating_ip_to_server(self, server, floating_ip):
@@ -72,7 +75,7 @@
def test_snapshot_pattern(self):
# prepare for booting a instance
self._add_keypair()
- self._create_loginable_secgroup_rule_nova()
+ self.security_group = self._create_security_group_nova()
# boot a instance and create a timestamp file in it
server = self._boot_image(CONF.compute.image_ref)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 5235871..be27024 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -62,8 +62,10 @@
volume_snapshot.id, status)
def _boot_image(self, image_id):
+ security_groups = [self.security_group.name]
create_kwargs = {
- 'key_name': self.keypair.name
+ 'key_name': self.keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image=image_id, create_kwargs=create_kwargs)
@@ -72,7 +74,7 @@
def _create_floating_ip(self):
floating_ip = self.compute_client.floating_ips.create()
- self.addCleanup(floating_ip.delete)
+ self.addCleanup(self.delete_wrapper, floating_ip)
return floating_ip
def _add_floating_ip(self, server, floating_ip):
@@ -152,7 +154,7 @@
def test_stamp_pattern(self):
# prepare for booting a instance
self._add_keypair()
- self._create_loginable_secgroup_rule_nova()
+ self.security_group = self._create_security_group_nova()
# boot an instance and create a timestamp file in it
volume = self._create_volume()
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index faca31f..bf5d1f6 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from cinderclient import exceptions as cinder_exc
+
from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log
@@ -55,9 +57,11 @@
bd_map = {
'vda': vol_id + ':::0'
}
+ security_groups = [self.security_group.name]
create_kwargs = {
'block_device_mapping': bd_map,
- 'key_name': keypair.name
+ 'key_name': keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image='', create_kwargs=create_kwargs)
@@ -67,7 +71,8 @@
snap = volume_snapshots.create(volume_id=vol_id,
force=True,
display_name=snap_name)
- self.set_resource(snap.id, snap)
+ self.addCleanup_with_wait(self.volume_client.volume_snapshots, snap.id,
+ exc_type=cinder_exc.NotFound)
self.status_timeout(volume_snapshots,
snap.id,
'available')
@@ -98,8 +103,7 @@
def _ssh_to_server(self, server, keypair):
if CONF.compute.use_floatingip_for_ssh:
floating_ip = self.compute_client.floating_ips.create()
- fip_name = data_utils.rand_name('scenario-fip')
- self.set_resource(fip_name, floating_ip)
+ self.addCleanup(self.delete_wrapper, floating_ip)
server.add_floating_ip(floating_ip)
ip = floating_ip.ip
else:
@@ -135,7 +139,7 @@
@test.services('compute', 'volume', 'image')
def test_volume_boot_pattern(self):
keypair = self.create_keypair()
- self._create_loginable_secgroup_rule_nova()
+ self.security_group = self._create_security_group_nova()
# create an instance from volume
volume_origin = self._create_volume_from_image()
@@ -182,8 +186,10 @@
bdms = [{'uuid': vol_id, 'source_type': 'volume',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}]
+ security_groups = [self.security_group.name]
create_kwargs = {
'block_device_mapping_v2': bdms,
- 'key_name': keypair.name
+ 'key_name': keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image='', create_kwargs=create_kwargs)
diff --git a/tempest/services/baremetal/base.py b/tempest/services/baremetal/base.py
index 2af287f..f98ecff 100644
--- a/tempest/services/baremetal/base.py
+++ b/tempest/services/baremetal/base.py
@@ -122,7 +122,7 @@
return resp, self.deserialize(body)
- def _show_request(self, resource, uuid, permanent=False):
+ def _show_request(self, resource, uuid, permanent=False, **kwargs):
"""
Gets a specific object of the specified type.
@@ -130,7 +130,10 @@
:return: Serialized object as a dictionary.
"""
- uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
+ if 'uri' in kwargs:
+ uri = kwargs['uri']
+ else:
+ uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
resp, body = self.get(uri)
return resp, self.deserialize(body)
@@ -199,3 +202,14 @@
"""
return self._list_request(version, permanent=True)
+
+ def _put_request(self, resource, put_object):
+ """
+ Update specified object with JSON-patch.
+
+ """
+ uri = self._get_uri(resource)
+ put_body = json.dumps(put_object)
+
+ resp, body = self.put(uri, body=put_body)
+ return resp, body
diff --git a/tempest/services/baremetal/v1/base_v1.py b/tempest/services/baremetal/v1/base_v1.py
index 296a199..9c753c2 100644
--- a/tempest/services/baremetal/v1/base_v1.py
+++ b/tempest/services/baremetal/v1/base_v1.py
@@ -47,9 +47,9 @@
return self._list_request('/nodes/%s/states' % uuid)
@base.handle_errors
- def list_ports_detail(self):
+ def list_ports_detail(self, **kwargs):
"""Details list all existing ports."""
- return self._list_request('/ports/detail')
+ return self._list_request('/ports/detail', **kwargs)
@base.handle_errors
def list_drivers(self):
@@ -89,6 +89,15 @@
"""
return self._show_request('ports', uuid)
+ def show_driver(self, driver_name):
+ """
+ Gets a specific driver.
+
+ :param driver_name: Name of driver.
+ :return: Serialized driver as a dictionary.
+ """
+ return self._show_request('drivers', driver_name)
+
@base.handle_errors
def create_node(self, chassis_id, **kwargs):
"""
@@ -226,3 +235,32 @@
"""
return self._patch_request('ports', uuid, patch)
+
+ @base.handle_errors
+ def set_node_power_state(self, node_uuid, state):
+ """
+ Set power state of the specified node.
+
+ :param node_uuid: The unique identifier of the node.
+ :state: desired state to set (on/off/reboot).
+
+ """
+ target = {'target': state}
+ return self._put_request('nodes/%s/states/power' % node_uuid,
+ target)
+
+ @base.handle_errors
+ def validate_driver_interface(self, node_uuid):
+ """
+ Get all driver interfaces of a specific node.
+
+ :param uuid: Unique identifier of the node in UUID format.
+
+ """
+
+ uri = '{pref}/{res}/{uuid}/{postf}'.format(pref=self.uri_prefix,
+ res='nodes',
+ uuid=node_uuid,
+ postf='validate')
+
+ return self._show_request('nodes', node_uuid, uri=uri)
diff --git a/tempest/services/botoclients.py b/tempest/services/botoclients.py
index 7616a99..7af904b 100644
--- a/tempest/services/botoclients.py
+++ b/tempest/services/botoclients.py
@@ -37,13 +37,16 @@
*args, **kwargs):
# FIXME(andreaf) replace credentials and auth_url with auth_provider
+ insecure_ssl = CONF.identity.disable_ssl_certificate_validation
+
self.connection_timeout = str(CONF.boto.http_socket_timeout)
self.num_retries = str(CONF.boto.num_retries)
self.build_timeout = CONF.boto.build_timeout
self.ks_cred = {"username": username,
"password": password,
"auth_url": auth_url,
- "tenant_name": tenant_name}
+ "tenant_name": tenant_name,
+ "insecure": insecure_ssl}
def _keystone_aws_get(self):
# FIXME(andreaf) Move EC2 credentials to AuthProvider
@@ -90,7 +93,10 @@
self._config_boto_timeout(self.connection_timeout, self.num_retries)
if not all((self.connection_data["aws_access_key_id"],
self.connection_data["aws_secret_access_key"])):
- if all(self.ks_cred.itervalues()):
+ if all([self.ks_cred.get('auth_url'),
+ self.ks_cred.get('username'),
+ self.ks_cred.get('tenant_name'),
+ self.ks_cred.get('password')]):
ec2_cred = self._keystone_aws_get()
self.connection_data["aws_access_key_id"] = \
ec2_cred.access
@@ -109,6 +115,7 @@
def __init__(self, *args, **kwargs):
super(APIClientEC2, self).__init__(*args, **kwargs)
+ insecure_ssl = CONF.identity.disable_ssl_certificate_validation
aws_access = CONF.boto.aws_access
aws_secret = CONF.boto.aws_secret
purl = urlparse.urlparse(CONF.boto.ec2_url)
@@ -129,6 +136,7 @@
self.connection_data = {"aws_access_key_id": aws_access,
"aws_secret_access_key": aws_secret,
"is_secure": purl.scheme == "https",
+ "validate_certs": not insecure_ssl,
"region": region,
"host": purl.hostname,
"port": port,
@@ -187,6 +195,7 @@
def __init__(self, *args, **kwargs):
super(ObjectClientS3, self).__init__(*args, **kwargs)
+ insecure_ssl = CONF.identity.disable_ssl_certificate_validation
aws_access = CONF.boto.aws_access
aws_secret = CONF.boto.aws_secret
purl = urlparse.urlparse(CONF.boto.s3_url)
@@ -201,6 +210,7 @@
self.connection_data = {"aws_access_key_id": aws_access,
"aws_secret_access_key": aws_secret,
"is_secure": purl.scheme == "https",
+ "validate_certs": not insecure_ssl,
"host": purl.hostname,
"port": port,
"calling_format": boto.s3.connection.
diff --git a/tempest/services/compute/json/agents_client.py b/tempest/services/compute/json/agents_client.py
index 98d8896..4f6602f 100644
--- a/tempest/services/compute/json/agents_client.py
+++ b/tempest/services/compute/json/agents_client.py
@@ -46,7 +46,9 @@
"""Create an agent build."""
post_body = json.dumps({'agent': kwargs})
resp, body = self.post('os-agents', post_body)
- return resp, self._parse_resp(body)
+ body = json.loads(body)
+ self.validate_response(schema.create_agent, resp, body)
+ return resp, body['agent']
def delete_agent(self, agent_id):
"""Delete an existing agent build."""
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index e2e12d5..cd195f4 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -112,3 +112,31 @@
body = json.loads(body)
self.validate_response(schema.floating_ip_pools, resp, body)
return resp, body['floating_ip_pools']
+
+ def create_floating_ips_bulk(self, ip_range, pool, interface):
+ """Allocate floating IPs in bulk."""
+ post_body = {
+ 'ip_range': ip_range,
+ 'pool': pool,
+ 'interface': interface
+ }
+ post_body = json.dumps({'floating_ips_bulk_create': post_body})
+ resp, body = self.post('os-floating-ips-bulk', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_floating_ips_bulk, resp, body)
+ return resp, body['floating_ips_bulk_create']
+
+ def list_floating_ips_bulk(self):
+ """Returns a list of all floating IPs bulk."""
+ resp, body = self.get('os-floating-ips-bulk')
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ips_bulk, resp, body)
+ return resp, body['floating_ip_info']
+
+ def delete_floating_ips_bulk(self, ip_range):
+ """Deletes the provided floating IPs bulk."""
+ post_body = json.dumps({'ip_range': ip_range})
+ resp, body = self.put('os-floating-ips-bulk/delete', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.delete_floating_ips_bulk, resp, body)
+ return resp, body['floating_ips_bulk_delete']
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 7e828d8..14b7100 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import quota_classes as classes_schema
from tempest.api_schema.compute.v2 import quotas as schema
from tempest.common import rest_client
from tempest import config
@@ -118,3 +119,32 @@
resp, body = self.delete('os-quota-sets/%s' % str(tenant_id))
self.validate_response(schema.delete_quota, resp, body)
return resp, body
+
+
+class QuotaClassesClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(QuotaClassesClientJSON, self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def get_quota_class_set(self, quota_class_id):
+ """List the quota class set for a quota class."""
+
+ url = 'os-quota-class-sets/%s' % str(quota_class_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(classes_schema.quota_set, resp, body)
+ return resp, body['quota_class_set']
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """
+ Updates the quota class's limits for one or more resources.
+ """
+ post_body = json.dumps({'quota_class_set': kwargs})
+
+ resp, body = self.put('os-quota-class-sets/%s' % str(quota_class_id),
+ post_body)
+
+ body = json.loads(body)
+ self.validate_response(classes_schema.quota_set_update, resp, body)
+ return resp, body['quota_class_set']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 77c73df..fea0d28 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -57,6 +57,7 @@
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
return_reservation_id: Enable/Disable the return of reservation id
+ block_device_mapping: Block device mapping for the server.
"""
post_body = {
'name': name,
@@ -69,7 +70,7 @@
'availability_zone', 'accessIPv4', 'accessIPv6',
'min_count', 'max_count', ('metadata', 'meta'),
('OS-DCF:diskConfig', 'disk_config'),
- 'return_reservation_id']:
+ 'return_reservation_id', 'block_device_mapping']:
if isinstance(option, tuple):
post_param = option[0]
key = option[1]
@@ -133,6 +134,7 @@
"""Returns the details of an existing server."""
resp, body = self.get("servers/%s" % str(server_id))
body = json.loads(body)
+ self.validate_response(schema.get_server, resp, body)
return resp, body['server']
def delete_server(self, server_id):
@@ -162,6 +164,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_servers_detail, resp, body)
return resp, body
def wait_for_server_status(self, server_id, status, extra_timeout=0,
@@ -193,6 +196,7 @@
"""Lists all addresses for a server."""
resp, body = self.get("servers/%s/ips" % str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_addresses, resp, body)
return resp, body['addresses']
def list_addresses_by_network(self, server_id, network_id):
@@ -307,6 +311,8 @@
resp, body = self.post('servers/%s/metadata' % str(server_id),
post_body)
body = json.loads(body)
+ self.validate_response(common_schema.update_server_metadata,
+ resp, body)
return resp, body['metadata']
def get_server_metadata_item(self, server_id, key):
@@ -428,8 +434,9 @@
return self.action(server_id, 'shelveOffload', None, **kwargs)
def get_console_output(self, server_id, length):
+ kwargs = {'length': length} if length else {}
return self.action(server_id, 'os-getConsoleOutput', 'output',
- common_schema.get_console_output, length=length)
+ common_schema.get_console_output, **kwargs)
def list_virtual_interfaces(self, server_id):
"""
@@ -459,6 +466,7 @@
resp, body = self.get("servers/%s/os-instance-actions" %
str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_instance_actions, resp, body)
return resp, body['instanceActions']
def get_instance_action(self, server_id, request_id):
@@ -505,20 +513,25 @@
resp, body = self.post('os-server-groups', post_body)
body = json.loads(body)
+ self.validate_response(schema.create_get_server_group, resp, body)
return resp, body['server_group']
def delete_server_group(self, server_group_id):
"""Delete the given server-group."""
- return self.delete("os-server-groups/%s" % str(server_group_id))
+ resp, body = self.delete("os-server-groups/%s" % str(server_group_id))
+ self.validate_response(schema.delete_server_group, resp, body)
+ return resp, body
def list_server_groups(self):
"""List the server-groups."""
resp, body = self.get("os-server-groups")
body = json.loads(body)
+ self.validate_response(schema.list_server_groups, resp, body)
return resp, body['server_groups']
def get_server_group(self, server_group_id):
"""Get the details of given server_group."""
resp, body = self.get("os-server-groups/%s" % str(server_group_id))
body = json.loads(body)
+ self.validate_response(schema.create_get_server_group, resp, body)
return resp, body['server_group']
diff --git a/tempest/services/compute/v3/json/agents_client.py b/tempest/services/compute/v3/json/agents_client.py
index 48be54c..31314b7 100644
--- a/tempest/services/compute/v3/json/agents_client.py
+++ b/tempest/services/compute/v3/json/agents_client.py
@@ -43,7 +43,9 @@
"""Create an agent build."""
post_body = json.dumps({'agent': kwargs})
resp, body = self.post('os-agents', post_body)
- return resp, self._parse_resp(body)
+ body = json.loads(body)
+ self.validate_response(schema.create_agent, resp, body)
+ return resp, body['agent']
def delete_agent(self, agent_id):
"""Delete an existing agent build."""
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 2a83f88..932cc2c 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -55,6 +55,7 @@
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
return_reservation_id: Enable/Disable the return of reservation id
+ block_device_mapping: Block device mapping for the server.
"""
post_body = {
'name': name,
@@ -75,7 +76,9 @@
('metadata', 'meta'),
('os-disk-config:disk_config', 'disk_config'),
('os-multiple-create:return_reservation_id',
- 'return_reservation_id')]:
+ 'return_reservation_id'),
+ ('os-block-device-mapping:block_device_mapping',
+ 'block_device_mapping')]:
if isinstance(option, tuple):
post_param = option[0]
key = option[1]
@@ -133,6 +136,7 @@
"""Returns the details of an existing server."""
resp, body = self.get("servers/%s" % str(server_id))
body = json.loads(body)
+ self.validate_response(schema.get_server, resp, body)
return resp, body['server']
def delete_server(self, server_id):
@@ -162,6 +166,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_servers_detail, resp, body)
return resp, body
def wait_for_server_status(self, server_id, status, extra_timeout=0,
@@ -193,6 +198,7 @@
"""Lists all addresses for a server."""
resp, body = self.get("servers/%s/ips" % str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_addresses, resp, body)
return resp, body['addresses']
def list_addresses_by_network(self, server_id, network_id):
@@ -323,6 +329,7 @@
resp, body = self.post('servers/%s/metadata' % str(server_id),
post_body)
body = json.loads(body)
+ self.validate_response(schema.update_server_metadata, resp, body)
return resp, body['metadata']
def get_server_metadata_item(self, server_id, key):
@@ -430,6 +437,9 @@
return self.action(server_id, 'shelve_offload', None, **kwargs)
def get_console_output(self, server_id, length):
+ if length is None:
+ # NOTE(mriedem): -1 means optional/unlimited in the nova v3 API.
+ length = -1
return self.action(server_id, 'get_console_output', 'output',
common_schema.get_console_output, length=length)
@@ -453,6 +463,7 @@
resp, body = self.get("servers/%s/os-server-actions" %
str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_server_actions, resp, body)
return resp, body['server_actions']
def get_server_action(self, server_id, request_id):
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 5502fcc..7f87248 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -22,6 +22,19 @@
CONF = config.CONF
+def format_quota(q):
+ quota = {}
+ for k, v in q.items():
+ try:
+ v = int(v)
+ except ValueError:
+ pass
+
+ quota[k] = v
+
+ return quota
+
+
class QuotasClientXML(rest_client.RestClient):
TYPE = "xml"
@@ -29,18 +42,6 @@
super(QuotasClientXML, self).__init__(auth_provider)
self.service = CONF.compute.catalog_type
- def _format_quota(self, q):
- quota = {}
- for k, v in q.items():
- try:
- v = int(v)
- except ValueError:
- pass
-
- quota[k] = v
-
- return quota
-
def get_quota_set(self, tenant_id, user_id=None):
"""List the quota set for a tenant."""
@@ -49,7 +50,7 @@
url += '?user_id=%s' % str(user_id)
resp, body = self.get(url)
body = xml_utils.xml_to_json(etree.fromstring(body))
- body = self._format_quota(body)
+ body = format_quota(body)
return resp, body
def get_default_quota_set(self, tenant_id):
@@ -58,7 +59,7 @@
url = 'os-quota-sets/%s/defaults' % str(tenant_id)
resp, body = self.get(url)
body = xml_utils.xml_to_json(etree.fromstring(body))
- body = self._format_quota(body)
+ body = format_quota(body)
return resp, body
def update_quota_set(self, tenant_id, user_id=None,
@@ -124,9 +125,41 @@
str(xml_utils.Document(post_body)))
body = xml_utils.xml_to_json(etree.fromstring(body))
- body = self._format_quota(body)
+ body = format_quota(body)
return resp, body
def delete_quota_set(self, tenant_id):
"""Delete the tenant's quota set."""
return self.delete('os-quota-sets/%s' % str(tenant_id))
+
+
+class QuotaClassesClientXML(rest_client.RestClient):
+ TYPE = "xml"
+
+ def __init__(self, auth_provider):
+ super(QuotaClassesClientXML, self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def get_quota_class_set(self, quota_class_id):
+ """List the quota class set for a quota class."""
+
+ url = 'os-quota-class-sets/%s' % str(quota_class_id)
+ resp, body = self.get(url)
+ body = xml_utils.xml_to_json(etree.fromstring(body))
+ body = format_quota(body)
+ return resp, body
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """
+ Updates the quota class's limits for one or more resources.
+ """
+ post_body = xml_utils.Element("quota_class_set",
+ xmlns=xml_utils.XMLNS_11,
+ **kwargs)
+
+ resp, body = self.put('os-quota-class-sets/%s' % str(quota_class_id),
+ str(xml_utils.Document(post_body)))
+
+ body = xml_utils.xml_to_json(etree.fromstring(body))
+ body = format_quota(body)
+ return resp, body
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index c1105f9..156d889 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -318,6 +318,7 @@
min_count: Count of minimum number of instances to launch.
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
+ block_device_mapping: Block device mapping for the server.
"""
server = xml_utils.Element("server",
xmlns=xml_utils.XMLNS_11,
@@ -327,7 +328,8 @@
for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name",
"user_data", "availability_zone", "min_count",
- "max_count", "return_reservation_id"]:
+ "max_count", "return_reservation_id",
+ "block_device_mapping"]:
if attr in kwargs:
server.add_attr(attr, kwargs[attr])
@@ -592,8 +594,9 @@
return resp, body
def get_console_output(self, server_id, length):
+ kwargs = {'length': length} if length else {}
return self.action(server_id, 'os-getConsoleOutput', 'output',
- length=length)
+ **kwargs)
def list_virtual_interfaces(self, server_id):
"""
diff --git a/tempest/services/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index 73e67c3..c2c7fd1 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -186,3 +186,49 @@
uri = 'job-binary-internals/%s' % job_binary_id
return self.delete(uri)
+
+ def get_job_binary_internal_data(self, job_binary_id):
+ """Returns data of a single job binary internal."""
+
+ uri = 'job-binary-internals/%s/data' % job_binary_id
+ return self.get(uri)
+
+ def list_job_binaries(self):
+ """List all job binaries for a user."""
+
+ uri = 'job-binaries'
+ return self._request_and_parse(self.get, uri, 'binaries')
+
+ def get_job_binary(self, job_binary_id):
+ """Returns the details of a single job binary."""
+
+ uri = 'job-binaries/%s' % job_binary_id
+ return self._request_and_parse(self.get, uri, 'job_binary')
+
+ def create_job_binary(self, name, url, extra=None, **kwargs):
+ """Creates job binary with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object.
+ """
+ uri = 'job-binaries'
+ body = kwargs.copy()
+ body.update({
+ 'name': name,
+ 'url': url,
+ 'extra': extra or dict(),
+ })
+ return self._request_and_parse(self.post, uri, 'job_binary',
+ body=json.dumps(body))
+
+ def delete_job_binary(self, job_binary_id):
+ """Deletes the specified job binary by id."""
+
+ uri = 'job-binaries/%s' % job_binary_id
+ return self.delete(uri)
+
+ def get_job_binary_data(self, job_binary_id):
+ """Returns data of a single job binary."""
+
+ uri = 'job-binaries/%s/data' % job_binary_id
+ return self.get(uri)
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 479a289..ac65f81 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -27,7 +27,8 @@
self.endpoint_url = 'adminURL'
# Needed for xml service client
- self.list_tags = ["roles", "tenants", "users", "services"]
+ self.list_tags = ["roles", "tenants", "users", "services",
+ "extensions"]
def has_admin_extensions(self):
"""
@@ -36,8 +37,12 @@
"""
if hasattr(self, '_has_admin_extensions'):
return self._has_admin_extensions
- resp, body = self.list_roles()
- self._has_admin_extensions = ('status' in resp and resp.status != 503)
+ # Try something that requires admin
+ try:
+ self.list_roles()
+ self._has_admin_extensions = True
+ except Exception:
+ self._has_admin_extensions = False
return self._has_admin_extensions
def create_role(self, name):
@@ -47,11 +52,13 @@
}
post_body = json.dumps({'role': post_body})
resp, body = self.post('OS-KSADM/roles', post_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_role(self, role_id):
"""Get a role by its id."""
resp, body = self.get('OS-KSADM/roles/%s' % role_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['role']
@@ -69,51 +76,63 @@
}
post_body = json.dumps({'tenant': post_body})
resp, body = self.post('tenants', post_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def delete_role(self, role_id):
"""Delete a role."""
- return self.delete('OS-KSADM/roles/%s' % str(role_id))
+ resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
+ return resp, body
def list_user_roles(self, tenant_id, user_id):
"""Returns a list of roles assigned to a user for a tenant."""
url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id)
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def assign_user_role(self, tenant_id, user_id, role_id):
"""Add roles to a user on a tenant."""
resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id), "")
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def remove_user_role(self, tenant_id, user_id, role_id):
"""Removes a role assignment for a user on a tenant."""
- return self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
- (tenant_id, user_id, role_id))
+ resp, body = self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
+ (tenant_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return resp, body
def delete_tenant(self, tenant_id):
"""Delete a tenant."""
- return self.delete('tenants/%s' % str(tenant_id))
+ resp, body = self.delete('tenants/%s' % str(tenant_id))
+ self.expected_success(204, resp.status)
+ return resp, body
def get_tenant(self, tenant_id):
"""Get tenant details."""
resp, body = self.get('tenants/%s' % str(tenant_id))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def list_roles(self):
"""Returns roles."""
resp, body = self.get('OS-KSADM/roles')
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def list_tenants(self):
"""Returns tenants."""
resp, body = self.get('tenants')
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['tenants']
def get_tenant_by_name(self, tenant_name):
- resp, tenants = self.list_tenants()
+ _, tenants = self.list_tenants()
for tenant in tenants:
if tenant['name'] == tenant_name:
return tenant
@@ -121,7 +140,7 @@
def update_tenant(self, tenant_id, **kwargs):
"""Updates a tenant."""
- resp, body = self.get_tenant(tenant_id)
+ _, body = self.get_tenant(tenant_id)
name = kwargs.get('name', body['name'])
desc = kwargs.get('description', body['description'])
en = kwargs.get('enabled', body['enabled'])
@@ -133,6 +152,7 @@
}
post_body = json.dumps({'tenant': post_body})
resp, body = self.post('tenants/%s' % tenant_id, post_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def create_user(self, name, password, tenant_id, email, **kwargs):
@@ -148,26 +168,32 @@
post_body['enabled'] = kwargs.get('enabled')
post_body = json.dumps({'user': post_body})
resp, body = self.post('users', post_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def update_user(self, user_id, **kwargs):
"""Updates a user."""
put_body = json.dumps({'user': kwargs})
resp, body = self.put('users/%s' % user_id, put_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def delete_user(self, user_id):
"""Delete a user."""
- return self.delete("users/%s" % user_id)
+ resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
+ return resp, body
def get_users(self):
"""Get the list of users."""
resp, body = self.get("users")
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def enable_disable_user(self, user_id, enabled):
@@ -177,24 +203,29 @@
}
put_body = json.dumps({'user': put_body})
resp, body = self.put('users/%s/enabled' % user_id, put_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_token(self, token_id):
"""Get token details."""
resp, body = self.get("tokens/%s" % token_id)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def delete_token(self, token_id):
"""Delete a token."""
- return self.delete("tokens/%s" % token_id)
+ resp, body = self.delete("tokens/%s" % token_id)
+ self.expected_success(204, resp.status)
+ return resp, body
def list_users_for_tenant(self, tenant_id):
"""List users for a Tenant."""
resp, body = self.get('/tenants/%s/users' % tenant_id)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_user_by_username(self, tenant_id, username):
- resp, users = self.list_users_for_tenant(tenant_id)
+ _, users = self.list_users_for_tenant(tenant_id)
for user in users:
if user['name'] == username:
return user
@@ -209,23 +240,28 @@
}
post_body = json.dumps({'OS-KSADM:service': post_body})
resp, body = self.post('/OS-KSADM/services', post_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_service(self, service_id):
"""Get Service."""
url = '/OS-KSADM/services/%s' % service_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def list_services(self):
"""List Service - Returns Services."""
resp, body = self.get('/OS-KSADM/services')
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def delete_service(self, service_id):
"""Delete Service."""
url = '/OS-KSADM/services/%s' % service_id
- return self.delete(url)
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return resp, body
def update_user_password(self, user_id, new_pass):
"""Update User Password."""
@@ -235,8 +271,16 @@
}
put_body = json.dumps({'user': put_body})
resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
+ def list_extensions(self):
+ """List all the extensions."""
+ resp, body = self.get('/extensions')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['extensions']['values']
+
class TokenClientJSON(IdentityClientJSON):
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 6829333..8c72dfa 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -49,12 +49,13 @@
}
post_body = json.dumps({'user': post_body})
resp, body = self.post('users', post_body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['user']
def update_user(self, user_id, name, **kwargs):
"""Updates a user."""
- resp, body = self.get_user(user_id)
+ _, body = self.get_user(user_id)
email = kwargs.get('email', body['email'])
en = kwargs.get('enabled', body['enabled'])
project_id = kwargs.get('project_id', body['project_id'])
@@ -71,30 +72,35 @@
}
post_body = json.dumps({'user': post_body})
resp, body = self.patch('users/%s' % user_id, post_body)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['user']
def list_user_projects(self, user_id):
"""Lists the projects on which a user has roles assigned."""
resp, body = self.get('users/%s/projects' % user_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['projects']
def get_users(self):
"""Get the list of users."""
resp, body = self.get("users")
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['users']
def get_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['user']
def delete_user(self, user_id):
"""Deletes a User."""
resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
return resp, body
def create_project(self, name, **kwargs):
@@ -110,16 +116,18 @@
}
post_body = json.dumps({'project': post_body})
resp, body = self.post('projects', post_body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['project']
def list_projects(self):
resp, body = self.get("projects")
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['projects']
def update_project(self, project_id, **kwargs):
- resp, body = self.get_project(project_id)
+ _, body = self.get_project(project_id)
name = kwargs.get('name', body['name'])
desc = kwargs.get('description', body['description'])
en = kwargs.get('enabled', body['enabled'])
@@ -133,18 +141,21 @@
}
post_body = json.dumps({'project': post_body})
resp, body = self.patch('projects/%s' % project_id, post_body)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['project']
def get_project(self, project_id):
"""GET a Project."""
resp, body = self.get("projects/%s" % project_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['project']
def delete_project(self, project_id):
"""Delete a project."""
resp, body = self.delete('projects/%s' % str(project_id))
+ self.expected_success(204, resp.status)
return resp, body
def create_role(self, name):
@@ -154,18 +165,21 @@
}
post_body = json.dumps({'role': post_body})
resp, body = self.post('roles', post_body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['role']
def get_role(self, role_id):
"""GET a Role."""
resp, body = self.get('roles/%s' % str(role_id))
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['role']
def list_roles(self):
"""Get the list of Roles."""
resp, body = self.get("roles")
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['roles']
@@ -176,18 +190,21 @@
}
post_body = json.dumps({'role': post_body})
resp, body = self.patch('roles/%s' % str(role_id), post_body)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['role']
def delete_role(self, role_id):
"""Delete a role."""
resp, body = self.delete('roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
return resp, body
def assign_user_role(self, project_id, user_id, role_id):
"""Add roles to a user on a project."""
resp, body = self.put('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
return resp, body
def create_domain(self, name, **kwargs):
@@ -201,23 +218,26 @@
}
post_body = json.dumps({'domain': post_body})
resp, body = self.post('domains', post_body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['domain']
def delete_domain(self, domain_id):
"""Delete a domain."""
resp, body = self.delete('domains/%s' % str(domain_id))
+ self.expected_success(204, resp.status)
return resp, body
def list_domains(self):
"""List Domains."""
resp, body = self.get('domains')
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['domains']
def update_domain(self, domain_id, **kwargs):
"""Updates a domain."""
- resp, body = self.get_domain(domain_id)
+ _, body = self.get_domain(domain_id)
description = kwargs.get('description', body['description'])
en = kwargs.get('enabled', body['enabled'])
name = kwargs.get('name', body['name'])
@@ -228,12 +248,14 @@
}
post_body = json.dumps({'domain': post_body})
resp, body = self.patch('domains/%s' % domain_id, post_body)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['domain']
def get_domain(self, domain_id):
"""Get Domain details."""
resp, body = self.get('domains/%s' % domain_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['domain']
@@ -241,6 +263,7 @@
"""Get token details."""
headers = {'X-Subject-Token': resp_token}
resp, body = self.get("auth/tokens", headers=headers)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['token']
@@ -248,6 +271,7 @@
"""Deletes token."""
headers = {'X-Subject-Token': resp_token}
resp, body = self.delete("auth/tokens", headers=headers)
+ self.expected_success(204, resp.status)
return resp, body
def create_group(self, name, **kwargs):
@@ -263,18 +287,20 @@
}
post_body = json.dumps({'group': post_body})
resp, body = self.post('groups', post_body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['group']
def get_group(self, group_id):
"""Get group details."""
resp, body = self.get('groups/%s' % group_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['group']
def update_group(self, group_id, **kwargs):
"""Updates a group."""
- resp, body = self.get_group(group_id)
+ _, body = self.get_group(group_id)
name = kwargs.get('name', body['name'])
description = kwargs.get('description', body['description'])
post_body = {
@@ -283,53 +309,62 @@
}
post_body = json.dumps({'group': post_body})
resp, body = self.patch('groups/%s' % group_id, post_body)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['group']
def delete_group(self, group_id):
"""Delete a group."""
resp, body = self.delete('groups/%s' % str(group_id))
+ self.expected_success(204, resp.status)
return resp, body
def add_group_user(self, group_id, user_id):
"""Add user into group."""
resp, body = self.put('groups/%s/users/%s' % (group_id, user_id),
None)
+ self.expected_success(204, resp.status)
return resp, body
def list_group_users(self, group_id):
"""List users in group."""
resp, body = self.get('groups/%s/users' % group_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['users']
def list_user_groups(self, user_id):
"""Lists groups which a user belongs to."""
resp, body = self.get('users/%s/groups' % user_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['groups']
def delete_group_user(self, group_id, user_id):
"""Delete user in group."""
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
+ self.expected_success(204, resp.status)
return resp, body
def assign_user_role_on_project(self, project_id, user_id, role_id):
"""Add roles to a user on a project."""
resp, body = self.put('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
return resp, body
def assign_user_role_on_domain(self, domain_id, user_id, role_id):
"""Add roles to a user on a domain."""
resp, body = self.put('domains/%s/users/%s/roles/%s' %
(domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
return resp, body
def list_user_roles_on_project(self, project_id, user_id):
"""list roles of a user on a project."""
resp, body = self.get('projects/%s/users/%s/roles' %
(project_id, user_id))
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['roles']
@@ -337,6 +372,7 @@
"""list roles of a user on a domain."""
resp, body = self.get('domains/%s/users/%s/roles' %
(domain_id, user_id))
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['roles']
@@ -344,30 +380,35 @@
"""Delete role of a user on a project."""
resp, body = self.delete('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def revoke_role_from_user_on_domain(self, domain_id, user_id, role_id):
"""Delete role of a user on a domain."""
resp, body = self.delete('domains/%s/users/%s/roles/%s' %
(domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def assign_group_role_on_project(self, project_id, group_id, role_id):
"""Add roles to a user on a project."""
resp, body = self.put('projects/%s/groups/%s/roles/%s' %
(project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
return resp, body
def assign_group_role_on_domain(self, domain_id, group_id, role_id):
"""Add roles to a user on a domain."""
resp, body = self.put('domains/%s/groups/%s/roles/%s' %
(domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
return resp, body
def list_group_roles_on_project(self, project_id, group_id):
"""list roles of a user on a project."""
resp, body = self.get('projects/%s/groups/%s/roles' %
(project_id, group_id))
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['roles']
@@ -375,6 +416,7 @@
"""list roles of a user on a domain."""
resp, body = self.get('domains/%s/groups/%s/roles' %
(domain_id, group_id))
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['roles']
@@ -382,12 +424,14 @@
"""Delete role of a user on a project."""
resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
(project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def revoke_role_from_group_on_domain(self, domain_id, group_id, role_id):
"""Delete role of a user on a domain."""
resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
(domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def create_trust(self, trustor_user_id, trustee_user_id, project_id,
@@ -404,12 +448,14 @@
}
post_body = json.dumps({'trust': post_body})
resp, body = self.post('OS-TRUST/trusts', post_body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['trust']
def delete_trust(self, trust_id):
"""Deletes a trust."""
resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(204, resp.status)
return resp, body
def get_trusts(self, trustor_user_id=None, trustee_user_id=None):
@@ -422,18 +468,21 @@
% trustee_user_id)
else:
resp, body = self.get("OS-TRUST/trusts")
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['trusts']
def get_trust(self, trust_id):
"""GET trust."""
resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['trust']
def get_trust_roles(self, trust_id):
"""GET roles delegated by a trust."""
resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['roles']
@@ -441,6 +490,7 @@
"""GET role delegated by a trust."""
resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
% (trust_id, role_id))
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['role']
@@ -448,6 +498,10 @@
"""HEAD Check if role is delegated by a trust."""
resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
% (trust_id, role_id))
+ # This code needs to change to 200 when the keystone changes
+ # for bug 1334368 merge and check_trust_roles test is
+ # unskipped
+ self.expected_success(204, resp.status)
return resp, body
diff --git a/tempest/services/identity/v3/json/region_client.py b/tempest/services/identity/v3/json/region_client.py
new file mode 100644
index 0000000..c078765
--- /dev/null
+++ b/tempest/services/identity/v3/json/region_client.py
@@ -0,0 +1,80 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import urllib
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class RegionClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(RegionClientJSON, self).__init__(auth_provider)
+ self.service = CONF.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+ self.api_version = "v3"
+
+ def create_region(self, description, **kwargs):
+ """Create region."""
+ req_body = {
+ 'description': description,
+ }
+ if kwargs.get('parent_region_id'):
+ req_body['parent_region_id'] = kwargs.get('parent_region_id')
+ req_body = json.dumps({'region': req_body})
+ if kwargs.get('unique_region_id'):
+ resp, body = self.put(
+ 'regions/%s' % kwargs.get('unique_region_id'), req_body)
+ else:
+ resp, body = self.post('regions', req_body)
+ body = json.loads(body)
+ return resp, body['region']
+
+ def update_region(self, region_id, **kwargs):
+ """Updates a region."""
+ post_body = {}
+ if 'description' in kwargs:
+ post_body['description'] = kwargs.get('description')
+ if 'parent_region_id' in kwargs:
+ post_body['parent_region_id'] = kwargs.get('parent_region_id')
+ post_body = json.dumps({'region': post_body})
+ resp, body = self.patch('regions/%s' % region_id, post_body)
+ body = json.loads(body)
+ return resp, body['region']
+
+ def get_region(self, region_id):
+ """Get region."""
+ url = 'regions/%s' % region_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['region']
+
+ def list_regions(self, params=None):
+ """List regions."""
+ url = 'regions'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['regions']
+
+ def delete_region(self, region_id):
+ """Delete region."""
+ resp, body = self.delete('regions/%s' % region_id)
+ return resp, body
diff --git a/tempest/services/identity/v3/json/service_client.py b/tempest/services/identity/v3/json/service_client.py
index b66fb4a..82e8aad 100644
--- a/tempest/services/identity/v3/json/service_client.py
+++ b/tempest/services/identity/v3/json/service_client.py
@@ -42,6 +42,7 @@
}
patch_body = json.dumps({'service': patch_body})
resp, body = self.patch('services/%s' % service_id, patch_body)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['service']
@@ -49,6 +50,7 @@
"""Get Service."""
url = 'services/%s' % service_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['service']
@@ -62,10 +64,12 @@
}
body = json.dumps({'service': body_dict})
resp, body = self.post("services", body)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body["service"]
def delete_service(self, serv_id):
url = "services/" + serv_id
resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
return resp, body
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 35295d7..242b032 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -104,12 +104,13 @@
project_id=project_id,
domain_id=domain_id)
resp, body = self.post('users', str(common.Document(post_body)))
+ self.expected_success(201, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def update_user(self, user_id, name, **kwargs):
"""Updates a user."""
- resp, body = self.get_user(user_id)
+ _, body = self.get_user(user_id)
email = kwargs.get('email', body['email'])
en = kwargs.get('enabled', body['enabled'])
project_id = kwargs.get('project_id', body['project_id'])
@@ -125,30 +126,35 @@
enabled=str(en).lower())
resp, body = self.patch('users/%s' % user_id,
str(common.Document(update_user)))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def list_user_projects(self, user_id):
"""Lists the projects on which a user has roles assigned."""
resp, body = self.get('users/%s/projects' % user_id)
+ self.expected_success(200, resp.status)
body = self._parse_projects(etree.fromstring(body))
return resp, body
def get_users(self):
"""Get the list of users."""
resp, body = self.get("users")
+ self.expected_success(200, resp.status)
body = self._parse_array(etree.fromstring(body))
return resp, body
def get_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def delete_user(self, user_id):
"""Deletes a User."""
resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
return resp, body
def create_project(self, name, **kwargs):
@@ -164,12 +170,14 @@
name=name)
resp, body = self.post('projects',
str(common.Document(post_body)))
+ self.expected_success(201, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def list_projects(self):
"""Get the list of projects."""
resp, body = self.get("projects")
+ self.expected_success(200, resp.status)
body = self._parse_projects(etree.fromstring(body))
return resp, body
@@ -188,18 +196,21 @@
domain_id=domain_id)
resp, body = self.patch('projects/%s' % project_id,
str(common.Document(post_body)))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def get_project(self, project_id):
"""GET a Project."""
resp, body = self.get("projects/%s" % project_id)
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def delete_project(self, project_id):
"""Delete a project."""
resp, body = self.delete('projects/%s' % str(project_id))
+ self.expected_success(204, resp.status)
return resp, body
def create_role(self, name):
@@ -208,18 +219,21 @@
xmlns=XMLNS,
name=name)
resp, body = self.post('roles', str(common.Document(post_body)))
+ self.expected_success(201, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def get_role(self, role_id):
"""GET a Role."""
resp, body = self.get('roles/%s' % str(role_id))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def list_roles(self):
"""Get the list of Roles."""
resp, body = self.get("roles")
+ self.expected_success(200, resp.status)
body = self._parse_roles(etree.fromstring(body))
return resp, body
@@ -230,18 +244,21 @@
name=name)
resp, body = self.patch('roles/%s' % str(role_id),
str(common.Document(post_body)))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def delete_role(self, role_id):
"""Delete a role."""
resp, body = self.delete('roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
return resp, body
def assign_user_role(self, project_id, user_id, role_id):
"""Add roles to a user on a tenant."""
resp, body = self.put('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id), '')
+ self.expected_success(204, resp.status)
return resp, body
def create_domain(self, name, **kwargs):
@@ -254,23 +271,26 @@
description=description,
enabled=str(en).lower())
resp, body = self.post('domains', str(common.Document(post_body)))
+ self.expected_success(201, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def list_domains(self):
"""Get the list of domains."""
resp, body = self.get("domains")
+ self.expected_success(200, resp.status)
body = self._parse_domains(etree.fromstring(body))
return resp, body
def delete_domain(self, domain_id):
"""Delete a domain."""
resp, body = self.delete('domains/%s' % domain_id)
+ self.expected_success(204, resp.status)
return resp, body
def update_domain(self, domain_id, **kwargs):
"""Updates a domain."""
- resp, body = self.get_domain(domain_id)
+ _, body = self.get_domain(domain_id)
description = kwargs.get('description', body['description'])
en = kwargs.get('enabled', body['enabled'])
name = kwargs.get('name', body['name'])
@@ -281,12 +301,14 @@
enabled=str(en).lower())
resp, body = self.patch('domains/%s' % domain_id,
str(common.Document(post_body)))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def get_domain(self, domain_id):
"""Get Domain details."""
resp, body = self.get('domains/%s' % domain_id)
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
@@ -296,6 +318,7 @@
'Accept': 'application/xml',
'X-Subject-Token': resp_token}
resp, body = self.get("auth/tokens", headers=headers)
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
@@ -303,6 +326,7 @@
"""Delete a Given Token."""
headers = {'X-Subject-Token': resp_token}
resp, body = self.delete("auth/tokens", headers=headers)
+ self.expected_success(204, resp.status)
return resp, body
def create_group(self, name, **kwargs):
@@ -317,18 +341,20 @@
domain_id=domain_id,
project_id=project_id)
resp, body = self.post('groups', str(common.Document(post_body)))
+ self.expected_success(201, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def get_group(self, group_id):
"""Get group details."""
resp, body = self.get('groups/%s' % group_id)
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def update_group(self, group_id, **kwargs):
"""Updates a group."""
- resp, body = self.get_group(group_id)
+ _, body = self.get_group(group_id)
name = kwargs.get('name', body['name'])
description = kwargs.get('description', body['description'])
post_body = common.Element("group",
@@ -337,52 +363,61 @@
description=description)
resp, body = self.patch('groups/%s' % group_id,
str(common.Document(post_body)))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def delete_group(self, group_id):
"""Delete a group."""
resp, body = self.delete('groups/%s' % group_id)
+ self.expected_success(204, resp.status)
return resp, body
def add_group_user(self, group_id, user_id):
"""Add user into group."""
resp, body = self.put('groups/%s/users/%s' % (group_id, user_id), '')
+ self.expected_success(204, resp.status)
return resp, body
def list_group_users(self, group_id):
"""List users in group."""
resp, body = self.get('groups/%s/users' % group_id)
+ self.expected_success(200, resp.status)
body = self._parse_group_users(etree.fromstring(body))
return resp, body
def list_user_groups(self, user_id):
"""Lists the groups which a user belongs to."""
resp, body = self.get('users/%s/groups' % user_id)
+ self.expected_success(200, resp.status)
body = self._parse_groups(etree.fromstring(body))
return resp, body
def delete_group_user(self, group_id, user_id):
"""Delete user in group."""
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
+ self.expected_success(204, resp.status)
return resp, body
def assign_user_role_on_project(self, project_id, user_id, role_id):
"""Add roles to a user on a project."""
resp, body = self.put('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id), '')
+ self.expected_success(204, resp.status)
return resp, body
def assign_user_role_on_domain(self, domain_id, user_id, role_id):
"""Add roles to a user on a domain."""
resp, body = self.put('domains/%s/users/%s/roles/%s' %
(domain_id, user_id, role_id), '')
+ self.expected_success(204, resp.status)
return resp, body
def list_user_roles_on_project(self, project_id, user_id):
"""list roles of a user on a project."""
resp, body = self.get('projects/%s/users/%s/roles' %
(project_id, user_id))
+ self.expected_success(200, resp.status)
body = self._parse_roles(etree.fromstring(body))
return resp, body
@@ -390,6 +425,7 @@
"""list roles of a user on a domain."""
resp, body = self.get('domains/%s/users/%s/roles' %
(domain_id, user_id))
+ self.expected_success(200, resp.status)
body = self._parse_roles(etree.fromstring(body))
return resp, body
@@ -397,30 +433,35 @@
"""Delete role of a user on a project."""
resp, body = self.delete('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def revoke_role_from_user_on_domain(self, domain_id, user_id, role_id):
"""Delete role of a user on a domain."""
resp, body = self.delete('domains/%s/users/%s/roles/%s' %
(domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def assign_group_role_on_project(self, project_id, group_id, role_id):
"""Add roles to a user on a project."""
resp, body = self.put('projects/%s/groups/%s/roles/%s' %
(project_id, group_id, role_id), '')
+ self.expected_success(204, resp.status)
return resp, body
def assign_group_role_on_domain(self, domain_id, group_id, role_id):
"""Add roles to a user on a domain."""
resp, body = self.put('domains/%s/groups/%s/roles/%s' %
(domain_id, group_id, role_id), '')
+ self.expected_success(204, resp.status)
return resp, body
def list_group_roles_on_project(self, project_id, group_id):
"""list roles of a user on a project."""
resp, body = self.get('projects/%s/groups/%s/roles' %
(project_id, group_id))
+ self.expected_success(200, resp.status)
body = self._parse_roles(etree.fromstring(body))
return resp, body
@@ -428,6 +469,7 @@
"""list roles of a user on a domain."""
resp, body = self.get('domains/%s/groups/%s/roles' %
(domain_id, group_id))
+ self.expected_success(200, resp.status)
body = self._parse_roles(etree.fromstring(body))
return resp, body
@@ -435,12 +477,14 @@
"""Delete role of a user on a project."""
resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
(project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
def revoke_role_from_group_on_domain(self, domain_id, group_id, role_id):
"""Delete role of a user on a domain."""
resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
(domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
return resp, body
diff --git a/tempest/services/identity/v3/xml/region_client.py b/tempest/services/identity/v3/xml/region_client.py
new file mode 100644
index 0000000..f854138
--- /dev/null
+++ b/tempest/services/identity/v3/xml/region_client.py
@@ -0,0 +1,120 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common import http
+from tempest.common import rest_client
+from tempest.common import xml_utils as common
+from tempest import config
+
+CONF = config.CONF
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class RegionClientXML(rest_client.RestClient):
+ TYPE = "xml"
+
+ def __init__(self, auth_provider):
+ super(RegionClientXML, self).__init__(auth_provider)
+ self.service = CONF.identity.catalog_type
+ self.region_url = 'adminURL'
+ self.api_version = "v3"
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "region":
+ array.append(common.xml_to_json(child))
+ return array
+
+ def _parse_body(self, body):
+ json = common.xml_to_json(body)
+ return json
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, wait=None):
+ """Overriding the existing HTTP request in super class RestClient."""
+ if extra_headers:
+ try:
+ headers.update(self.get_headers())
+ except (ValueError, TypeError):
+ headers = self.get_headers()
+ dscv = CONF.identity.disable_ssl_certificate_validation
+ self.http_obj = http.ClosingHttp(
+ disable_ssl_certificate_validation=dscv)
+ return super(RegionClientXML, self).request(method, url,
+ extra_headers,
+ headers=headers,
+ body=body)
+
+ def create_region(self, description, **kwargs):
+ """Create region."""
+ create_region = common.Element("region",
+ xmlns=XMLNS,
+ description=description)
+ if 'parent_region_id' in kwargs:
+ create_region.append(common.Element(
+ 'parent_region_id', kwargs.get('parent_region_id')))
+ if 'unique_region_id' in kwargs:
+ resp, body = self.put(
+ 'regions/%s' % kwargs.get('unique_region_id'),
+ str(common.Document(create_region)))
+ else:
+ resp, body = self.post('regions',
+ str(common.Document(create_region)))
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def update_region(self, region_id, **kwargs):
+ """Updates an region with given parameters.
+ """
+ description = kwargs.get('description', None)
+ update_region = common.Element("region",
+ xmlns=XMLNS,
+ description=description)
+ if 'parent_region_id' in kwargs:
+ update_region.append(common.Element('parent_region_id',
+ kwargs.get('parent_region_id')))
+
+ resp, body = self.patch('regions/%s' % str(region_id),
+ str(common.Document(update_region)))
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_region(self, region_id):
+ """Get Region."""
+ url = 'regions/%s' % region_id
+ resp, body = self.get(url)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_regions(self, params=None):
+ """Get the list of regions."""
+ url = 'regions'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def delete_region(self, region_id):
+ """Delete region."""
+ resp, body = self.delete('regions/%s' % region_id)
+ return resp, body
diff --git a/tempest/services/identity/v3/xml/service_client.py b/tempest/services/identity/v3/xml/service_client.py
index 37ed892..3beeb89 100644
--- a/tempest/services/identity/v3/xml/service_client.py
+++ b/tempest/services/identity/v3/xml/service_client.py
@@ -51,6 +51,7 @@
type=type)
resp, body = self.patch('services/%s' % service_id,
str(common.Document(update_service)))
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
@@ -58,6 +59,7 @@
"""Get Service."""
url = 'services/%s' % service_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
@@ -68,10 +70,12 @@
description=description,
type=serv_type)
resp, body = self.post("services", str(common.Document(post_body)))
+ self.expected_success(201, resp.status)
body = self._parse_body(etree.fromstring(body))
return resp, body
def delete_service(self, serv_id):
url = "services/" + serv_id
resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
return resp, body
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index b213c1a..4ada46c 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -29,11 +29,13 @@
create_role = xml.Element("role", xmlns=XMLNS, name=name)
resp, body = self.post('OS-KSADM/roles',
str(xml.Document(create_role)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_role(self, role_id):
"""Get a role by its id."""
resp, body = self.get('OS-KSADM/roles/%s' % role_id)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def create_tenant(self, name, **kwargs):
@@ -50,16 +52,18 @@
description=kwargs.get('description', ''),
enabled=str(en).lower())
resp, body = self.post('tenants', str(xml.Document(create_tenant)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def list_tenants(self):
"""Returns tenants."""
resp, body = self.get('tenants')
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def update_tenant(self, tenant_id, **kwargs):
"""Updates a tenant."""
- resp, body = self.get_tenant(tenant_id)
+ _, body = self.get_tenant(tenant_id)
name = kwargs.get('name', body['name'])
desc = kwargs.get('description', body['description'])
en = kwargs.get('enabled', body['enabled'])
@@ -72,6 +76,7 @@
resp, body = self.post('tenants/%s' % tenant_id,
str(xml.Document(update_tenant)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def create_user(self, name, password, tenant_id, email, **kwargs):
@@ -87,6 +92,7 @@
create_user.add_attr('enabled', str(kwargs['enabled']).lower())
resp, body = self.post('users', str(xml.Document(create_user)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def update_user(self, user_id, **kwargs):
@@ -97,6 +103,7 @@
resp, body = self.put('users/%s' % user_id,
str(xml.Document(update_user)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def enable_disable_user(self, user_id, enabled):
@@ -104,6 +111,7 @@
enable_user = xml.Element("user", enabled=str(enabled).lower())
resp, body = self.put('users/%s/enabled' % user_id,
str(xml.Document(enable_user)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def create_service(self, name, service_type, **kwargs):
@@ -116,6 +124,7 @@
description=kwargs.get('description'))
resp, body = self.post('OS-KSADM/services',
str(xml.Document(create_service)))
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def update_user_password(self, user_id, new_pass):
@@ -125,6 +134,13 @@
password=new_pass)
resp, body = self.put('users/%s/OS-KSADM/password' % user_id,
str(xml.Document(put_body)))
+ self.expected_success(200, resp.status)
+ return resp, self._parse_resp(body)
+
+ def list_extensions(self):
+ """List all the extensions."""
+ resp, body = self.get('/extensions')
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index e22cd9c..4a7c163 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -22,6 +22,7 @@
from tempest.common import glance_http
from tempest.common import rest_client
+from tempest.common.utils import misc as misc_utils
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -153,6 +154,7 @@
return self._create_with_data(headers, kwargs.get('data'))
resp, body = self.post('v1/images', None, headers)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body['image']
@@ -176,12 +178,15 @@
url = 'v1/images/%s' % image_id
resp, body = self.put(url, data, headers)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['image']
def delete_image(self, image_id):
url = 'v1/images/%s' % image_id
- return self.delete(url)
+ resp, body = self.delete(url)
+ self.expected_success(200, resp.status)
+ return resp, body
def image_list(self, **kwargs):
url = 'v1/images'
@@ -190,6 +195,7 @@
url += '?%s' % urllib.urlencode(kwargs)
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['images']
@@ -210,18 +216,21 @@
url += '?%s' % urllib.urlencode(kwargs)
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body['images']
def get_image_meta(self, image_id):
url = 'v1/images/%s' % image_id
resp, __ = self.head(url)
+ self.expected_success(200, resp.status)
body = self._image_meta_from_headers(resp)
return resp, body
def get_image(self, image_id):
url = 'v1/images/%s' % image_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
return resp, body
def is_resource_deleted(self, id):
@@ -234,12 +243,14 @@
def get_image_membership(self, image_id):
url = 'v1/images/%s/members' % image_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body
def get_shared_images(self, member_id):
url = 'v1/shared-images/%s' % member_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body
@@ -249,20 +260,15 @@
if can_share:
body = json.dumps({'member': {'can_share': True}})
resp, __ = self.put(url, body)
+ self.expected_success(204, resp.status)
return resp
def delete_member(self, member_id, image_id):
url = 'v1/images/%s/members/%s' % (image_id, member_id)
resp, __ = self.delete(url)
+ self.expected_success(204, resp.status)
return resp
- def replace_membership_list(self, image_id, member_list):
- url = 'v1/images/%s/members' % image_id
- body = json.dumps({'membership': member_list})
- resp, data = self.put(url, body)
- data = json.loads(data)
- return resp, data
-
# NOTE(afazekas): just for the wait function
def _get_image_status(self, image_id):
resp, meta = self.get_image_meta(image_id)
@@ -292,6 +298,9 @@
'while waiting for %s, '
'but we got %s.' %
(self.build_timeout, status, value))
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
time.sleep(self.build_interval)
old_value = value
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index 201869e..c420df9 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -61,6 +61,7 @@
headers = {"Content-Type": "application/openstack-images-v2.0"
"-json-patch"}
resp, body = self.patch('v2/images/%s' % image_id, data, headers)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def create_image(self, name, container_format, disk_format, **kwargs):
@@ -81,12 +82,14 @@
self._validate_schema(data)
resp, body = self.post('v2/images', data)
+ self.expected_success(201, resp.status)
body = json.loads(body)
return resp, body
def delete_image(self, image_id):
url = 'v2/images/%s' % image_id
- self.delete(url)
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
def image_list(self, params=None):
url = 'v2/images'
@@ -95,6 +98,7 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
self._validate_schema(body, type='images')
return resp, body['images']
@@ -102,6 +106,7 @@
def get_image(self, image_id):
url = 'v2/images/%s' % image_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body
@@ -117,36 +122,40 @@
headers = {'Content-Type': 'application/octet-stream'}
resp, body = self.http.raw_request('PUT', url, headers=headers,
body=data)
+ self.expected_success(204, resp.status)
return resp, body
def get_image_file(self, image_id):
url = 'v2/images/%s/file' % image_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
return resp, body
def add_image_tag(self, image_id, tag):
url = 'v2/images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
+ self.expected_success(204, resp.status)
return resp, body
def delete_image_tag(self, image_id, tag):
url = 'v2/images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
return resp
def get_image_membership(self, image_id):
url = 'v2/images/%s/members' % image_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
- self.expected_success(200, resp)
return resp, body
def add_member(self, image_id, member_id):
url = 'v2/images/%s/members' % image_id
data = json.dumps({'member': member_id})
resp, body = self.post(url, data)
+ self.expected_success(200, resp.status)
body = json.loads(body)
- self.expected_success(200, resp)
return resp, body
def update_member_status(self, image_id, member_id, status):
@@ -154,24 +163,25 @@
url = 'v2/images/%s/members/%s' % (image_id, member_id)
data = json.dumps({'status': status})
resp, body = self.put(url, data)
+ self.expected_success(200, resp.status)
body = json.loads(body)
- self.expected_success(200, resp)
return resp, body
def get_member(self, image_id, member_id):
url = 'v2/images/%s/members/%s' % (image_id, member_id)
resp, body = self.get(url)
- self.expected_success(200, resp)
+ self.expected_success(200, resp.status)
return resp, json.loads(body)
def remove_member(self, image_id, member_id):
url = 'v2/images/%s/members/%s' % (image_id, member_id)
resp, _ = self.delete(url)
- self.expected_success(204, resp)
+ self.expected_success(204, resp.status)
return resp
def get_schema(self, schema):
url = 'v2/schemas/%s' % schema
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = json.loads(body)
return resp, body
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 2a797b2..4ee8302 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -28,8 +28,10 @@
'vips': 'lb',
'health_monitors': 'lb',
'members': 'lb',
+ 'ipsecpolicies': 'vpn',
'vpnservices': 'vpn',
'ikepolicies': 'vpn',
+ 'ipsecpolicies': 'vpn',
'metering_labels': 'metering',
'metering_label_rules': 'metering',
'firewall_rules': 'fw',
@@ -46,7 +48,9 @@
resource_plural_map = {
'security_groups': 'security_groups',
'security_group_rules': 'security_group_rules',
+ 'ipsecpolicy': 'ipsecpolicies',
'ikepolicy': 'ikepolicies',
+ 'ipsecpolicy': 'ipsecpolicies',
'quotas': 'quotas',
'firewall_policy': 'firewall_policies'
}
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a7a6b2c..22cc948 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -38,7 +38,7 @@
return _root_tag_fetcher_and_xml_to_json_parse(body)
def serialize(self, body):
- #TODO(enikanorov): implement better json to xml conversion
+ # TODO(enikanorov): implement better json to xml conversion
# expecting the dict with single key
root = body.keys()[0]
post_body = common.Element(root)
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index f3f4eb6..b2f8205 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -13,7 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import httplib
import urllib
+import urlparse
from tempest.common import http
from tempest.common import rest_client
@@ -143,6 +145,31 @@
resp, body = self.put(url, data)
return resp, body
+ def put_object_with_chunk(self, container, name, contents, chunk_size):
+ """
+ Put an object with Transfer-Encoding header
+ """
+ if self.base_url is None:
+ self._set_auth()
+
+ headers = {'Transfer-Encoding': 'chunked'}
+ if self.token:
+ headers['X-Auth-Token'] = self.token
+
+ conn = put_object_connection(self.base_url, container, name, contents,
+ chunk_size, headers)
+
+ resp = conn.getresponse()
+ body = resp.read()
+
+ resp_headers = {}
+ for header, value in resp.getheaders():
+ resp_headers[header.lower()] = value
+
+ self._error_checker('PUT', None, headers, contents, resp, body)
+
+ return resp.status, resp.reason, resp_headers
+
class ObjectClientCustomizedHeader(rest_client.RestClient):
@@ -220,3 +247,89 @@
url = "%s/%s" % (str(container), str(object_name))
resp, body = self.delete(url, headers=headers)
return resp, body
+
+ def create_object_continue(self, container, object_name,
+ data, metadata=None):
+ """Create storage object."""
+ headers = {}
+ if metadata:
+ for key in metadata:
+ headers[str(key)] = metadata[key]
+
+ if not data:
+ headers['content-length'] = '0'
+
+ if self.base_url is None:
+ self._set_auth()
+ headers['X-Auth-Token'] = self.token
+
+ conn = put_object_connection(self.base_url, str(container),
+ str(object_name), data, None, headers)
+
+ response = conn.response_class(conn.sock,
+ strict=conn.strict,
+ method=conn._method)
+ version, status, reason = response._read_status()
+ resp = {'version': version,
+ 'status': str(status),
+ 'reason': reason}
+
+ return resp
+
+
+def put_object_connection(base_url, container, name, contents=None,
+ chunk_size=65536, headers=None, query_string=None):
+ """
+ Helper function to make connection to put object with httplib
+ :param base_url: base_url of an object client
+ :param container: container name that the object is in
+ :param name: object name to put
+ :param contents: a string or a file like object to read object data
+ from; if None, a zero-byte put will be done
+ :param chunk_size: chunk size of data to write; it defaults to 65536;
+ used only if the the contents object has a 'read'
+ method, eg. file-like objects, ignored otherwise
+ :param headers: additional headers to include in the request, if any
+ :param query_string: if set will be appended with '?' to generated path
+ """
+ parsed = urlparse.urlparse(base_url)
+ if parsed.scheme == 'https':
+ conn = httplib.HTTPSConnection(parsed.netloc)
+ else:
+ conn = httplib.HTTPConnection(parsed.netloc)
+ path = str(parsed.path) + "/"
+ path += "%s/%s" % (str(container), str(name))
+
+ if query_string:
+ path += '?' + query_string
+ if headers:
+ headers = dict(headers)
+ else:
+ headers = {}
+ if hasattr(contents, 'read'):
+ conn.putrequest('PUT', path)
+ for header, value in headers.iteritems():
+ conn.putheader(header, value)
+ if 'Content-Length' not in headers:
+ if 'Transfer-Encoding' not in headers:
+ conn.putheader('Transfer-Encoding', 'chunked')
+ conn.endheaders()
+ chunk = contents.read(chunk_size)
+ while chunk:
+ conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
+ chunk = contents.read(chunk_size)
+ conn.send('0\r\n\r\n')
+ else:
+ conn.endheaders()
+ left = headers['Content-Length']
+ while left > 0:
+ size = chunk_size
+ if size > left:
+ size = left
+ chunk = contents.read(size)
+ conn.send(chunk)
+ left -= len(chunk)
+ else:
+ conn.request('PUT', path, contents, headers)
+
+ return conn
diff --git a/tempest/services/queuing/json/queuing_client.py b/tempest/services/queuing/json/queuing_client.py
index e5978f5..031c9c6 100644
--- a/tempest/services/queuing/json/queuing_client.py
+++ b/tempest/services/queuing/json/queuing_client.py
@@ -14,11 +14,14 @@
# limitations under the License.
import json
+import urllib
from tempest.api_schema.queuing.v1 import queues as queues_schema
from tempest.common import rest_client
+from tempest.common.utils import data_utils
from tempest import config
+
CONF = config.CONF
@@ -30,11 +33,16 @@
self.version = '1'
self.uri_prefix = 'v{0}'.format(self.version)
+ client_id = data_utils.rand_uuid_hex()
+ self.headers = {'Client-ID': client_id}
+
def list_queues(self):
uri = '{0}/queues'.format(self.uri_prefix)
resp, body = self.get(uri)
- body = json.loads(body)
- self.validate_response(queues_schema.list_queues, resp, body)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.list_queues, resp, body)
return resp, body
def create_queue(self, queue_name):
@@ -74,3 +82,80 @@
uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
resp, body = self.put(uri, body=json.dumps(rbody))
return resp, body
+
+ def post_messages(self, queue_name, rbody):
+ uri = '{0}/queues/{1}/messages'.format(self.uri_prefix, queue_name)
+ resp, body = self.post(uri, body=json.dumps(rbody),
+ extra_headers=True,
+ headers=self.headers)
+
+ body = json.loads(body)
+ return resp, body
+
+ def list_messages(self, queue_name):
+ uri = '{0}/queues/{1}/messages?echo=True'.format(self.uri_prefix,
+ queue_name)
+ resp, body = self.get(uri, extra_headers=True, headers=self.headers)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.list_messages, resp, body)
+
+ return resp, body
+
+ def get_single_message(self, message_uri):
+ resp, body = self.get(message_uri, extra_headers=True,
+ headers=self.headers)
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.get_single_message, resp,
+ body)
+ return resp, body
+
+ def get_multiple_messages(self, message_uri):
+ resp, body = self.get(message_uri, extra_headers=True,
+ headers=self.headers)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.get_multiple_messages,
+ resp,
+ body)
+
+ return resp, body
+
+ def delete_messages(self, message_uri):
+ resp, body = self.delete(message_uri)
+ assert(resp['status'] == '204')
+ return resp, body
+
+ def post_claims(self, queue_name, rbody, url_params=False):
+ uri = '{0}/queues/{1}/claims'.format(self.uri_prefix, queue_name)
+ if url_params:
+ uri += '?%s' % urllib.urlencode(url_params)
+
+ resp, body = self.post(uri, body=json.dumps(rbody),
+ extra_headers=True,
+ headers=self.headers)
+
+ body = json.loads(body)
+ self.validate_response(queues_schema.claim_messages, resp, body)
+ return resp, body
+
+ def query_claim(self, claim_uri):
+ resp, body = self.get(claim_uri)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.query_claim, resp, body)
+ return resp, body
+
+ def update_claim(self, claim_uri, rbody):
+ resp, body = self.patch(claim_uri, body=json.dumps(rbody))
+ assert(resp['status'] == '204')
+ return resp, body
+
+ def release_claim(self, claim_uri):
+ resp, body = self.delete(claim_uri)
+ assert(resp['status'] == '204')
+ return resp, body
diff --git a/tempest/services/telemetry/telemetry_client_base.py b/tempest/services/telemetry/telemetry_client_base.py
index a073f54..b45c239 100644
--- a/tempest/services/telemetry/telemetry_client_base.py
+++ b/tempest/services/telemetry/telemetry_client_base.py
@@ -101,17 +101,17 @@
uri += "?%s" % urllib.urlencode(uri_dict)
return self.get(uri)
- def list_resources(self):
+ def list_resources(self, query=None):
uri = '%s/resources' % self.uri_prefix
- return self.get(uri)
+ return self.helper_list(uri, query)
- def list_meters(self):
+ def list_meters(self, query=None):
uri = '%s/meters' % self.uri_prefix
- return self.get(uri)
+ return self.helper_list(uri, query)
- def list_alarms(self):
+ def list_alarms(self, query=None):
uri = '%s/alarms' % self.uri_prefix
- return self.get(uri)
+ return self.helper_list(uri, query)
def list_statistics(self, meter, period=None, query=None):
uri = "%s/meters/%s/statistics" % (self.uri_prefix, meter)
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/json/admin/volume_types_client.py
index c9c0582..65ecc67 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/json/admin/volume_types_client.py
@@ -18,6 +18,7 @@
from tempest.common import rest_client
from tempest import config
+from tempest import exceptions
CONF = config.CONF
@@ -34,6 +35,26 @@
self.build_interval = CONF.volume.build_interval
self.build_timeout = CONF.volume.build_timeout
+ def is_resource_deleted(self, resource):
+ # to use this method self.resource must be defined to respective value
+ # Resource is a dictionary containing resource id and type
+ # Resource : {"id" : resource_id
+ # "type": resource_type}
+ try:
+ if resource['type'] == "volume-type":
+ self.get_volume_type(resource['id'])
+ elif resource['type'] == "encryption-type":
+ resp, body = self.get_encryption_type(resource['id'])
+ assert 200 == resp.status
+ if not body:
+ return True
+ else:
+ msg = (" resource value is either not defined or incorrect.")
+ raise exceptions.UnprocessableEntity(msg)
+ except exceptions.NotFound:
+ return True
+ return False
+
def list_volume_types(self, params=None):
"""List all the volume_types created."""
url = 'types'
@@ -150,3 +171,7 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
return resp, body['encryption']
+
+ def delete_encryption_type(self, vol_type_id):
+ """Delete the encryption type for the specified volume-type."""
+ return self.delete("/types/%s/encryption/provider" % str(vol_type_id))
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index b55a037..6c97497 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -24,13 +24,13 @@
CONF = config.CONF
-class VolumesClientJSON(rest_client.RestClient):
+class BaseVolumesClientJSON(rest_client.RestClient):
"""
- Client class to send CRUD Volume API requests to a Cinder endpoint
+ Base client class to send CRUD Volume API requests to a Cinder endpoint
"""
def __init__(self, auth_provider):
- super(VolumesClientJSON, self).__init__(auth_provider)
+ super(BaseVolumesClientJSON, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
self.build_interval = CONF.volume.build_interval
@@ -72,7 +72,8 @@
Creates a new Volume.
size: Size of volume in GB.
Following optional keyword arguments are accepted:
- display_name: Optional Volume Name.
+ display_name: Optional Volume Name(only for V1).
+ name: Optional Volume Name(only for V2).
metadata: A dictionary of values to be used as metadata.
volume_type: Optional Name of volume_type for the volume
snapshot_id: When specified the volume is created from this snapshot
@@ -150,7 +151,6 @@
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
- volume_name = body['display_name']
volume_status = body['status']
start = int(time.time())
@@ -162,9 +162,10 @@
raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
if int(time.time()) - start >= self.build_timeout:
- message = ('Volume %s failed to reach %s status within '
- 'the required time (%s s).' %
- (volume_name, status, self.build_timeout))
+ message = 'Volume %s failed to reach %s status within '\
+ 'the required time (%s s).' % (volume_id,
+ status,
+ self.build_timeout)
raise exceptions.TimeoutException(message)
def is_resource_deleted(self, id):
@@ -297,3 +298,9 @@
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.delete(url)
return resp, body
+
+
+class VolumesClientJSON(BaseVolumesClientJSON):
+ """
+ Client class to send CRUD Volume V1 API requests to a Cinder endpoint
+ """
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index df20a2a..1f16ead 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -13,18 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-import time
-import urllib
-
-from tempest.common import rest_client
-from tempest import config
-from tempest import exceptions
-
-CONF = config.CONF
+from tempest.services.volume.json import volumes_client
-class VolumesV2ClientJSON(rest_client.RestClient):
+class VolumesV2ClientJSON(volumes_client.BaseVolumesClientJSON):
"""
Client class to send CRUD Volume V2 API requests to a Cinder endpoint
"""
@@ -33,268 +25,3 @@
super(VolumesV2ClientJSON, self).__init__(auth_provider)
self.api_version = "v2"
- self.service = CONF.volume.catalog_type
- self.build_interval = CONF.volume.build_interval
- self.build_timeout = CONF.volume.build_timeout
-
- def get_attachment_from_volume(self, volume):
- """Return the element 'attachment' from input volumes."""
- return volume['attachments'][0]
-
- def list_volumes(self, params=None):
- """List all the volumes created."""
- url = 'volumes'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['volumes']
-
- def list_volumes_with_detail(self, params=None):
- """List the details of all volumes."""
- url = 'volumes/detail'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['volumes']
-
- def get_volume(self, volume_id):
- """Returns the details of a single volume."""
- url = "volumes/%s" % str(volume_id)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['volume']
-
- def create_volume(self, size=None, **kwargs):
- """
- Creates a new Volume.
- size: Size of volume in GB.
- Following optional keyword arguments are accepted:
- name: Optional Volume Name.
- metadata: A dictionary of values to be used as metadata.
- volume_type: Optional Name of volume_type for the volume
- snapshot_id: When specified the volume is created from this snapshot
- imageRef: When specified the volume is created from this image
- """
- # for bug #1293885:
- # If no size specified, read volume size from CONF
- if size is None:
- size = CONF.volume.volume_size
- post_body = {'size': size}
- post_body.update(kwargs)
- post_body = json.dumps({'volume': post_body})
- resp, body = self.post('volumes', post_body)
- body = json.loads(body)
- return resp, body['volume']
-
- def update_volume(self, volume_id, **kwargs):
- """Updates the Specified Volume."""
- put_body = json.dumps({'volume': kwargs})
- resp, body = self.put('volumes/%s' % volume_id, put_body)
- body = json.loads(body)
- return resp, body['volume']
-
- def delete_volume(self, volume_id):
- """Deletes the Specified Volume."""
- return self.delete("volumes/%s" % str(volume_id))
-
- def upload_volume(self, volume_id, image_name, disk_format):
- """Uploads a volume in Glance."""
- post_body = {
- 'image_name': image_name,
- 'disk_format': disk_format
- }
- post_body = json.dumps({'os-volume_upload_image': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- 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 = {
- 'instance_uuid': instance_uuid,
- 'mountpoint': mountpoint,
- }
- post_body = json.dumps({'os-attach': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def detach_volume(self, volume_id):
- """Detaches a volume from an instance."""
- post_body = {}
- post_body = json.dumps({'os-detach': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def reserve_volume(self, volume_id):
- """Reserves a volume."""
- post_body = {}
- post_body = json.dumps({'os-reserve': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def unreserve_volume(self, volume_id):
- """Restore a reserved volume ."""
- post_body = {}
- post_body = json.dumps({'os-unreserve': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status."""
- resp, body = self.get_volume(volume_id)
- volume_name = body['name']
- volume_status = body['status']
- start = int(time.time())
-
- while volume_status != status:
- time.sleep(self.build_interval)
- resp, body = self.get_volume(volume_id)
- volume_status = body['status']
- if volume_status == 'error':
- raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
-
- if int(time.time()) - start >= self.build_timeout:
- message = ('Volume %s failed to reach %s status within '
- 'the required time (%s s).' %
- (volume_name, status, self.build_timeout))
- raise exceptions.TimeoutException(message)
-
- def is_resource_deleted(self, id):
- try:
- self.get_volume(id)
- except exceptions.NotFound:
- return True
- return False
-
- def extend_volume(self, volume_id, extend_size):
- """Extend a volume."""
- post_body = {
- 'new_size': extend_size
- }
- post_body = json.dumps({'os-extend': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def reset_volume_status(self, volume_id, status):
- """Reset the Specified Volume's Status."""
- post_body = json.dumps({'os-reset_status': {"status": status}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def volume_begin_detaching(self, volume_id):
- """Volume Begin Detaching."""
- post_body = json.dumps({'os-begin_detaching': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def volume_roll_detaching(self, volume_id):
- """Volume Roll Detaching."""
- post_body = json.dumps({'os-roll_detaching': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def create_volume_transfer(self, vol_id, name=None):
- """Create a volume transfer."""
- post_body = {
- 'volume_id': vol_id
- }
- if name:
- post_body['name'] = name
- post_body = json.dumps({'transfer': post_body})
- resp, body = self.post('os-volume-transfer', post_body)
- body = json.loads(body)
- return resp, body['transfer']
-
- def get_volume_transfer(self, transfer_id):
- """Returns the details of a volume transfer."""
- url = "os-volume-transfer/%s" % str(transfer_id)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['transfer']
-
- def list_volume_transfers(self, params=None):
- """List all the volume transfers created."""
- url = 'os-volume-transfer'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['transfers']
-
- def delete_volume_transfer(self, transfer_id):
- """Delete a volume transfer."""
- return self.delete("os-volume-transfer/%s" % str(transfer_id))
-
- def accept_volume_transfer(self, transfer_id, transfer_auth_key):
- """Accept a volume transfer."""
- post_body = {
- 'auth_key': transfer_auth_key,
- }
- url = 'os-volume-transfer/%s/accept' % transfer_id
- post_body = json.dumps({'accept': post_body})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- return resp, body['transfer']
-
- def update_volume_readonly(self, volume_id, readonly):
- """Update the Specified Volume readonly."""
- post_body = {
- 'readonly': readonly
- }
- post_body = json.dumps({'os-update_readonly_flag': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def force_delete_volume(self, volume_id):
- """Force Delete Volume."""
- post_body = json.dumps({'os-force_delete': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume."""
- put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.post(url, put_body)
- body = json.loads(body)
- return resp, body['metadata']
-
- def get_volume_metadata(self, volume_id):
- """Get metadata of the volume."""
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['metadata']
-
- def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume."""
- put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- return resp, body['metadata']
-
- def update_volume_metadata_item(self, volume_id, id, meta_item):
- """Update metadata item for the volume."""
- put_body = json.dumps({'meta': meta_item})
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- return resp, body['meta']
-
- def delete_volume_metadata_item(self, volume_id, id):
- """Delete metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.delete(url)
- return resp, body
diff --git a/tempest/services/volume/v2/xml/volumes_client.py b/tempest/services/volume/v2/xml/volumes_client.py
index 1fdaf19..c1bcf6e 100644
--- a/tempest/services/volume/v2/xml/volumes_client.py
+++ b/tempest/services/volume/v2/xml/volumes_client.py
@@ -13,32 +13,23 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
import urllib
from lxml import etree
-from tempest.common import rest_client
from tempest.common import xml_utils as common
-from tempest import config
-from tempest import exceptions
-
-CONF = config.CONF
+from tempest.services.volume.xml import volumes_client
-class VolumesV2ClientXML(rest_client.RestClient):
+class VolumesV2ClientXML(volumes_client.BaseVolumesClientXML):
"""
- Client class to send CRUD Volume API requests to a Cinder endpoint
+ Client class to send CRUD Volume API V2 requests to a Cinder endpoint
"""
- TYPE = "xml"
def __init__(self, auth_provider):
super(VolumesV2ClientXML, self).__init__(auth_provider)
self.api_version = "v2"
- self.service = CONF.volume.catalog_type
- self.build_interval = CONF.compute.build_interval
- self.build_timeout = CONF.compute.build_timeout
def _parse_volume(self, body):
vol = dict((attr, body.get(attr)) for attr in body.keys())
@@ -53,46 +44,9 @@
child.getchildren())
else:
vol[tag] = common.xml_to_json(child)
+ self._translate_attributes_to_json(vol)
return vol
- def get_attachment_from_volume(self, volume):
- """Return the element 'attachment' from input volumes."""
- return volume['attachments']['attachment']
-
- def _check_if_bootable(self, volume):
- """
- Check if the volume is bootable, also change the value
- of 'bootable' from string to boolean.
- """
-
- # NOTE(jdg): Version 1 of Cinder API uses lc strings
- # We should consider being explicit in this check to
- # avoid introducing bugs like: LP #1227837
-
- if volume['bootable'].lower() == 'true':
- volume['bootable'] = True
- elif volume['bootable'].lower() == 'false':
- volume['bootable'] = False
- else:
- raise ValueError(
- 'bootable flag is supposed to be either True or False,'
- 'it is %s' % volume['bootable'])
- return volume
-
- def list_volumes(self, params=None):
- """List all the volumes created."""
- url = 'volumes'
-
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = etree.fromstring(body)
- volumes = []
- if body is not None:
- volumes += [self._parse_volume(vol) for vol in list(body)]
- return resp, volumes
-
def list_volumes_with_detail(self, params=None):
"""List all the details of volumes."""
url = 'volumes/detail'
@@ -116,293 +70,3 @@
body = self._parse_volume(etree.fromstring(body))
body = self._check_if_bootable(body)
return resp, body
-
- def create_volume(self, size=None, **kwargs):
- """Creates a new Volume.
-
- :param size: Size of volume in GB.
- :param name: Optional Volume Name.
- :param metadata: An optional dictionary of values for metadata.
- :param volume_type: Optional Name of volume_type for the volume
- :param snapshot_id: When specified the volume is created from
- this snapshot
- :param imageRef: When specified the volume is created from this
- image
- """
- # for bug #1293885:
- # If no size specified, read volume size from CONF
- if size is None:
- size = CONF.volume.volume_size
- # NOTE(afazekas): it should use a volume namespace
- volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
-
- if 'metadata' in kwargs:
- _metadata = common.Element('metadata')
- volume.append(_metadata)
- for key, value in kwargs['metadata'].items():
- meta = common.Element('meta')
- meta.add_attr('key', key)
- meta.append(common.Text(value))
- _metadata.append(meta)
- attr_to_add = kwargs.copy()
- del attr_to_add['metadata']
- else:
- attr_to_add = kwargs
-
- for key, value in attr_to_add.items():
- volume.add_attr(key, value)
-
- resp, body = self.post('volumes', str(common.Document(volume)))
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def update_volume(self, volume_id, **kwargs):
- """Updates the Specified Volume."""
- put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
-
- resp, body = self.put('volumes/%s' % volume_id,
- str(common.Document(put_body)))
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def delete_volume(self, volume_id):
- """Deletes the Specified Volume."""
- return self.delete("volumes/%s" % str(volume_id))
-
- def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status."""
- resp, body = self.get_volume(volume_id)
- volume_status = body['status']
- start = int(time.time())
-
- while volume_status != status:
- time.sleep(self.build_interval)
- resp, body = self.get_volume(volume_id)
- volume_status = body['status']
- if volume_status == 'error':
- raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
-
- if int(time.time()) - start >= self.build_timeout:
- message = 'Volume %s failed to reach %s status within '\
- 'the required time (%s s).' % (volume_id,
- status,
- self.build_timeout)
- raise exceptions.TimeoutException(message)
-
- def is_resource_deleted(self, id):
- try:
- self.get_volume(id)
- except exceptions.NotFound:
- return True
- return False
-
- def attach_volume(self, volume_id, instance_uuid, mountpoint):
- """Attaches a volume to a given instance on a given mountpoint."""
- post_body = common.Element("os-attach",
- instance_uuid=instance_uuid,
- mountpoint=mountpoint
- )
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def detach_volume(self, volume_id):
- """Detaches a volume from an instance."""
- post_body = common.Element("os-detach")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def upload_volume(self, volume_id, image_name, disk_format):
- """Uploads a volume in Glance."""
- post_body = common.Element("os-volume_upload_image",
- image_name=image_name,
- disk_format=disk_format)
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def extend_volume(self, volume_id, extend_size):
- """Extend a volume."""
- post_body = common.Element("os-extend",
- new_size=extend_size)
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def reset_volume_status(self, volume_id, status):
- """Reset the Specified Volume's Status."""
- post_body = common.Element("os-reset_status",
- status=status
- )
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def volume_begin_detaching(self, volume_id):
- """Volume Begin Detaching."""
- post_body = common.Element("os-begin_detaching")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def volume_roll_detaching(self, volume_id):
- """Volume Roll Detaching."""
- post_body = common.Element("os-roll_detaching")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def reserve_volume(self, volume_id):
- """Reserves a volume."""
- post_body = common.Element("os-reserve")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def unreserve_volume(self, volume_id):
- """Restore a reserved volume ."""
- post_body = common.Element("os-unreserve")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def create_volume_transfer(self, vol_id, name=None):
- """Create a volume transfer."""
- post_body = common.Element("transfer", volume_id=vol_id)
- if name:
- post_body.add_attr('name', name)
- resp, body = self.post('os-volume-transfer',
- str(common.Document(post_body)))
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def get_volume_transfer(self, transfer_id):
- """Returns the details of a volume transfer."""
- url = "os-volume-transfer/%s" % str(transfer_id)
- resp, body = self.get(url)
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def list_volume_transfers(self, params=None):
- """List all the volume transfers created."""
- url = 'os-volume-transfer'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = etree.fromstring(body)
- volumes = []
- if body is not None:
- volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
- return resp, volumes
-
- def _parse_volume_transfer(self, body):
- vol = dict((attr, body.get(attr)) for attr in body.keys())
- for child in body.getchildren():
- tag = child.tag
- if tag.startswith("{"):
- tag = tag.split("}", 1)
- vol[tag] = common.xml_to_json(child)
- return vol
-
- def delete_volume_transfer(self, transfer_id):
- """Delete a volume transfer."""
- return self.delete("os-volume-transfer/%s" % str(transfer_id))
-
- def accept_volume_transfer(self, transfer_id, transfer_auth_key):
- """Accept a volume transfer."""
- post_body = common.Element("accept", auth_key=transfer_auth_key)
- url = 'os-volume-transfer/%s/accept' % transfer_id
- resp, body = self.post(url, str(common.Document(post_body)))
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def update_volume_readonly(self, volume_id, readonly):
- """Update the Specified Volume readonly."""
- post_body = common.Element("os-update_readonly_flag",
- readonly=readonly)
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def force_delete_volume(self, volume_id):
- """Force Delete Volume."""
- post_body = common.Element("os-force_delete")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def _metadata_body(self, meta):
- post_body = common.Element('metadata')
- for k, v in meta.items():
- data = common.Element('meta', key=k)
- data.append(common.Text(v))
- post_body.append(data)
- return post_body
-
- def _parse_key_value(self, node):
- """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
- data = {}
- for node in node.getchildren():
- data[node.get('key')] = node.text
- return data
-
- def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume."""
- post_body = self._metadata_body(metadata)
- resp, body = self.post('volumes/%s/metadata' % volume_id,
- str(common.Document(post_body)))
- body = self._parse_key_value(etree.fromstring(body))
- return resp, body
-
- def get_volume_metadata(self, volume_id):
- """Get metadata of the volume."""
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.get(url)
- body = self._parse_key_value(etree.fromstring(body))
- return resp, body
-
- def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume."""
- put_body = self._metadata_body(metadata)
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.put(url, str(common.Document(put_body)))
- body = self._parse_key_value(etree.fromstring(body))
- return resp, body
-
- def update_volume_metadata_item(self, volume_id, id, meta_item):
- """Update metadata item for the volume."""
- for k, v in meta_item.items():
- put_body = common.Element('meta', key=k)
- put_body.append(common.Text(v))
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.put(url, str(common.Document(put_body)))
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def delete_volume_metadata_item(self, volume_id, id):
- """Delete metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- return self.delete(url)
diff --git a/tempest/services/volume/xml/backups_client.py b/tempest/services/volume/xml/backups_client.py
index 81caaee..a691a25 100644
--- a/tempest/services/volume/xml/backups_client.py
+++ b/tempest/services/volume/xml/backups_client.py
@@ -22,5 +22,5 @@
"""
TYPE = "xml"
- #TODO(gfidente): XML client isn't yet implemented because of bug 1270589
+ # TODO(gfidente): XML client isn't yet implemented because of bug 1270589
pass
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 9799e55..2d4a9e9 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -32,14 +32,14 @@
VOLUMES_TENANT_NS = VOLUME_NS_BASE + 'volume_tenant_attribute/api/v1'
-class VolumesClientXML(rest_client.RestClient):
+class BaseVolumesClientXML(rest_client.RestClient):
"""
- Client class to send CRUD Volume API requests to a Cinder endpoint
+ Base client class to send CRUD Volume API requests to a Cinder endpoint
"""
TYPE = "xml"
def __init__(self, auth_provider):
- super(VolumesClientXML, self).__init__(auth_provider)
+ super(BaseVolumesClientXML, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
self.build_interval = CONF.compute.build_interval
self.build_timeout = CONF.compute.build_timeout
@@ -141,6 +141,8 @@
"""Creates a new Volume.
:param size: Size of volume in GB.
+ :param display_name: Optional Volume Name(only for V1).
+ :param name: Optional Volume Name(only for V2).
:param display_name: Optional Volume Name.
:param metadata: An optional dictionary of values for metadata.
:param volume_type: Optional Name of volume_type for the volume
@@ -428,3 +430,9 @@
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
return self.delete(url)
+
+
+class VolumesClientXML(BaseVolumesClientXML):
+ """
+ Client class to send CRUD Volume API V1 requests to a Cinder endpoint
+ """
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index b56f96b..0a63679 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -34,14 +34,14 @@
In order to use this discovery you have to be in the tempest root directory
and execute the following:
- tempest/stress/run_stress.py -a -d 30
+ run-tempest-stress -a -d 30
Running the sample test
-----------------------
-To test installation, do the following (from the tempest/stress directory):
+To test installation, do the following:
- ./run_stress.py -t etc/server-create-destroy-test.json -d 30
+ run-tempest-stress -t tempest/stress/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/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
index c2e6072..b438f52 100644
--- a/tempest/stress/actions/volume_attach_delete.py
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -28,9 +28,9 @@
# Step 1: create volume
name = data_utils.rand_name("volume")
self.logger.info("creating volume: %s" % name)
- resp, volume = self.manager.volumes_client.create_volume(size=1,
- display_name=
- name)
+ resp, volume = self.manager.volumes_client.create_volume(
+ size=1,
+ display_name=name)
assert(resp.status == 200)
self.manager.volumes_client.wait_for_volume_status(volume['id'],
'available')
diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py
index 1bc3b06..a3ca0b7 100644
--- a/tempest/stress/actions/volume_attach_verify.py
+++ b/tempest/stress/actions/volume_attach_verify.py
@@ -81,9 +81,9 @@
name = data_utils.rand_name("volume")
self.logger.info("creating volume: %s" % name)
volumes_client = self.manager.volumes_client
- resp, self.volume = volumes_client.create_volume(size=1,
- display_name=
- name)
+ resp, self.volume = volumes_client.create_volume(
+ size=1,
+ display_name=name)
assert(resp.status == 200)
volumes_client.wait_for_volume_status(self.volume['id'],
'available')
diff --git a/tempest/test.py b/tempest/test.py
index 748a98c..afe7a96 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -107,6 +107,8 @@
'identity': True,
'object_storage': CONF.service_available.swift,
'dashboard': CONF.service_available.horizon,
+ 'ceilometer': CONF.service_available.ceilometer,
+ 'data_processing': CONF.service_available.sahara
}
def decorator(f):
@@ -256,6 +258,12 @@
network_resources = {}
+ # NOTE(sdague): log_format is defined inline here instead of using the oslo
+ # default because going through the config path recouples config to the
+ # stress tests too early, and depending on testr order will fail unit tests
+ log_format = ('%(asctime)s %(process)d %(levelname)-8s '
+ '[%(name)s] %(message)s')
+
@classmethod
def setUpClass(cls):
if hasattr(super(BaseTestCase, cls), 'setUpClass'):
@@ -293,9 +301,8 @@
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
os.environ.get('OS_LOG_CAPTURE') != '0'):
- log_format = '%(asctime)-15s %(message)s'
self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
- format=log_format,
+ format=self.log_format,
level=None))
@classmethod
@@ -396,7 +403,7 @@
:param file: the file name
"""
- #NOTE(mkoderer): must be extended for xml support
+ # NOTE(mkoderer): must be extended for xml support
fn = os.path.join(
os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
"etc", "schemas", file)
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 40caf30..d0140dd 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -15,6 +15,7 @@
import json
import mock
+from oslo.config import cfg
from tempest.cmd import verify_tempest_config
from tempest import config
@@ -152,6 +153,7 @@
False, True)
def test_verify_nova_versions(self):
+ cfg.CONF.set_default('api_v3', True, 'compute-feature-enabled')
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
@@ -280,9 +282,9 @@
def test_verify_extensions_nova(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
@@ -302,9 +304,9 @@
def test_verify_extensions_nova_all(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
@@ -319,9 +321,9 @@
def test_verify_extensions_nova_v3(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_v3_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
@@ -341,9 +343,9 @@
def test_verify_extensions_nova_v3_all(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_v3_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
diff --git a/tempest/tests/common/utils/linux/__init__.py b/tempest/tests/common/utils/linux/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/common/utils/linux/__init__.py
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
new file mode 100644
index 0000000..0db4cfa
--- /dev/null
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -0,0 +1,150 @@
+# Copyright 2014 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 time
+
+from oslo.config import cfg
+
+from tempest.common.utils.linux import remote_client
+from tempest import config
+from tempest.openstack.common.fixture import mockpatch
+from tempest.tests import base
+from tempest.tests import fake_config
+
+
+class TestRemoteClient(base.TestCase):
+ def setUp(self):
+ super(TestRemoteClient, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ cfg.CONF.set_default('ip_version_for_ssh', 4, group='compute')
+ cfg.CONF.set_default('network_for_ssh', 'public', group='compute')
+ cfg.CONF.set_default('ssh_channel_timeout', 1, group='compute')
+
+ self.conn = remote_client.RemoteClient('127.0.0.1', 'user', 'pass')
+ self.ssh_mock = self.useFixture(mockpatch.PatchObject(self.conn,
+ 'ssh_client'))
+
+ def test_hostname_equals_servername_for_expected_names(self):
+ self.ssh_mock.mock.exec_command.return_value = 'fake_hostname'
+ self.assertTrue(self.conn.hostname_equals_servername('fake_hostname'))
+
+ def test_hostname_equals_servername_for_unexpected_names(self):
+ self.ssh_mock.mock.exec_command.return_value = 'fake_hostname'
+ self.assertFalse(
+ self.conn.hostname_equals_servername('unexpected_hostname'))
+
+ def test_get_ram_size(self):
+ free_output = "Mem: 48294 45738 2555 0" \
+ "402 40346"
+ self.ssh_mock.mock.exec_command.return_value = free_output
+ self.assertEqual(self.conn.get_ram_size_in_mb(), '48294')
+
+ def test_write_to_console_regular_str(self):
+ self.conn.write_to_console('test')
+ self._assert_exec_called_with(
+ 'sudo sh -c "echo \\"test\\" >/dev/console"')
+
+ def _test_write_to_console_helper(self, message, expected_call):
+ self.conn.write_to_console(message)
+ self._assert_exec_called_with(expected_call)
+
+ def test_write_to_console_special_chars(self):
+ self._test_write_to_console_helper(
+ '\`',
+ 'sudo sh -c "echo \\"\\\\\\`\\" >/dev/console"')
+ self.conn.write_to_console('$')
+ self._assert_exec_called_with(
+ 'sudo sh -c "echo \\"\\\\$\\" >/dev/console"')
+
+ # NOTE(maurosr): The tests below end up closer to an output format
+ # assurance than a test since it's basically using comand_exec to format
+ # the information using gnu/linux tools.
+
+ def _assert_exec_called_with(self, cmd):
+ self.ssh_mock.mock.exec_command.assert_called_with(cmd)
+
+ def test_get_number_of_vcpus(self):
+ self.ssh_mock.mock.exec_command.return_value = '16'
+ self.assertEqual(self.conn.get_number_of_vcpus(), 16)
+ self._assert_exec_called_with(
+ 'cat /proc/cpuinfo | grep processor | wc -l')
+
+ def test_get_partitions(self):
+ proc_partitions = """major minor #blocks name
+
+8 0 1048576 vda"""
+ self.ssh_mock.mock.exec_command.return_value = proc_partitions
+ self.assertEqual(self.conn.get_partitions(), proc_partitions)
+ self._assert_exec_called_with('cat /proc/partitions')
+
+ def test_get_boot_time(self):
+ booted_at = 10000
+ uptime_sec = 5000.02
+ self.ssh_mock.mock.exec_command.return_value = uptime_sec
+ self.useFixture(mockpatch.PatchObject(
+ time, 'time', return_value=booted_at + uptime_sec))
+ self.assertEqual(self.conn.get_boot_time(),
+ time.localtime(booted_at))
+ self._assert_exec_called_with('cut -f1 -d. /proc/uptime')
+
+ def test_ping_host(self):
+ ping_response = """PING localhost (127.0.0.1) 56(84) bytes of data.
+64 bytes from localhost (127.0.0.1): icmp_req=1 ttl=64 time=0.048 ms
+
+--- localhost ping statistics ---
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+rtt min/avg/max/mdev = 0.048/0.048/0.048/0.000 ms"""
+ self.ssh_mock.mock.exec_command.return_value = ping_response
+ self.assertEqual(self.conn.ping_host('127.0.0.1'), ping_response)
+ self._assert_exec_called_with('ping -c1 -w1 127.0.0.1')
+
+ def test_get_mac_address(self):
+ macs = """0a:0b:0c:0d:0e:0f
+a0:b0:c0:d0:e0:f0"""
+ self.ssh_mock.mock.exec_command.return_value = macs
+
+ self.assertEqual(self.conn.get_mac_address(), macs)
+ self._assert_exec_called_with(
+ "/sbin/ifconfig | awk '/HWaddr/ {print $5}'")
+
+ def test_get_ip_list(self):
+ ips = """1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+ inet 127.0.0.1/8 scope host lo
+ inet6 ::1/128 scope host
+ valid_lft forever preferred_lft forever
+2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
+ link/ether fa:16:3e:6e:26:3b brd ff:ff:ff:ff:ff:ff
+ inet 10.0.0.4/24 brd 10.0.0.255 scope global eth0
+ inet6 fd55:faaf:e1ab:3d9:f816:3eff:fe6e:263b/64 scope global dynamic
+ valid_lft 2591936sec preferred_lft 604736sec
+ inet6 fe80::f816:3eff:fe6e:263b/64 scope link
+ valid_lft forever preferred_lft forever"""
+ self.ssh_mock.mock.exec_command.return_value = ips
+ self.assertEqual(self.conn.get_ip_list(), ips)
+ self._assert_exec_called_with('/bin/ip address')
+
+ def test_assign_static_ip(self):
+ self.ssh_mock.mock.exec_command.return_value = ''
+ ip = '10.0.0.2'
+ nic = 'eth0'
+ self.assertEqual(self.conn.assign_static_ip(nic, ip), '')
+ self._assert_exec_called_with(
+ "sudo /bin/ip addr add %s/%s dev %s" % (ip, '28', nic))
+
+ def test_turn_nic_on(self):
+ nic = 'eth0'
+ self.conn.turn_nic_on(nic)
+ self._assert_exec_called_with('sudo /bin/ip link set %s up' % nic)
diff --git a/tempest/tests/fake_http.py b/tempest/tests/fake_http.py
index 7b878af..ce2b2c0 100644
--- a/tempest/tests/fake_http.py
+++ b/tempest/tests/fake_http.py
@@ -32,7 +32,6 @@
'headers': headers
}
return (fake_headers, return_obj)
- # return (headers, return_obj)
elif isinstance(self.return_type, int):
body = "fake_body"
header_info = {
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
index c77faca..a7af619 100644
--- a/tempest/tests/negative/test_negative_generators.py
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -102,7 +102,7 @@
}
}
- unkown_type_schema = {
+ unknown_type_schema = {
"type": "not_defined"
}
@@ -131,7 +131,7 @@
def test_generate_with_unknown_type(self):
self.assertRaises(TypeError, self.generator.generate,
- self.unkown_type_schema)
+ self.unknown_type_schema)
class TestNegativeValidGenerator(base.TestCase, BaseNegativeGenerator):
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
index c76abde..3dc2199 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -18,12 +18,12 @@
import tempest.cli as cli
from tempest.openstack.common import log as logging
-import tempest.test
+from tempest.tests import base
LOG = logging.getLogger(__name__)
-class StressFrameworkTest(tempest.test.BaseTestCase):
+class StressFrameworkTest(base.TestCase):
"""Basic test for the stress test framework.
"""
@@ -32,7 +32,7 @@
cmd = ' '.join([cmd, param])
LOG.info("running: '%s'" % cmd)
cmd_str = cmd
- cmd = shlex.split(cmd)
+ cmd = shlex.split(cmd.encode('utf-8'))
result = ''
result_err = ''
try:
@@ -51,5 +51,5 @@
return proc.returncode
def test_help_function(self):
- result = self._cmd("python", "-m tempest.stress.run_stress -h")
+ result = self._cmd("python", "-m tempest.cmd.run_stress -h")
self.assertEqual(0, result)
diff --git a/tempest/tests/test_commands.py b/tempest/tests/test_commands.py
index bdb9269..1e2925b 100644
--- a/tempest/tests/test_commands.py
+++ b/tempest/tests/test_commands.py
@@ -47,7 +47,8 @@
@mock.patch('subprocess.Popen')
def test_iptables_raw(self, mock):
table = 'filter'
- expected = ['/usr/bin/sudo', '-n', 'iptables', '-v', '-S', '-t',
+ expected = ['/usr/bin/sudo', '-n', 'iptables', '--line-numbers',
+ '-L', '-nv', '-t',
'%s' % table]
commands.iptables_raw(table)
mock.assert_called_once_with(expected, **self.subprocess_args)
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index bb2df43..88b8129 100644
--- a/tempest/tests/test_glance_http.py
+++ b/tempest/tests/test_glance_http.py
@@ -54,26 +54,37 @@
return_value=resp))
return resp
- def test_json_request_without_content_type_header(self):
+ def test_json_request_without_content_type_header_in_response(self):
self._set_response_fixture({}, 200, 'fake_response_body')
- resp, body = self.client.json_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertIsNone(body)
+ self.assertRaises(exceptions.InvalidContentType,
+ self.client.json_request, 'GET', '/images')
- def test_json_request_with_xml_content_type_header(self):
+ def test_json_request_with_xml_content_type_header_in_request(self):
+ self.assertRaises(exceptions.InvalidContentType,
+ self.client.json_request, 'GET', '/images',
+ headers={'Content-Type': 'application/xml'})
+
+ def test_json_request_with_xml_content_type_header_in_response(self):
self._set_response_fixture({'content-type': 'application/xml'},
200, 'fake_response_body')
- resp, body = self.client.json_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertIsNone(body)
+ self.assertRaises(exceptions.InvalidContentType,
+ self.client.json_request, 'GET', '/images')
- def test_json_request_with_content_type_header(self):
+ def test_json_request_with_json_content_type_header_only_in_resp(self):
self._set_response_fixture({'content-type': 'application/json'},
200, 'fake_response_body')
resp, body = self.client.json_request('GET', '/images')
self.assertEqual(200, resp.status)
self.assertEqual('fake_response_body', body)
+ def test_json_request_with_json_content_type_header_in_req_and_resp(self):
+ self._set_response_fixture({'content-type': 'application/json'},
+ 200, 'fake_response_body')
+ resp, body = self.client.json_request('GET', '/images', headers={
+ 'Content-Type': 'application/json'})
+ self.assertEqual(200, resp.status)
+ self.assertEqual('fake_response_body', body)
+
def test_json_request_fails_to_json_loads(self):
self._set_response_fixture({'content-type': 'application/json'},
200, 'fake_response_body')
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index ab81836..52fdf7e 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -70,9 +70,15 @@
self.assertFalse(checks.scenario_tests_need_service_tags(
'def test_fake_test:', './tempest/api/compute/test_fake.py',
"@test.services('image')"))
+ self.assertFalse(checks.scenario_tests_need_service_tags(
+ 'def test_fake:', './tempest/scenario/orchestration/test_fake.py',
+ "@test.services('compute')"))
self.assertTrue(checks.scenario_tests_need_service_tags(
'def test_fake_test:', './tempest/scenario/test_fake.py',
'\n'))
+ self.assertTrue(checks.scenario_tests_need_service_tags(
+ 'def test_fake:', './tempest/scenario/orchestration/test_fake.py',
+ "\n"))
def test_no_vi_headers(self):
# NOTE(mtreinish) The lines parameter is used only for finding the
@@ -93,3 +99,11 @@
'./tempest/scenario/compute/fake_test.py'))
self.assertFalse(checks.service_tags_not_in_module_path(
"@test.services('compute')", './tempest/api/image/fake_test.py'))
+
+ def test_no_official_client_manager_in_api_tests(self):
+ self.assertTrue(checks.no_official_client_manager_in_api_tests(
+ "cls.official_client = clients.OfficialClientManager(credentials)",
+ "tempest/api/compute/base.py"))
+ self.assertFalse(checks.no_official_client_manager_in_api_tests(
+ "cls.official_client = clients.OfficialClientManager(credentials)",
+ "tempest/scenario/fake_test.py"))
diff --git a/tempest/tests/test_rest_client.py b/tempest/tests/test_rest_client.py
index d20520c..a351bd5 100644
--- a/tempest/tests/test_rest_client.py
+++ b/tempest/tests/test_rest_client.py
@@ -541,3 +541,50 @@
self.assertRaises(AssertionError,
self.negative_rest_client.send_request,
'OTHER', self.url, [])
+
+
+class TestExpectedSuccess(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestExpectedSuccess, self).setUp()
+
+ def test_expected_succes_int_match(self):
+ expected_code = 202
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_int_no_match(self):
+ expected_code = 204
+ read_code = 202
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_expected_succes_list_match(self):
+ expected_code = [202, 204]
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_list_no_match(self):
+ expected_code = [202, 204]
+ read_code = 200
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_int(self):
+ expected_code = 404
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_list(self):
+ expected_code = [404, 202]
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
diff --git a/tempest/tests/test_ssh.py b/tempest/tests/test_ssh.py
index 0da52dc..27cd6b5 100644
--- a/tempest/tests/test_ssh.py
+++ b/tempest/tests/test_ssh.py
@@ -36,13 +36,13 @@
rsa_mock.assert_called_once_with(mock.sentinel.csio)
cs_mock.assert_called_once_with('mykey')
rsa_mock.reset_mock()
- cs_mock.rest_mock()
+ cs_mock.reset_mock()
pkey = mock.sentinel.pkey
# Shouldn't call out to load a file from RSAKey, since
# a sentinel isn't a basestring...
ssh.Client('localhost', 'root', pkey=pkey)
- rsa_mock.assert_not_called()
- cs_mock.assert_not_called()
+ self.assertEqual(0, rsa_mock.call_count)
+ self.assertEqual(0, cs_mock.call_count)
def _set_ssh_connection_mocks(self):
client_mock = mock.MagicMock()
@@ -75,7 +75,7 @@
password=None
)]
self.assertEqual(expected_connect, client_mock.connect.mock_calls)
- s_mock.assert_not_called()
+ self.assertEqual(0, s_mock.call_count)
def test_get_ssh_connection_two_attemps(self):
c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
@@ -90,8 +90,8 @@
start_time = int(time.time())
client._get_ssh_connection(sleep=1)
end_time = int(time.time())
- self.assertTrue((end_time - start_time) < 3)
- self.assertTrue((end_time - start_time) > 1)
+ self.assertLess((end_time - start_time), 4)
+ self.assertGreater((end_time - start_time), 1)
def test_get_ssh_connection_timeout(self):
c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
@@ -108,8 +108,8 @@
with testtools.ExpectedException(exceptions.SSHTimeout):
client._get_ssh_connection()
end_time = int(time.time())
- self.assertTrue((end_time - start_time) < 4)
- self.assertTrue((end_time - start_time) >= 2)
+ self.assertLess((end_time - start_time), 5)
+ self.assertGreaterEqual((end_time - start_time), 2)
def test_exec_command(self):
gsc_mock = self.patch('tempest.common.ssh.Client._get_ssh_connection')
diff --git a/tempest/tests/test_tenant_isolation.py b/tempest/tests/test_tenant_isolation.py
index 7a9b6be..bbc3d15 100644
--- a/tempest/tests/test_tenant_isolation.py
+++ b/tempest/tests/test_tenant_isolation.py
@@ -42,6 +42,8 @@
self.fake_http = fake_http.fake_httplib2(return_type=200)
self.stubs.Set(http.ClosingHttp, 'request',
fake_identity._fake_v2_response)
+ cfg.CONF.set_default('operator_role', 'FakeRole',
+ group='object-storage')
def test_tempest_client(self):
iso_creds = isolated_creds.IsolatedCreds('test class')
@@ -59,6 +61,8 @@
'_get_object_storage_client'))
self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager,
'_get_orchestration_client'))
+ self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager,
+ '_get_ceilometer_client'))
iso_creds = isolated_creds.IsolatedCreds('test class',
tempest_client=False)
self.assertTrue(isinstance(iso_creds.identity_admin_client,
@@ -90,6 +94,31 @@
{'id': id, 'name': name})))
return tenant_fix
+ def _mock_list_roles(self, id, name):
+ roles_fix = self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'list_roles',
+ return_value=({'status': 200},
+ [{'id': id, 'name': name},
+ {'id': '1', 'name': 'FakeRole'}])))
+ return roles_fix
+
+ def _mock_assign_user_role(self):
+ tenant_fix = self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'assign_user_role',
+ return_value=({'status': 200},
+ {})))
+ return tenant_fix
+
+ def _mock_list_role(self):
+ roles_fix = self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'list_roles',
+ return_value=({'status': 200},
+ [{'id': '1', 'name': 'FakeRole'}])))
+ return roles_fix
+
def _mock_network_create(self, iso_creds, id, name):
net_fix = self.useFixture(mockpatch.PatchObject(
iso_creds.network_admin_client,
@@ -119,6 +148,8 @@
cfg.CONF.set_default('neutron', False, 'service_available')
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_tenant_create('1234', 'fake_prim_tenant')
self._mock_user_create('1234', 'fake_prim_user')
primary_creds = iso_creds.get_primary_creds()
@@ -133,13 +164,9 @@
cfg.CONF.set_default('neutron', False, 'service_available')
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_list_roles('1234', 'admin')
self._mock_user_create('1234', 'fake_admin_user')
self._mock_tenant_create('1234', 'fake_admin_tenant')
- self.useFixture(mockpatch.PatchObject(
- json_iden_client.IdentityClientJSON,
- 'list_roles',
- return_value=({'status': 200},
- [{'id': '1234', 'name': 'admin'}])))
user_mock = mock.patch.object(json_iden_client.IdentityClientJSON,
'assign_user_role')
@@ -148,7 +175,9 @@
with mock.patch.object(json_iden_client.IdentityClientJSON,
'assign_user_role') as user_mock:
admin_creds = iso_creds.get_admin_creds()
- user_mock.assert_called_once_with('1234', '1234', '1234')
+ user_mock.assert_has_calls([
+ mock.call('1234', '1234', '1'),
+ mock.call('1234', '1234', '1234')])
self.assertEqual(admin_creds.username, 'fake_admin_user')
self.assertEqual(admin_creds.tenant_name, 'fake_admin_tenant')
# Verify IDs
@@ -160,6 +189,8 @@
cfg.CONF.set_default('neutron', False, 'service_available')
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_assign_user_role()
+ roles_fix = self._mock_list_role()
tenant_fix = self._mock_tenant_create('1234', 'fake_prim_tenant')
user_fix = self._mock_user_create('1234', 'fake_prim_user')
iso_creds.get_primary_creds()
@@ -170,16 +201,11 @@
iso_creds.get_alt_creds()
tenant_fix.cleanUp()
user_fix.cleanUp()
+ roles_fix.cleanUp()
tenant_fix = self._mock_tenant_create('123456', 'fake_admin_tenant')
user_fix = self._mock_user_create('123456', 'fake_admin_user')
- self.useFixture(mockpatch.PatchObject(
- json_iden_client.IdentityClientJSON,
- 'list_roles',
- return_value=({'status': 200},
- [{'id': '123456', 'name': 'admin'}])))
- with mock.patch.object(json_iden_client.IdentityClientJSON,
- 'assign_user_role'):
- iso_creds.get_admin_creds()
+ self._mock_list_roles('123456', 'admin')
+ iso_creds.get_admin_creds()
user_mock = self.patch(
'tempest.services.identity.json.identity_client.'
'IdentityClientJSON.delete_user')
@@ -207,6 +233,8 @@
cfg.CONF.set_default('neutron', False, 'service_available')
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_alt_user')
self._mock_tenant_create('1234', 'fake_alt_tenant')
alt_creds = iso_creds.get_alt_creds()
@@ -220,6 +248,8 @@
def test_network_creation(self, MockRestClient):
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_prim_user')
self._mock_tenant_create('1234', 'fake_prim_tenant')
self._mock_network_create(iso_creds, '1234', 'fake_net')
@@ -245,6 +275,8 @@
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
# Create primary tenant and network
+ self._mock_assign_user_role()
+ roles_fix = self._mock_list_role()
user_fix = self._mock_user_create('1234', 'fake_prim_user')
tenant_fix = self._mock_tenant_create('1234', 'fake_prim_tenant')
net_fix = self._mock_network_create(iso_creds, '1234', 'fake_net')
@@ -276,6 +308,7 @@
net_fix.cleanUp()
subnet_fix.cleanUp()
router_fix.cleanUp()
+ roles_fix.cleanUp()
# Create admin tenant and networks
user_fix = self._mock_user_create('123456', 'fake_admin_user')
tenant_fix = self._mock_tenant_create('123456', 'fake_admin_tenant')
@@ -284,14 +317,8 @@
subnet_fix = self._mock_subnet_create(iso_creds, '123456',
'fake_admin_subnet')
router_fix = self._mock_router_create('123456', 'fake_admin_router')
- self.useFixture(mockpatch.PatchObject(
- json_iden_client.IdentityClientJSON,
- 'list_roles',
- return_value=({'status': 200},
- [{'id': '123456', 'name': 'admin'}])))
- with mock.patch.object(json_iden_client.IdentityClientJSON,
- 'assign_user_role'):
- iso_creds.get_admin_creds()
+ self._mock_list_roles('123456', 'admin')
+ iso_creds.get_admin_creds()
self.patch('tempest.services.identity.json.identity_client.'
'IdentityClientJSON.delete_user')
self.patch('tempest.services.identity.json.identity_client.'
@@ -346,6 +373,8 @@
def test_network_alt_creation(self, MockRestClient):
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_alt_user')
self._mock_tenant_create('1234', 'fake_alt_tenant')
self._mock_network_create(iso_creds, '1234', 'fake_alt_net')
@@ -370,6 +399,7 @@
def test_network_admin_creation(self, MockRestClient):
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password')
+ self._mock_assign_user_role()
self._mock_user_create('1234', 'fake_admin_user')
self._mock_tenant_create('1234', 'fake_admin_tenant')
self._mock_network_create(iso_creds, '1234', 'fake_admin_net')
@@ -378,14 +408,8 @@
router_interface_mock = self.patch(
'tempest.services.network.json.network_client.NetworkClientJSON.'
'add_router_interface_with_subnet_id')
- self.useFixture(mockpatch.PatchObject(
- json_iden_client.IdentityClientJSON,
- 'list_roles',
- return_value=({'status': 200},
- [{'id': '123456', 'name': 'admin'}])))
- with mock.patch.object(json_iden_client.IdentityClientJSON,
- 'assign_user_role'):
- iso_creds.get_admin_creds()
+ self._mock_list_roles('123456', 'admin')
+ iso_creds.get_admin_creds()
router_interface_mock.called_once_with('1234', '1234')
network = iso_creds.get_admin_network()
subnet = iso_creds.get_admin_subnet()
@@ -408,6 +432,8 @@
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password',
network_resources=net_dict)
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_prim_user')
self._mock_tenant_create('1234', 'fake_prim_tenant')
net = mock.patch.object(iso_creds.network_admin_client,
@@ -442,6 +468,8 @@
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password',
network_resources=net_dict)
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_prim_user')
self._mock_tenant_create('1234', 'fake_prim_tenant')
self.assertRaises(exceptions.InvalidConfiguration,
@@ -458,6 +486,8 @@
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password',
network_resources=net_dict)
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_prim_user')
self._mock_tenant_create('1234', 'fake_prim_tenant')
self.assertRaises(exceptions.InvalidConfiguration,
@@ -474,6 +504,8 @@
iso_creds = isolated_creds.IsolatedCreds('test class',
password='fake_password',
network_resources=net_dict)
+ self._mock_assign_user_role()
+ self._mock_list_role()
self._mock_user_create('1234', 'fake_prim_user')
self._mock_tenant_create('1234', 'fake_prim_tenant')
self.assertRaises(exceptions.InvalidConfiguration,
diff --git a/tempest/tests/test_waiters.py b/tempest/tests/test_waiters.py
index 1f9825e..a29cb46 100644
--- a/tempest/tests/test_waiters.py
+++ b/tempest/tests/test_waiters.py
@@ -15,6 +15,7 @@
import time
import mock
+import testtools
from tempest.common import waiters
from tempest import exceptions
@@ -47,3 +48,221 @@
self.assertRaises(exceptions.AddImageException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
+
+
+class TestServerWaiters(base.TestCase):
+ def setUp(self):
+ super(TestServerWaiters, self).setUp()
+ self.client = mock.MagicMock()
+ self.client.build_timeout = 1
+ self.client.build_interval = 1
+
+ def test_wait_for_server_status(self):
+ self.client.get_server.return_value = (None, {'status':
+ 'active'}
+ )
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'active'
+ )
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertTrue((end_time - start_time) < 2)
+
+ def test_wait_for_server_status_BUILD_from_not_UNKNOWN(self):
+ self.client.get_server.return_value = (None, {'status': 'active'})
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'BUILD')
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertTrue((end_time - start_time) < 2)
+
+ def test_wait_for_server_status_ready_wait_with_BUILD(self):
+ self.client.get_server.return_value = (None, {'status': 'BUILD'})
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'BUILD', True)
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertTrue((end_time - start_time) < 2)
+
+ def test_wait_for_server_status_ready_wait(self):
+ self.client.get_server.return_value = (None, {'status':
+ 'ERROR',
+ 'OS-EXT-STS:task_state':
+ 'n/a'
+ }
+ )
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'active status and task state n/a within the '
+ 'required time (1 s).\nCurrent status: SUSPENDED.'
+ '\nCurrent task state: None.'}
+ )
+ self.assertRaises(exceptions.BuildErrorException,
+ waiters.wait_for_server_status,
+ self.client, 'fake_svr_id', 'active',
+ ready_wait=True, extra_timeout=0,
+ raise_on_error=True
+ )
+
+ def test_wait_for_server_status_no_ready_wait(self):
+ self.client.get_server.return_value = (None, {'status':
+ 'ERROR',
+ 'OS-EXT-STS:task_state':
+ 'n/a'
+ }
+ )
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'ERROR', ready_wait=False,
+ extra_timeout=10, raise_on_error=True
+ )
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout + extra_timeout
+ self.assertTrue((end_time - start_time) < 12)
+
+ def test_wait_for_server_status_timeout(self):
+ self.client.get_server.return_value = (None, {'status': 'SUSPENDED'})
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'active status and task state n/a within the '
+ 'required time (1 s).\nCurrent status: SUSPENDED.'
+ '\nCurrent task state: None.'}
+ )
+ self.assertRaises(exceptions.TimeoutException,
+ waiters.wait_for_server_status,
+ self.client, 'fake_svr_id', 'active')
+
+ def test_wait_for_server_status_extra_timeout(self):
+ self.client.get_server.return_value = (None, {'status': 'SUSPENDED'})
+ start_time = int(time.time())
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'active status and task state n/a within the '
+ 'required time (10 s). \nCurrent status: SUSPENDED.'
+ '\nCurrent task state: None.'}
+ )
+ self.assertRaises(exceptions.TimeoutException,
+ waiters.wait_for_server_status,
+ self.client, 'fake_svr_id',
+ 'active', ready_wait=True,
+ extra_timeout=10, raise_on_error=True
+ )
+ end_time = int(time.time())
+ # Ensure waiter returns after build_timeout but
+ # before build_timeout+extra timeout
+ self.assertTrue(10 < (end_time - start_time) < 12)
+
+ def test_wait_for_server_status_error_on_server_create(self):
+ self.client.get_server.return_value = (None, {'status': 'ERROR'})
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'activestatus and task state n/a within the '
+ 'required time (1 s).\nCurrent status: ERROR.'
+ '\nCurrent task state: None.'}
+ )
+ self.assertRaises(exceptions.BuildErrorException,
+ waiters.wait_for_server_status,
+ self.client, 'fake_svr_id', 'active')
+
+ def test_wait_for_server_status_no_raise_on_error(self):
+ self.client.get_server.return_value = (None, {'status': 'ERROR'})
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'activestatus and task state n/a within the '
+ 'required time (1 s).\nCurrent status: ERROR.'
+ '\nCurrent task state: None.'}
+ )
+ self.assertRaises(exceptions.TimeoutException,
+ waiters.wait_for_server_status,
+ self.client, 'fake_svr_id', 'active',
+ ready_wait=True, extra_timeout=0,
+ raise_on_error=False
+ )
+
+ def test_wait_for_server_status_no_ready_wait_timeout(self):
+ self.client.get_server.return_value = (None, {'status': 'ERROR'})
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'active status and task state n/a within the '
+ 'required time (11 s).\nCurrent status: ERROR.'
+ '\nCurrent task state: None.'}
+ )
+ expected_msg = '''Request timed out
+Details: (TestServerWaiters:test_wait_for_server_status_no_ready_wait_timeout)\
+ Server fake_svr_id failed to reach active status and task state "n/a" within\
+ the required time (11 s). Current status: ERROR. Current task state: None.\
+'''
+ with testtools.ExpectedException(exceptions.TimeoutException,
+ testtools.matchers.AfterPreprocessing(
+ str,
+ testtools.matchers.Equals(expected_msg)
+ )
+ ):
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'active', ready_wait=False,
+ extra_timeout=10,
+ raise_on_error=False
+ )
+
+ def test_wait_for_server_status_ready_wait_timeout(self):
+ self.client.get_server.return_value = (None, {'status': 'ERROR'})
+ self.client.get_console_output.return_value = (None,
+ {'output': 'Server fake_svr_id failed to reach '
+ 'activestatus and task state n/a within the '
+ 'required time (11 s).\nCurrent status: ERROR.'
+ '\nCurrent task state: None.'}
+ )
+ expected_msg = '''Request timed out
+Details: (TestServerWaiters:test_wait_for_server_status_ready_wait_timeout)\
+ Server fake_svr_id failed to reach active status and task state "None" within\
+ the required time (11 s). Current status: ERROR. Current task state: None.\
+'''
+ with testtools.ExpectedException(exceptions.TimeoutException,
+ testtools.matchers.AfterPreprocessing(
+ str,
+ testtools.matchers.Equals(expected_msg)
+ )
+ ):
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'active', ready_wait=True,
+ extra_timeout=10,
+ raise_on_error=False
+ )
+
+ def test_wait_for_changing_server_status(self):
+ self.client.get_server.side_effect = [(None, {'status': 'BUILD'}),
+ (None, {'status': 'active'})]
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'active', ready_wait=True,
+ extra_timeout=10,
+ raise_on_error=True
+ )
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout + extra_timeout
+ self.assertTrue((end_time - start_time) < 12)
+
+ def test_wait_for_changing_server_task_status(self):
+ self.client.get_server.side_effect = [(None, {'status': 'BUILD',
+ 'OS-EXT-STS:task_state':
+ 'n/a'
+ }
+ ),
+ (None, {'status': 'active',
+ 'OS-EXT-STS:task_state':
+ 'None'
+ }
+ )
+ ]
+ start_time = int(time.time())
+ waiters.wait_for_server_status(self.client, 'fake_svr_id',
+ 'active', ready_wait=True,
+ extra_timeout=10,
+ raise_on_error=True
+ )
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout + extra_timeout
+ self.assertTrue((end_time - start_time) < 12)
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index 4c39f78..d3cbc4b 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -56,8 +56,8 @@
A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
boto_logger = logging.getLogger('boto')
level = boto_logger.logger.level
- boto_logger.logger.setLevel(orig_logging.CRITICAL) # suppress logging
- # for these
+ # suppress logging for boto
+ boto_logger.logger.setLevel(orig_logging.CRITICAL)
def _cred_sub_check(connection_data):
if not id_matcher.match(connection_data["aws_access_key_id"]):
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index b2eb18d..2c68d6b 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -20,7 +20,6 @@
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
from tempest.thirdparty.boto.utils import s3
from tempest.thirdparty.boto.utils import wait
@@ -80,10 +79,9 @@
if state != "available":
for _image in cls.images.itervalues():
cls.ec2_client.deregister_image(_image["image_id"])
- raise exceptions.EC2RegisterImageException(image_id=
- image["image_id"])
+ raise exceptions.EC2RegisterImageException(
+ image_id=image["image_id"])
- @test.attr(type='smoke')
def test_run_idempotent_instances(self):
# EC2 run instances idempotently
@@ -121,7 +119,6 @@
_terminate_reservation(reservation_1, rcuk_1)
_terminate_reservation(reservation_2, rcuk_2)
- @test.attr(type='smoke')
def test_run_stop_terminate_instance(self):
# EC2 run, stop and terminate instance
image_ami = self.ec2_client.get_image(self.images["ami"]
@@ -146,7 +143,6 @@
instance.terminate()
self.cancelResourceCleanUp(rcuk)
- @test.attr(type='smoke')
def test_run_stop_terminate_instance_with_tags(self):
# EC2 run, stop and terminate instance with tags
image_ami = self.ec2_client.get_image(self.images["ami"]
@@ -181,7 +177,10 @@
instance.remove_tag('key1', value='value1')
tags = self.ec2_client.get_all_tags()
- self.assertEqual(len(tags), 0, str(tags))
+
+ # NOTE: Volume-attach and detach causes metadata (tags) to be created
+ # for the volume. So exclude them while asserting.
+ self.assertNotIn('key1', tags)
for instance in reservation.instances:
instance.stop()
@@ -193,7 +192,6 @@
instance.terminate()
self.cancelResourceCleanUp(rcuk)
- @test.attr(type='smoke')
def test_run_terminate_instance(self):
# EC2 run, terminate immediately
image_ami = self.ec2_client.get_image(self.images["ami"]
@@ -217,7 +215,6 @@
else:
self.assertNotEqual(instance.state, "running")
- @test.attr(type='smoke')
def test_compute_with_volumes(self):
# EC2 1. integration test (not strict)
image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"])
diff --git a/tempest/thirdparty/boto/test_ec2_keys.py b/tempest/thirdparty/boto/test_ec2_keys.py
index dec0170..698e3e1 100644
--- a/tempest/thirdparty/boto/test_ec2_keys.py
+++ b/tempest/thirdparty/boto/test_ec2_keys.py
@@ -32,7 +32,6 @@
cls.ec = cls.ec2_error_code
# TODO(afazekas): merge create, delete, get test cases
- @test.attr(type='smoke')
def test_create_ec2_keypair(self):
# EC2 create KeyPair
key_name = data_utils.rand_name("keypair-")
@@ -42,7 +41,6 @@
self.client.get_key_pair(key_name)))
@test.skip_because(bug="1072318")
- @test.attr(type='smoke')
def test_delete_ec2_keypair(self):
# EC2 delete KeyPair
key_name = data_utils.rand_name("keypair-")
@@ -50,7 +48,6 @@
self.client.delete_key_pair(key_name)
self.assertIsNone(self.client.get_key_pair(key_name))
- @test.attr(type='smoke')
def test_get_ec2_keypair(self):
# EC2 get KeyPair
key_name = data_utils.rand_name("keypair-")
@@ -59,7 +56,6 @@
self.assertTrue(compare_key_pairs(keypair,
self.client.get_key_pair(key_name)))
- @test.attr(type='smoke')
def test_duplicate_ec2_keypair(self):
# EC2 duplicate KeyPair
key_name = data_utils.rand_name("keypair-")
diff --git a/tempest/thirdparty/boto/test_ec2_network.py b/tempest/thirdparty/boto/test_ec2_network.py
index d508c07..792dde3 100644
--- a/tempest/thirdparty/boto/test_ec2_network.py
+++ b/tempest/thirdparty/boto/test_ec2_network.py
@@ -26,7 +26,6 @@
# Note(afazekas): these tests for things duable without an instance
@test.skip_because(bug="1080406")
- @test.attr(type='smoke')
def test_disassociate_not_associated_floating_ip(self):
# EC2 disassociate not associated floating ip
ec2_codes = self.ec2_error_code
diff --git a/tempest/thirdparty/boto/test_ec2_security_groups.py b/tempest/thirdparty/boto/test_ec2_security_groups.py
index 86140ec..7d9bdab 100644
--- a/tempest/thirdparty/boto/test_ec2_security_groups.py
+++ b/tempest/thirdparty/boto/test_ec2_security_groups.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.common.utils import data_utils
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
@@ -25,7 +24,6 @@
super(EC2SecurityGroupTest, cls).setUpClass()
cls.client = cls.os.ec2api_client
- @test.attr(type='smoke')
def test_create_authorize_security_group(self):
# EC2 Create, authorize/revoke security group
group_name = data_utils.rand_name("securty_group-")
diff --git a/tempest/thirdparty/boto/test_ec2_volumes.py b/tempest/thirdparty/boto/test_ec2_volumes.py
index 12dea18..b50c6b0 100644
--- a/tempest/thirdparty/boto/test_ec2_volumes.py
+++ b/tempest/thirdparty/boto/test_ec2_volumes.py
@@ -15,7 +15,6 @@
from tempest import config
from tempest.openstack.common import log as logging
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
CONF = config.CONF
@@ -40,7 +39,6 @@
cls.client = cls.os.ec2api_client
cls.zone = CONF.boto.aws_zone
- @test.attr(type='smoke')
def test_create_get_delete(self):
# EC2 Create, get, delete Volume
volume = self.client.create_volume(1, self.zone)
@@ -53,7 +51,6 @@
self.client.delete_volume(volume.id)
self.cancelResourceCleanUp(cuk)
- @test.attr(type='smoke')
def test_create_volume_from_snapshot(self):
# EC2 Create volume from snapshot
volume = self.client.create_volume(1, self.zone)
diff --git a/tempest/thirdparty/boto/test_s3_buckets.py b/tempest/thirdparty/boto/test_s3_buckets.py
index af6aa8b..3a8dc89 100644
--- a/tempest/thirdparty/boto/test_s3_buckets.py
+++ b/tempest/thirdparty/boto/test_s3_buckets.py
@@ -26,7 +26,6 @@
cls.client = cls.os.s3_client
@test.skip_because(bug="1076965")
- @test.attr(type='smoke')
def test_create_and_get_delete_bucket(self):
# S3 Create, get and delete bucket
bucket_name = data_utils.rand_name("s3bucket-")
diff --git a/tempest/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index d2300ee..389e25c 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -17,7 +17,6 @@
from tempest.common.utils import data_utils
from tempest import config
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
from tempest.thirdparty.boto.utils import s3
@@ -48,7 +47,6 @@
cls.bucket_name)
s3.s3_upload_dir(bucket, cls.materials_path)
- @test.attr(type='smoke')
def test_register_get_deregister_ami_image(self):
# Register and deregister ami image
image = {"name": data_utils.rand_name("ami-name-"),
diff --git a/tempest/thirdparty/boto/test_s3_objects.py b/tempest/thirdparty/boto/test_s3_objects.py
index 1ae46de..db3c1cf 100644
--- a/tempest/thirdparty/boto/test_s3_objects.py
+++ b/tempest/thirdparty/boto/test_s3_objects.py
@@ -18,7 +18,6 @@
import boto.s3.key
from tempest.common.utils import data_utils
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
@@ -29,7 +28,6 @@
super(S3BucketsTest, cls).setUpClass()
cls.client = cls.os.s3_client
- @test.attr(type='smoke')
def test_create_get_delete_object(self):
# S3 Create, get and delete object
bucket_name = data_utils.rand_name("s3bucket-")
diff --git a/test-requirements.txt b/test-requirements.txt
index b9c75c8..cd8154b 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,10 +1,10 @@
-hacking>=0.8.0,<0.9
+hacking>=0.9.2,<0.10
# needed for doc build
-docutils==0.9.1
-sphinx>=1.2.1,<1.3
+sphinx>=1.1.2,!=1.2.0,<1.3
python-subunit>=0.0.18
oslosphinx
mox>=0.5.3
mock>=1.0
coverage>=3.6
oslotest
+stevedore>=0.14
diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh
index 528bd5b..0f0d77e 100755
--- a/tools/config/check_uptodate.sh
+++ b/tools/config/check_uptodate.sh
@@ -16,6 +16,10 @@
trap "rm -rf $TEMPDIR" EXIT
tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR}
+if [ $? != 0 ]
+then
+ exit 1
+fi
if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE}
then
diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh
index 20ddfbb..d22b2f0 100755
--- a/tools/config/generate_sample.sh
+++ b/tools/config/generate_sample.sh
@@ -1,5 +1,15 @@
#!/usr/bin/env bash
+# Generate sample configuration for your project.
+#
+# Aside from the command line flags, it also respects a config file which
+# should be named oslo.config.generator.rc and be placed in the same directory.
+#
+# You can then export the following variables:
+# TEMPEST_CONFIG_GENERATOR_EXTRA_MODULES: list of modules to interrogate for options.
+# TEMPEST_CONFIG_GENERATOR_EXTRA_LIBRARIES: list of libraries to discover.
+# TEMPEST_CONFIG_GENERATOR_EXCLUDED_FILES: list of files to remove from automatic listing.
+
print_hint() {
echo "Try \`${0##*/} --help' for more information." >&2
}
@@ -95,6 +105,10 @@
source "$RC_FILE"
fi
+for filename in ${TEMPEST_CONFIG_GENERATOR_EXCLUDED_FILES}; do
+ FILES="${FILES[@]/$filename/}"
+done
+
for mod in ${TEMPEST_CONFIG_GENERATOR_EXTRA_MODULES}; do
MODULES="$MODULES -m $mod"
done
@@ -111,6 +125,11 @@
MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE
+if [ $? != 0 ]
+then
+ echo "Can not generate $OUTPUTFILE"
+ exit 1
+fi
# Hook to allow projects to append custom config file snippets
CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null)
diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc
new file mode 100644
index 0000000..303e156
--- /dev/null
+++ b/tools/config/oslo.config.generator.rc
@@ -0,0 +1 @@
+MODULEPATH=tempest.common.generate_sample_tempest
diff --git a/tools/generate_sample.sh b/tools/generate_sample.sh
deleted file mode 100755
index 9b312c9..0000000
--- a/tools/generate_sample.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-MODULEPATH=tempest.common.generate_sample_tempest tools/config/generate_sample.sh $@
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index f3c88f3..0a04ce6 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -3,4 +3,4 @@
set -o pipefail
TESTRARGS=$1
-python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py
+python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py --no-failure-debug -f
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index 1634b8e..db70890 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -7,7 +7,8 @@
if [ ! -d .testrepository ]; then
testr init
fi
-testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py
+testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py -f -n
retval=$?
testr slowest
+
exit $retval
diff --git a/tools/subunit-trace.py b/tools/subunit-trace.py
index 7bb88a4..8ad59bb 100755
--- a/tools/subunit-trace.py
+++ b/tools/subunit-trace.py
@@ -18,6 +18,7 @@
"""Trace a subunit stream in reasonable detail and high accuracy."""
+import argparse
import functools
import re
import sys
@@ -151,7 +152,7 @@
stream.write(" %s\n" % line)
-def show_outcome(stream, test):
+def show_outcome(stream, test, print_failures=False):
global RESULTS
status = test['status']
# TODO(sdague): ask lifeless why on this?
@@ -178,14 +179,16 @@
FAILS.append(test)
stream.write('{%s} %s [%s] ... FAILED\n' % (
worker, name, duration))
- print_attachments(stream, test, all_channels=True)
+ if not print_failures:
+ print_attachments(stream, test, all_channels=True)
elif status == 'skip':
stream.write('{%s} %s ... SKIPPED: %s\n' % (
worker, name, test['details']['reason'].as_text()))
else:
stream.write('{%s} %s [%s] ... %s\n' % (
worker, name, duration, test['status']))
- print_attachments(stream, test, all_channels=True)
+ if not print_failures:
+ print_attachments(stream, test, all_channels=True)
stream.flush()
@@ -218,6 +221,14 @@
return count
+def run_time():
+ runtime = 0.0
+ for k, v in RESULTS.items():
+ for test in v:
+ runtime += float(get_duration(test['timestamps']).strip('s'))
+ return runtime
+
+
def worker_stats(worker):
tests = RESULTS[worker]
num_tests = len(tests)
@@ -227,7 +238,8 @@
def print_summary(stream):
stream.write("\n======\nTotals\n======\n")
- stream.write("Run: %s\n" % count_tests('status', '.*'))
+ stream.write("Run: %s in %s sec.\n" % (count_tests('status', '.*'),
+ run_time()))
stream.write(" - Passed: %s\n" % count_tests('status', 'success'))
stream.write(" - Skipped: %s\n" % count_tests('status', 'skip'))
stream.write(" - Failed: %s\n" % count_tests('status', 'fail'))
@@ -247,12 +259,25 @@
(w, num, time))
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--no-failure-debug', '-n', action='store_true',
+ dest='print_failures', help='Disable printing failure '
+ 'debug information in realtime')
+ parser.add_argument('--fails', '-f', action='store_true',
+ dest='post_fails', help='Print failure debug '
+ 'information after the stream is proccesed')
+ return parser.parse_args()
+
+
def main():
+ args = parse_args()
stream = subunit.ByteStreamToStreamResult(
sys.stdin, non_subunit_name='stdout')
starts = Starts(sys.stdout)
outcomes = testtools.StreamToDict(
- functools.partial(show_outcome, sys.stdout))
+ functools.partial(show_outcome, sys.stdout,
+ print_failures=args.print_failures))
summary = testtools.StreamSummary()
result = testtools.CopyStreamResult([starts, outcomes, summary])
result.startTestRun()
@@ -260,6 +285,8 @@
stream.run(result)
finally:
result.stopTestRun()
+ if args.post_fails:
+ print_fails(sys.stdout)
print_summary(sys.stdout)
return (0 if summary.wasSuccessful() else 1)
diff --git a/tox.ini b/tox.ini
index 2110362..4f2465a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = pep8
+envlist = pep8,py27
minversion = 1.6
skipsdist = True
@@ -77,15 +77,19 @@
[testenv:stress]
sitepackages = True
commands =
- python -m tempest/stress/run_stress -a -d 3600 -S
+ run-tempest-stress -a -d 3600 -S
[testenv:venv]
commands = {posargs}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
+[testenv:docs]
+commands = python setup.py build_sphinx {posargs}
+deps = -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
+
[testenv:pep8]
-setenv = MODULEPATH=tempest.common.generate_sample_tempest
commands =
flake8 {posargs}
{toxinidir}/tools/config/check_uptodate.sh
@@ -99,6 +103,8 @@
[flake8]
# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/
-ignore = E125,H404
+# H402 skipped because some docstrings aren't sentences
+# Skipped because of new hacking 0.9: H407,H405,H904,H305,E123,H307,E122,E129,E128
+ignore = E125,H402,H404,H407,H405,H904,H305,E123,H307,E122,E129,E128
show-source = True
exclude = .git,.venv,.tox,dist,doc,openstack,*egg