Merge "Remove unnecessary log message"
diff --git a/doc/source/index.rst b/doc/source/index.rst
index c45273e..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
=======================
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 2ed2582..08f3fd4 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
@@ -378,12 +391,14 @@
# A list of enabled compute extensions with a special entry
# all which indicates every extension is enabled. Each
-# extension should be specified with alias name (list value)
+# extension should be specified with alias name. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# A list of enabled v3 extensions with a special entry all
# which indicates every extension is enabled. Each extension
-# should be specified with alias name (list value)
+# should be specified with alias name. Empty list indicates
+# all extensions are disabled (list value)
#api_v3_extensions=all
# Does the test environment support changing the admin
@@ -396,6 +411,10 @@
# Does the test environment support pausing? (boolean value)
#pause=true
+# Does the test environment support shelving/unshelving?
+# (boolean value)
+#shelve=true
+
# Does the test environment support suspend/resume? (boolean
# value)
#suspend=true
@@ -428,6 +447,11 @@
# (boolean value)
#rescue=true
+# Enables returning of the instance password by the relevant
+# server API calls such as create, rebuild or rescue. (boolean
+# value)
+#enable_instance_password=true
+
[dashboard]
@@ -737,7 +761,8 @@
#ipv6=true
# A list of enabled network extensions with a special entry
-# all which indicates every extension is enabled (list value)
+# all which indicates every extension is enabled. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# Allow the execution of IPv6 subnet tests that use the
@@ -1092,7 +1117,8 @@
#snapshot=true
# A list of enabled volume extensions with a special entry all
-# which indicates every extension is enabled (list value)
+# which indicates every extension is enabled. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# Is the v1 volume API enabled (boolean value)
diff --git a/requirements.txt b/requirements.txt
index ab2903a..9a3b74d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,11 +7,12 @@
boto>=2.12.0,!=2.13.0
paramiko>=1.13.0
netaddr>=0.7.6
-python-glanceclient>=0.9.0
+python-ceilometerclient>=1.0.6
+python-glanceclient>=0.13.1
python-keystoneclient>=0.9.0
python-novaclient>=2.17.0
-python-neutronclient>=2.3.4,<3
-python-cinderclient>=1.0.6
+python-neutronclient>=2.3.5,<3
+python-cinderclient>=1.0.7
python-heatclient>=0.2.9
python-ironicclient
python-saharaclient>=0.6.0
diff --git a/run_tempest.sh b/run_tempest.sh
index bdd1f69..5a9b742 100755
--- a/run_tempest.sh
+++ b/run_tempest.sh
@@ -58,7 +58,7 @@
-l|--logging) logging=1;;
-L|--logging-config) logging_config=$2; shift;;
--) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;;
- *) testrargs+="$testrargs $1";;
+ *) testrargs="$testrargs $1";;
esac
shift
done
diff --git a/tempest/README.rst b/tempest/README.rst
index dbac809..fb25151 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -23,9 +23,8 @@
belongs in each directory, the rules and examples for good tests, are
documented in a README.rst file in the directory.
-
-api
----
+:ref:`api_field_guide`
+----------------------
API tests are validation tests for the OpenStack API. They should not
use the existing python clients for OpenStack, but should instead use
@@ -39,8 +38,8 @@
frameworks.
-cli
----
+:ref:`cli_field_guide`
+----------------------
CLI tests use the openstack CLI to interact with the OpenStack
cloud. CLI testing in unit tests is somewhat difficult because unlike
@@ -49,8 +48,8 @@
prereqs having a running OpenStack cloud.
-scenario
---------
+:ref:`scenario_field_guide`
+---------------------------
Scenario tests are complex "through path" tests for OpenStack
functionality. They are typically a series of steps where complicated
@@ -59,21 +58,26 @@
Scenario tests can and should use the OpenStack python clients.
-stress
-------
+:ref:`stress_field_guide`
+-------------------------
-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).
+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.
-TODO: old stress tests deleted, new_stress that david is working on
-moves into here.
-
-
-thirdparty
-----------
+:ref:`third_party_field_guide`
+-----------------------------
Many openstack components include 3rdparty API support. It is
completely legitimate for Tempest to include tests of 3rdparty APIs,
but those should be kept separate from the normal OpenStack
validation.
+
+:ref:`unit_tests_field_guide`
+-----------------------------
+
+Unit tests are the self checks for Tempest. They provide functional
+verification and regression checking for the internal components of tempest.
+They should be used to just verify that the individual pieces of tempest are
+working as expected.
diff --git a/tempest/api/README.rst b/tempest/api/README.rst
index 9eac19d..91e6ad6 100644
--- a/tempest/api/README.rst
+++ b/tempest/api/README.rst
@@ -1,3 +1,5 @@
+.. _api_field_guide:
+
Tempest Field Guide to API tests
================================
diff --git a/tempest/api/baremetal/base.py b/tempest/api/baremetal/base.py
index 6f7e438..62edd10 100644
--- a/tempest/api/baremetal/base.py
+++ b/tempest/api/baremetal/base.py
@@ -21,6 +21,14 @@
CONF = config.CONF
+# NOTE(adam_g): The baremetal API tests exercise operations such as enroll
+# node, power on, power off, etc. Testing against real drivers (ie, IPMI)
+# will require passing driver-specific data to Tempest (addresses,
+# credentials, etc). Until then, only support testing against the fake driver,
+# which has no external dependencies.
+SUPPORTED_DRIVERS = ['fake']
+
+
def creates(resource):
"""Decorator that adds resources to the appropriate cleanup list."""
@@ -48,6 +56,13 @@
skip_msg = ('%s skipped as Ironic is not available' % cls.__name__)
raise cls.skipException(skip_msg)
+ if CONF.baremetal.driver not in SUPPORTED_DRIVERS:
+ skip_msg = ('%s skipped as Ironic driver %s is not supported for '
+ 'testing.' %
+ (cls.__name__, CONF.baremetal.driver))
+ raise cls.skipException(skip_msg)
+ cls.driver = CONF.baremetal.driver
+
mgr = clients.AdminManager()
cls.client = mgr.baremetal_client
cls.power_timeout = CONF.baremetal.power_timeout
@@ -85,7 +100,7 @@
@classmethod
@creates('node')
def create_node(cls, chassis_id, cpu_arch='x86', cpu_num=8, storage=1024,
- memory=4096, driver='fake'):
+ memory=4096):
"""
Wrapper utility for creating test baremetal nodes.
@@ -98,7 +113,7 @@
"""
resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch,
cpu_num=cpu_num, storage=storage,
- memory=memory, driver=driver)
+ memory=memory, driver=cls.driver)
return resp, body
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/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_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
index 208b032..16c2810 100644
--- a/tempest/api/compute/admin/test_floating_ips_bulk.py
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -58,6 +58,7 @@
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'
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 f147b9c..599b058 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -111,7 +111,9 @@
security_groups=default_sg_quota)
# Check we cannot create anymore
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.sg_client.create_security_group,
"sg-overlimit", "sg-desc")
@@ -147,7 +149,9 @@
ip_protocol = 'tcp'
# Check we cannot create SG rule anymore
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.OverLimit, exceptions.Unauthorized),
self.sg_client.create_security_group_rule,
secgroup_id, ip_protocol, 1025, 1025)
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
new file mode 100644
index 0000000..07408a8
--- /dev/null
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -0,0 +1,127 @@
+# 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.
+
+import testtools
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import exceptions
+from tempest import test
+
+CONF = config.CONF
+
+
+class SecurityGroupDefaultRulesTest(base.BaseV2ComputeAdminTest):
+
+ @classmethod
+ # TODO(GMann): Once Bug# 1311500 is fixed, these test can run
+ # for Neutron also.
+ @testtools.skipIf(CONF.service_available.neutron,
+ "Skip as this functionality is not yet "
+ "implemented in Neutron. Related Bug#1311500")
+ @test.safe_setup
+ def setUpClass(cls):
+ # A network and a subnet will be created for these tests
+ cls.set_network_resources(network=True, subnet=True)
+ super(SecurityGroupDefaultRulesTest, cls).setUpClass()
+ cls.adm_client = cls.os_adm.security_group_default_rules_client
+
+ def _create_security_group_default_rules(self, ip_protocol='tcp',
+ from_port=22, to_port=22,
+ cidr='10.10.0.0/24'):
+ # Create Security Group default rule
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr)
+ self.assertEqual(ip_protocol, rule['ip_protocol'])
+ self.assertEqual(from_port, rule['from_port'])
+ self.assertEqual(to_port, rule['to_port'])
+ self.assertEqual(cidr, rule['ip_range']['cidr'])
+ return rule
+
+ @test.attr(type='smoke')
+ def test_create_delete_security_group_default_rules(self):
+ # Create and delete Security Group default rule
+ ip_protocols = {'tcp', 'udp', 'icmp'}
+ for ip_protocol in ip_protocols:
+ rule = self._create_security_group_default_rules(ip_protocol)
+ # Delete Security Group default rule
+ self.adm_client.delete_security_group_default_rule(rule['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.adm_client.get_security_group_default_rule,
+ rule['id'])
+
+ @test.attr(type='smoke')
+ def test_create_security_group_default_rule_without_cidr(self):
+ ip_protocol = 'udp'
+ from_port = 80
+ to_port = 80
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ self.assertNotEqual(0, rule['id'])
+ self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr'])
+
+ @test.attr(type='smoke')
+ def test_create_security_group_default_rule_with_blank_cidr(self):
+ ip_protocol = 'icmp'
+ from_port = 10
+ to_port = 10
+ cidr = ''
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ self.assertNotEqual(0, rule['id'])
+ self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr'])
+
+ @test.attr(type='smoke')
+ def test_security_group_default_rules_list(self):
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.10.0.0/24'
+ rule = self._create_security_group_default_rules(ip_protocol,
+ from_port,
+ to_port,
+ cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ _, rules = self.adm_client.list_security_group_default_rules()
+ self.assertNotEqual(0, len(rules))
+ self.assertIn(rule, rules)
+
+ @test.attr(type='smoke')
+ def test_default_security_group_default_rule_show(self):
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.10.0.0/24'
+ rule = self._create_security_group_default_rules(ip_protocol,
+ from_port,
+ to_port,
+ cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ _, fetched_rule = self.adm_client.get_security_group_default_rule(
+ rule['id'])
+ self.assertEqual(rule, fetched_rule)
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 cccaf13..f4d010e 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -127,6 +127,8 @@
self.client.migrate_server,
str(uuid.uuid4()))
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
'Suspend is not available.')
@test.attr(type=['negative', 'gate'])
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 a1aaa95..a3295eb 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
@@ -81,6 +83,8 @@
cls.hypervisor_client = cls.os.hypervisor_client
cls.certificates_client = cls.os.certificates_client
cls.migrations_client = cls.os.migrations_client
+ cls.security_group_default_rules_client = (
+ cls.os.security_group_default_rules_client)
elif cls._api_version == 3:
if not CONF.compute_feature_enabled.api_v3:
@@ -390,8 +394,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/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_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 91eb4c5..9036726 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.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.common.utils import data_utils
from tempest import config
@@ -31,25 +33,21 @@
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ cls.glance_client = cls.os.image_client
cls.client = cls.images_client
cls.image_id = None
- resp, server = cls.create_test_server(wait_until='ACTIVE')
- cls.server_id = server['id']
-
- # Snapshot the server once to save time
name = data_utils.rand_name('image')
- resp, _ = cls.client.create_image(cls.server_id, name, {})
- cls.image_id = resp['location'].rsplit('/', 1)[1]
-
+ resp, body = cls.glance_client.create_image(name=name,
+ container_format='bare',
+ disk_format='raw',
+ is_public=False)
+ cls.image_id = body['id']
+ cls.images.append(cls.image_id)
+ image_file = StringIO.StringIO(('*' * 1024))
+ cls.glance_client.update_image(cls.image_id, data=image_file)
cls.client.wait_for_image_status(cls.image_id, 'ACTIVE')
- @classmethod
- def tearDownClass(cls):
- if cls.image_id:
- cls.client.delete_image(cls.image_id)
- super(ImagesMetadataTestJSON, cls).tearDownClass()
-
def setUp(self):
super(ImagesMetadataTestJSON, self).setUp()
meta = {'key1': 'value1', 'key2': 'value2'}
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 86ee4a4..f9350e1 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -13,7 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import StringIO
+import time
+
from tempest.api.compute import base
+from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
from tempest import test
@@ -32,7 +36,34 @@
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
cls.client = cls.images_client
+ cls.glance_client = cls.os.image_client
+ def _create_image():
+ name = data_utils.rand_name('image')
+ _, body = cls.glance_client.create_image(name=name,
+ container_format='bare',
+ disk_format='raw',
+ is_public=False)
+ image_id = body['id']
+ cls.images.append(image_id)
+ # Wait 1 second between creation and upload to ensure a delta
+ # between created_at and updated_at.
+ time.sleep(1)
+ image_file = StringIO.StringIO(('*' * 1024))
+ cls.glance_client.update_image(image_id, data=image_file)
+ cls.client.wait_for_image_status(image_id, 'ACTIVE')
+ _, body = cls.client.get_image(image_id)
+ return body
+
+ # Create non-snapshot images via glance
+ cls.image1 = _create_image()
+ cls.image1_id = cls.image1['id']
+ cls.image2 = _create_image()
+ cls.image2_id = cls.image2['id']
+ cls.image3 = _create_image()
+ cls.image3_id = cls.image3['id']
+
+ # Create instances and snapshots via nova
try:
resp, cls.server1 = cls.create_test_server()
resp, cls.server2 = cls.create_test_server(wait_until='ACTIVE')
@@ -41,21 +72,21 @@
'ACTIVE')
# Create images to be used in the filter tests
- resp, cls.image1 = cls.create_image_from_server(
+ resp, cls.snapshot1 = cls.create_image_from_server(
cls.server1['id'], wait_until='ACTIVE')
- cls.image1_id = cls.image1['id']
+ cls.snapshot1_id = cls.snapshot1['id']
# Servers have a hidden property for when they are being imaged
# Performing back-to-back create image calls on a single
# server will sometimes cause failures
- resp, cls.image3 = cls.create_image_from_server(
+ resp, cls.snapshot3 = cls.create_image_from_server(
cls.server2['id'], wait_until='ACTIVE')
- cls.image3_id = cls.image3['id']
+ cls.snapshot3_id = cls.snapshot3['id']
# Wait for the server to be active after the image upload
- resp, cls.image2 = cls.create_image_from_server(
+ resp, cls.snapshot2 = cls.create_image_from_server(
cls.server1['id'], wait_until='ACTIVE')
- cls.image2_id = cls.image2['id']
+ cls.snapshot2_id = cls.snapshot2['id']
except Exception:
LOG.exception('setUpClass failed')
cls.tearDownClass()
@@ -89,11 +120,14 @@
params = {'server': self.server1['id']}
resp, images = self.client.list_images(params)
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]),
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot1_id]),
"Failed to find image %s in images. Got images %s" %
(self.image1_id, images))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot2_id]))
+ self.assertFalse(any([i for i in images
+ if i['id'] == self.snapshot3_id]))
@test.attr(type='gate')
def test_list_images_filter_by_server_ref(self):
@@ -106,11 +140,11 @@
resp, images = self.client.list_images(params)
self.assertFalse(any([i for i in images
- if i['id'] == self.image1_id]))
+ if i['id'] == self.snapshot1_id]))
self.assertFalse(any([i for i in images
- if i['id'] == self.image2_id]))
+ if i['id'] == self.snapshot2_id]))
self.assertTrue(any([i for i in images
- if i['id'] == self.image3_id]))
+ if i['id'] == self.snapshot3_id]))
@test.attr(type='gate')
def test_list_images_filter_by_type(self):
@@ -118,10 +152,14 @@
params = {'type': 'snapshot'}
resp, images = self.client.list_images(params)
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot1_id]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot2_id]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot3_id]))
+ self.assertFalse(any([i for i in images
+ if i['id'] == self.image_ref]))
@test.attr(type='gate')
def test_list_images_limit_results(self):
@@ -184,11 +222,11 @@
resp, images = self.client.list_images_with_detail(params)
self.assertFalse(any([i for i in images
- if i['id'] == self.image1_id]))
+ if i['id'] == self.snapshot1_id]))
self.assertFalse(any([i for i in images
- if i['id'] == self.image2_id]))
+ if i['id'] == self.snapshot2_id]))
self.assertTrue(any([i for i in images
- if i['id'] == self.image3_id]))
+ if i['id'] == self.snapshot3_id]))
@test.attr(type='gate')
def test_list_images_with_detail_filter_by_type(self):
@@ -197,10 +235,14 @@
resp, images = self.client.list_images_with_detail(params)
resp, image4 = self.client.get_image(self.image_ref)
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot1_id]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot2_id]))
+ self.assertTrue(any([i for i in images
+ if i['id'] == self.snapshot3_id]))
+ self.assertFalse(any([i for i in images
+ if i['id'] == self.image_ref]))
@test.attr(type='gate')
def test_list_images_with_detail_filter_by_changes_since(self):
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 35f6fc2..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
@@ -78,6 +80,7 @@
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
@@ -114,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_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 9e34922..9c8271f 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -70,6 +70,8 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_server_termination(server['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_delete_server_while_in_shelved_state(self):
# Delete a server while it's VM state is Shelved
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index f66020c..9d39c9f 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -203,11 +203,13 @@
params = {'status': 'active'}
resp, body = self.client.list_servers_with_detail(params)
servers = body['servers']
+ test_ids = [s['id'] for s in (self.s1, self.s2, self.s3)]
self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s2['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
- self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers])
+ self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers
+ if x['id'] in test_ids])
@test.attr(type='gate')
def test_list_servers_filtered_by_name_wildcard(self):
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 71fcbff..ee525e7 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -403,6 +403,8 @@
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_shelve_unshelve_server(self):
resp, server = self.client.shelve_server(self.server_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_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index b55833c..fbda401 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -119,20 +119,22 @@
@test.attr(type=['negative', 'gate'])
def test_metadata_items_limit(self):
- # Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant.
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised while exceeding metadata items limit for
+ # tenant.
_, quota_set = self.quotas.get_quota_set(self.tenant_id)
quota_metadata = quota_set['metadata_items']
req_metadata = {}
for num in range(1, quota_metadata + 2):
req_metadata['key' + str(num)] = 'val' + str(num)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises((exceptions.OverLimit, exceptions.Unauthorized),
self.client.set_server_metadata,
self.server_id, req_metadata)
- # Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant (update).
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised while exceeding metadata items limit for
+ # tenant.
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.client.update_server_metadata,
self.server_id, req_metadata)
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index d3297ce..792b523 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -425,6 +425,8 @@
self.client.restore_soft_deleted_server,
self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_non_existent_server(self):
# shelve a non existent server
@@ -432,6 +434,8 @@
self.assertRaises(exceptions.NotFound, self.client.shelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_shelved_server(self):
# shelve a shelved server.
@@ -460,6 +464,8 @@
self.client.unshelve_server(self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_non_existent_server(self):
# unshelve a non existent server
@@ -467,6 +473,8 @@
self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_server_invalid_state(self):
# unshelve an active server.
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_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 8a4e3cf..09d76b8 100644
--- a/tempest/api/compute/v3/admin/test_flavors.py
+++ b/tempest/api/compute/v3/admin/test_flavors.py
@@ -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/servers/test_delete_server.py b/tempest/api/compute/v3/servers/test_delete_server.py
index add69ab..e2b47ee 100644
--- a/tempest/api/compute/v3/servers/test_delete_server.py
+++ b/tempest/api/compute/v3/servers/test_delete_server.py
@@ -68,6 +68,8 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_server_termination(server['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_delete_server_while_in_shelved_state(self):
# Delete a server while it's VM state is Shelved
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_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 3ee8050..4404043 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -394,6 +394,8 @@
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_shelve_unshelve_server(self):
resp, server = self.client.shelve_server(self.server_id)
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 90deaa9..f8ff7c8 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -397,6 +397,8 @@
self.client.restore_soft_deleted_server,
self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_non_existent_server(self):
# shelve a non existent server
@@ -404,6 +406,8 @@
self.assertRaises(exceptions.NotFound, self.client.shelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_shelved_server(self):
# shelve a shelved server.
@@ -431,6 +435,8 @@
self.client.unshelve_server(self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_non_existent_server(self):
# unshelve a non existent server
@@ -438,6 +444,8 @@
self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_server_invalid_state(self):
# unshelve an active server.
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/data_processing/base.py b/tempest/api/data_processing/base.py
index ab0e83a..65085b9 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -40,6 +40,7 @@
cls._data_sources = []
cls._job_binary_internals = []
cls._job_binaries = []
+ cls._jobs = []
@classmethod
def tearDownClass(cls):
@@ -47,12 +48,13 @@
cls.client.delete_cluster_template)
cls.cleanup_resources(getattr(cls, '_node_group_templates', []),
cls.client.delete_node_group_template)
- cls.cleanup_resources(getattr(cls, '_data_sources', []),
- cls.client.delete_data_source)
- cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
- cls.client.delete_job_binary_internal)
+ cls.cleanup_resources(getattr(cls, '_jobs', []), cls.client.delete_job)
cls.cleanup_resources(getattr(cls, '_job_binaries', []),
cls.client.delete_job_binary)
+ cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
+ cls.client.delete_job_binary_internal)
+ cls.cleanup_resources(getattr(cls, '_data_sources', []),
+ cls.client.delete_data_source)
cls.clear_isolated_creds()
super(BaseDataProcessingTest, cls).tearDownClass()
@@ -75,16 +77,16 @@
object. All resources created in this method will be automatically
removed in tearDownClass method.
"""
- resp, body = cls.client.create_node_group_template(name, plugin_name,
- hadoop_version,
- node_processes,
- flavor_id,
- node_configs,
- **kwargs)
+ _, resp_body = cls.client.create_node_group_template(name, plugin_name,
+ hadoop_version,
+ node_processes,
+ flavor_id,
+ node_configs,
+ **kwargs)
# store id of created node group template
- cls._node_group_templates.append(body['id'])
+ cls._node_group_templates.append(resp_body['id'])
- return resp, body
+ return resp_body
@classmethod
def create_cluster_template(cls, name, plugin_name, hadoop_version,
@@ -95,15 +97,15 @@
object. All resources created in this method will be automatically
removed in tearDownClass method.
"""
- resp, body = cls.client.create_cluster_template(name, plugin_name,
- hadoop_version,
- node_groups,
- cluster_configs,
- **kwargs)
+ _, resp_body = cls.client.create_cluster_template(name, plugin_name,
+ hadoop_version,
+ node_groups,
+ cluster_configs,
+ **kwargs)
# store id of created cluster template
- cls._cluster_templates.append(body['id'])
+ cls._cluster_templates.append(resp_body['id'])
- return resp, body
+ return resp_body
@classmethod
def create_data_source(cls, name, type, url, **kwargs):
@@ -113,11 +115,11 @@
object. All resources created in this method will be automatically
removed in tearDownClass method.
"""
- resp, body = cls.client.create_data_source(name, type, url, **kwargs)
+ _, resp_body = cls.client.create_data_source(name, type, url, **kwargs)
# store id of created data source
- cls._data_sources.append(body['id'])
+ cls._data_sources.append(resp_body['id'])
- return resp, body
+ return resp_body
@classmethod
def create_job_binary_internal(cls, name, data):
@@ -126,12 +128,13 @@
It returns created object. All resources created in this method will
be automatically removed in tearDownClass method.
"""
- resp, body = cls.client.create_job_binary_internal(name, data)
+ _, resp_body = cls.client.create_job_binary_internal(name, data)
# store id of created job binary internal
- cls._job_binary_internals.append(body['id'])
+ cls._job_binary_internals.append(resp_body['id'])
- return resp, body
+ return resp_body
+ @classmethod
def create_job_binary(cls, name, url, extra=None, **kwargs):
"""Creates watched job binary with specified params.
@@ -139,8 +142,23 @@
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)
+ _, resp_body = cls.client.create_job_binary(name, url, extra, **kwargs)
# store id of created job binary
- cls._job_binaries.append(body['id'])
+ cls._job_binaries.append(resp_body['id'])
- return resp, body
+ return resp_body
+
+ @classmethod
+ def create_job(cls, name, job_type, mains, libs=None, **kwargs):
+ """Creates watched job 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(name,
+ job_type, mains, libs, **kwargs)
+ # store id of created job
+ cls._jobs.append(resp_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
index e5c6303..ff67c1c 100644
--- a/tempest/api/data_processing/test_cluster_templates.py
+++ b/tempest/api/data_processing/test_cluster_templates.py
@@ -22,6 +22,7 @@
sahara/restapi/rest_api_v1.0.html#cluster-templates
"""
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ClusterTemplateTest, cls).setUpClass()
# create node group template
@@ -38,7 +39,8 @@
}
}
}
- resp_body = cls.create_node_group_template(**node_group_template)[1]
+ resp_body = cls.create_node_group_template(**node_group_template)
+ node_group_template_id = resp_body['id']
cls.full_cluster_template = {
'description': 'Test cluster template',
@@ -65,7 +67,7 @@
},
{
'name': 'worker-node',
- 'node_group_template_id': resp_body['id'],
+ 'node_group_template_id': node_group_template_id,
'count': 3
}
]
@@ -93,23 +95,22 @@
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.
+ It creates template, ensures 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)
+ 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)
+ self.assertEqual(template_name, resp_body['name'])
+ self.assertDictContainsSubset(self.cluster_template, resp_body)
- return body['id'], template_name
+ return resp_body['id'], template_name
@test.attr(type='smoke')
def test_cluster_template_create(self):
@@ -120,8 +121,7 @@
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 = self.client.list_cluster_templates()
templates_info = [(template['id'], template['name'])
for template in templates]
self.assertIn(template_info, templates_info)
@@ -131,16 +131,14 @@
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)
+ _, template = self.client.get_cluster_template(template_id)
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]
+ template_id, _ = self._create_cluster_template()
# delete the cluster template by id
- resp = self.client.delete_cluster_template(template_id)[0]
- self.assertEqual(204, resp.status)
+ self.client.delete_cluster_template(template_id)
# 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..aae56c4 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'
}
@@ -51,65 +48,59 @@
def _create_data_source(self, source_body, source_name=None):
"""Creates Data Source with optional name specified.
- It creates a link to input-source file (it may not exist) and ensures
- response status and source name. Returns id and name of created source.
+ It creates a link to input-source file (it may not exist), ensures
+ source name and response body. Returns id and name of created source.
"""
if not source_name:
# generate random name if it's not specified
source_name = data_utils.rand_name('sahara-data-source')
# create data source
- resp, body = self.create_data_source(source_name, **source_body)
+ resp_body = self.create_data_source(source_name, **source_body)
# ensure that source created successfully
- self.assertEqual(202, resp.status)
- self.assertEqual(source_name, body['name'])
+ self.assertEqual(source_name, resp_body['name'])
if source_body['type'] == 'swift':
source_body = self.swift_data_source
- self.assertDictContainsSubset(source_body, body)
+ self.assertDictContainsSubset(source_body, resp_body)
- return body['id'], source_name
+ return resp_body['id'], source_name
def _list_data_sources(self, source_info):
# check for data source in list
- resp, sources = self.client.list_data_sources()
- self.assertEqual(200, resp.status)
+ _, sources = self.client.list_data_sources()
sources_info = [(source['id'], source['name']) for source in sources]
self.assertIn(source_info, sources_info)
def _get_data_source(self, source_id, source_name, source_body):
# check data source fetch by id
- resp, source = self.client.get_data_source(source_id)
- self.assertEqual(200, resp.status)
+ _, source = self.client.get_data_source(source_id)
self.assertEqual(source_name, source['name'])
self.assertDictContainsSubset(source_body, source)
- def _delete_data_source(self, source_id):
- # delete the data source by id
- resp = self.client.delete_data_source(source_id)[0]
- self.assertEqual(204, resp.status)
-
@test.attr(type='smoke')
def test_swift_data_source_create(self):
self._create_data_source(self.swift_data_source_with_creds)
@test.attr(type='smoke')
def test_swift_data_source_list(self):
- source_info = self._create_data_source(
- self.swift_data_source_with_creds)
+ source_info = (
+ self._create_data_source(self.swift_data_source_with_creds))
self._list_data_sources(source_info)
@test.attr(type='smoke')
def test_swift_data_source_get(self):
- source_id, source_name = self._create_data_source(
- self.swift_data_source_with_creds)
+ source_id, source_name = (
+ self._create_data_source(self.swift_data_source_with_creds))
self._get_data_source(source_id, source_name, self.swift_data_source)
@test.attr(type='smoke')
def test_swift_data_source_delete(self):
- source_id = self._create_data_source(
- self.swift_data_source_with_creds)[0]
- self._delete_data_source(source_id)
+ source_id, _ = (
+ self._create_data_source(self.swift_data_source_with_creds))
+
+ # delete the data source by id
+ self.client.delete_data_source(source_id)
@test.attr(type='smoke')
def test_local_hdfs_data_source_create(self):
@@ -122,15 +113,17 @@
@test.attr(type='smoke')
def test_local_hdfs_data_source_get(self):
- source_id, source_name = self._create_data_source(
- self.local_hdfs_data_source)
+ source_id, source_name = (
+ self._create_data_source(self.local_hdfs_data_source))
self._get_data_source(
source_id, source_name, self.local_hdfs_data_source)
@test.attr(type='smoke')
def test_local_hdfs_data_source_delete(self):
- source_id = self._create_data_source(self.local_hdfs_data_source)[0]
- self._delete_data_source(source_id)
+ source_id, _ = self._create_data_source(self.local_hdfs_data_source)
+
+ # delete the data source by id
+ self.client.delete_data_source(source_id)
@test.attr(type='smoke')
def test_external_hdfs_data_source_create(self):
@@ -143,12 +136,14 @@
@test.attr(type='smoke')
def test_external_hdfs_data_source_get(self):
- source_id, source_name = self._create_data_source(
- self.external_hdfs_data_source)
+ source_id, source_name = (
+ self._create_data_source(self.external_hdfs_data_source))
self._get_data_source(
source_id, source_name, self.external_hdfs_data_source)
@test.attr(type='smoke')
def test_external_hdfs_data_source_delete(self):
- source_id = self._create_data_source(self.external_hdfs_data_source)[0]
- self._delete_data_source(source_id)
+ source_id, _ = self._create_data_source(self.external_hdfs_data_source)
+
+ # delete the data source by id
+ self.client.delete_data_source(source_id)
diff --git a/tempest/api/data_processing/test_job_binaries.py b/tempest/api/data_processing/test_job_binaries.py
index 689c1fe..15ee145 100644
--- a/tempest/api/data_processing/test_job_binaries.py
+++ b/tempest/api/data_processing/test_job_binaries.py
@@ -40,8 +40,8 @@
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]
+ job_binary_internal = (
+ cls.create_job_binary_internal(name, cls.job_binary_data))
cls.internal_db_job_binary = {
'url': 'internal-db://%s' % job_binary_internal['id'],
'description': 'Test job binary',
@@ -50,26 +50,25 @@
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.
+ It creates a link to data (jar, pig files, etc.), ensures 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)
+ 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'])
+ self.assertEqual(binary_name, resp_body['name'])
if 'swift' in binary_body['url']:
binary_body = self.swift_job_binary
- self.assertDictContainsSubset(binary_body, body)
+ self.assertDictContainsSubset(binary_body, resp_body)
- return body['id'], binary_name
+ return resp_body['id'], binary_name
@test.attr(type='smoke')
def test_swift_job_binary_create(self):
@@ -80,30 +79,27 @@
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 = self.client.list_job_binaries()
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)
+ 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)
+ _, binary = self.client.get_job_binary(binary_id)
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]
+ binary_id, _ = (
+ self._create_job_binary(self.swift_job_binary_with_extra))
# delete the job binary by id
- resp = self.client.delete_job_binary(binary_id)[0]
- self.assertEqual(204, resp.status)
+ self.client.delete_job_binary(binary_id)
@test.attr(type='smoke')
def test_internal_db_job_binary_create(self):
@@ -114,35 +110,31 @@
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 = self.client.list_job_binaries()
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)
+ 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)
+ _, binary = self.client.get_job_binary(binary_id)
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]
+ binary_id, _ = self._create_job_binary(self.internal_db_job_binary)
# delete the job binary by id
- resp = self.client.delete_job_binary(binary_id)[0]
- self.assertEqual(204, resp.status)
+ self.client.delete_job_binary(binary_id)
@test.attr(type='smoke')
def test_job_binary_get_data(self):
- binary_id = self._create_job_binary(self.internal_db_job_binary)[0]
+ binary_id, _ = self._create_job_binary(self.internal_db_job_binary)
# get data of job binary by id
- resp, data = self.client.get_job_binary_data(binary_id)
- self.assertEqual(200, resp.status)
+ _, data = self.client.get_job_binary_data(binary_id)
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
index 6d59177..45e1140 100644
--- a/tempest/api/data_processing/test_job_binary_internals.py
+++ b/tempest/api/data_processing/test_job_binary_internals.py
@@ -29,23 +29,22 @@
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.
+ It puts data into Sahara database and ensures 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)
+ 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'])
+ self.assertEqual(binary_name, resp_body['name'])
- return body['id'], binary_name
+ return resp_body['id'], binary_name
@test.attr(type='smoke')
def test_job_binary_internal_create(self):
@@ -56,8 +55,7 @@
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 = self.client.list_job_binary_internals()
binaries_info = [(binary['id'], binary['name']) for binary in binaries]
self.assertIn(binary_info, binaries_info)
@@ -66,23 +64,20 @@
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)
+ _, binary = self.client.get_job_binary_internal(binary_id)
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]
+ binary_id, _ = self._create_job_binary_internal()
# delete the job binary internal by id
- resp = self.client.delete_job_binary_internal(binary_id)[0]
- self.assertEqual(204, resp.status)
+ self.client.delete_job_binary_internal(binary_id)
@test.attr(type='smoke')
def test_job_binary_internal_get_data(self):
- binary_id = self._create_job_binary_internal()[0]
+ binary_id, _ = self._create_job_binary_internal()
# get data of job binary internal by id
- resp, data = self.client.get_job_binary_internal_data(binary_id)
- self.assertEqual(200, resp.status)
+ _, data = self.client.get_job_binary_internal_data(binary_id)
self.assertEqual(data, self.job_binary_internal_data)
diff --git a/tempest/api/data_processing/test_jobs.py b/tempest/api/data_processing/test_jobs.py
new file mode 100644
index 0000000..8591dbd
--- /dev/null
+++ b/tempest/api/data_processing/test_jobs.py
@@ -0,0 +1,90 @@
+# 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 JobTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.1_EDP.html#jobs
+ """
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(JobTest, cls).setUpClass()
+ # create job binary
+ job_binary = {
+ 'name': data_utils.rand_name('sahara-job-binary'),
+ 'url': 'swift://sahara-container.sahara/example.jar',
+ 'description': 'Test job binary',
+ 'extra': {
+ 'user': cls.os.credentials.username,
+ 'password': cls.os.credentials.password
+ }
+ }
+ resp_body = cls.create_job_binary(**job_binary)
+ job_binary_id = resp_body['id']
+
+ cls.job = {
+ 'job_type': 'Pig',
+ 'mains': [job_binary_id]
+ }
+
+ def _create_job(self, job_name=None):
+ """Creates Job with optional name specified.
+
+ It creates job and ensures job name. Returns id and name of created
+ job.
+ """
+ if not job_name:
+ # generate random name if it's not specified
+ job_name = data_utils.rand_name('sahara-job')
+
+ # create job
+ resp_body = self.create_job(job_name, **self.job)
+
+ # ensure that job created successfully
+ self.assertEqual(job_name, resp_body['name'])
+
+ return resp_body['id'], job_name
+
+ @test.attr(type='smoke')
+ def test_job_create(self):
+ self._create_job()
+
+ @test.attr(type='smoke')
+ def test_job_list(self):
+ job_info = self._create_job()
+
+ # check for job in list
+ _, jobs = self.client.list_jobs()
+ jobs_info = [(job['id'], job['name']) for job in jobs]
+ self.assertIn(job_info, jobs_info)
+
+ @test.attr(type='smoke')
+ def test_job_get(self):
+ job_id, job_name = self._create_job()
+
+ # check job fetch by id
+ _, job = self.client.get_job(job_id)
+ self.assertEqual(job_name, job['name'])
+
+ @test.attr(type='smoke')
+ def test_job_delete(self):
+ job_id, _ = self._create_job()
+
+ # delete the job by id
+ self.client.delete_job(job_id)
diff --git a/tempest/api/data_processing/test_node_group_templates.py b/tempest/api/data_processing/test_node_group_templates.py
index 04f98b4..c2c0075 100644
--- a/tempest/api/data_processing/test_node_group_templates.py
+++ b/tempest/api/data_processing/test_node_group_templates.py
@@ -43,7 +43,7 @@
def _create_node_group_template(self, template_name=None):
"""Creates Node Group Template with optional name specified.
- It creates template and ensures response status and template name.
+ It creates template, ensures template name and response body.
Returns id and name of created template.
"""
if not template_name:
@@ -51,15 +51,14 @@
template_name = data_utils.rand_name('sahara-ng-template')
# create node group template
- resp, body = self.create_node_group_template(
- template_name, **self.node_group_template)
+ resp_body = self.create_node_group_template(template_name,
+ **self.node_group_template)
# ensure that template created successfully
- self.assertEqual(202, resp.status)
- self.assertEqual(template_name, body['name'])
- self.assertDictContainsSubset(self.node_group_template, body)
+ self.assertEqual(template_name, resp_body['name'])
+ self.assertDictContainsSubset(self.node_group_template, resp_body)
- return body['id'], template_name
+ return resp_body['id'], template_name
@test.attr(type='smoke')
def test_node_group_template_create(self):
@@ -70,8 +69,7 @@
template_info = self._create_node_group_template()
# check for node group template in list
- resp, templates = self.client.list_node_group_templates()
- self.assertEqual(200, resp.status)
+ _, templates = self.client.list_node_group_templates()
templates_info = [(template['id'], template['name'])
for template in templates]
self.assertIn(template_info, templates_info)
@@ -81,15 +79,13 @@
template_id, template_name = self._create_node_group_template()
# check node group template fetch by id
- resp, template = self.client.get_node_group_template(template_id)
- self.assertEqual(200, resp.status)
+ _, template = self.client.get_node_group_template(template_id)
self.assertEqual(template_name, template['name'])
self.assertDictContainsSubset(self.node_group_template, template)
@test.attr(type='smoke')
def test_node_group_template_delete(self):
- template_id = self._create_node_group_template()[0]
+ template_id, _ = self._create_node_group_template()
# delete the node group template by id
- resp = self.client.delete_node_group_template(template_id)[0]
- self.assertEqual(204, resp.status)
+ self.client.delete_node_group_template(template_id)
diff --git a/tempest/api/data_processing/test_plugins.py b/tempest/api/data_processing/test_plugins.py
index d643f23..9fd7a17 100644
--- a/tempest/api/data_processing/test_plugins.py
+++ b/tempest/api/data_processing/test_plugins.py
@@ -20,10 +20,9 @@
def _list_all_plugin_names(self):
"""Returns all enabled plugin names.
- It ensures response status and main plugins availability.
+ It ensures main plugins availability.
"""
- resp, plugins = self.client.list_plugins()
- self.assertEqual(200, resp.status)
+ _, plugins = self.client.list_plugins()
plugins_names = [plugin['name'] for plugin in plugins]
self.assertIn('vanilla', plugins_names)
self.assertIn('hdp', plugins_names)
@@ -37,14 +36,12 @@
@test.attr(type='smoke')
def test_plugin_get(self):
for plugin_name in self._list_all_plugin_names():
- resp, plugin = self.client.get_plugin(plugin_name)
- self.assertEqual(200, resp.status)
+ _, plugin = self.client.get_plugin(plugin_name)
self.assertEqual(plugin_name, plugin['name'])
for plugin_version in plugin['versions']:
- resp, detailed_plugin = self.client.get_plugin(plugin_name,
- plugin_version)
- self.assertEqual(200, resp.status)
+ _, detailed_plugin = self.client.get_plugin(plugin_name,
+ plugin_version)
self.assertEqual(plugin_name, detailed_plugin['name'])
# check that required image tags contains name and version
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 5926488..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,8 +95,7 @@
self.addCleanup(delete_services)
# List and Verify Services
- resp, body = self.client.list_services()
- self.assertEqual(200, resp.status)
+ _, 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 93734d2..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)
+ _, 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 881f69e..6beb8f2 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']
@@ -40,7 +40,7 @@
cls.setup_endpoints = list()
for i in range(2):
region = data_utils.rand_name('region')
- url = data_utils.rand_name('url')
+ url = data_utils.rand_url()
interface = 'public'
resp, endpoint = cls.client.create_endpoint(
cls.service_id, interface, url, region=region, enabled=True)
@@ -69,7 +69,7 @@
@test.attr(type='gate')
def test_create_list_delete_endpoint(self):
region = data_utils.rand_name('region')
- url = data_utils.rand_name('url')
+ url = data_utils.rand_url()
interface = 'public'
resp, endpoint =\
self.client.create_endpoint(self.service_id, interface, url,
@@ -97,7 +97,7 @@
# Creating an endpoint so as to check update endpoint
# with new values
region1 = data_utils.rand_name('region')
- url1 = data_utils.rand_name('url')
+ url1 = data_utils.rand_url()
interface1 = 'public'
resp, endpoint_for_update =\
self.client.create_endpoint(self.service_id, interface1,
@@ -108,13 +108,13 @@
s_name = data_utils.rand_name('service-')
s_type = data_utils.rand_name('type--')
s_description = data_utils.rand_name('description-')
- resp, service2 =\
+ _, service2 =\
self.service_client.create_service(s_name, s_type,
description=s_description)
self.service_ids.append(service2['id'])
# Updating endpoint with new values
region2 = data_utils.rand_name('region')
- url2 = data_utils.rand_name('url')
+ url2 = data_utils.rand_url()
interface2 = 'internal'
resp, endpoint = \
self.client.update_endpoint(endpoint_for_update['id'],
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 5b46f89..d728b1d 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']
@@ -49,7 +49,7 @@
def test_create_with_enabled_False(self):
# Enabled should be a boolean, not a string like 'False'
interface = 'public'
- url = data_utils.rand_name('url')
+ url = data_utils.rand_url()
region = data_utils.rand_name('region')
self.assertRaises(exceptions.BadRequest, self.client.create_endpoint,
self.service_id, interface, url, region=region,
@@ -59,7 +59,7 @@
def test_create_with_enabled_True(self):
# Enabled should be a boolean, not a string like 'True'
interface = 'public'
- url = data_utils.rand_name('url')
+ url = data_utils.rand_url()
region = data_utils.rand_name('region')
self.assertRaises(exceptions.BadRequest, self.client.create_endpoint,
self.service_id, interface, url, region=region,
@@ -69,7 +69,7 @@
# Create an endpoint
region1 = data_utils.rand_name('region')
- url1 = data_utils.rand_name('url')
+ url1 = data_utils.rand_url()
interface1 = 'public'
resp, endpoint_for_update = (
self.client.create_endpoint(self.service_id, interface1,
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_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
new file mode 100644
index 0000000..a3944e2
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -0,0 +1,73 @@
+# 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 test
+
+
+class ListProjectsTestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ListProjectsTestJSON, cls).setUpClass()
+ cls.project_ids = list()
+ cls.data.setup_test_domain()
+ # Create project with domain
+ cls.p1_name = data_utils.rand_name('project')
+ _, cls.p1 = cls.client.create_project(
+ cls.p1_name, enabled=False, domain_id=cls.data.domain['id'])
+ cls.data.projects.append(cls.p1)
+ cls.project_ids.append(cls.p1['id'])
+ # Create default project
+ p2_name = data_utils.rand_name('project')
+ _, cls.p2 = cls.client.create_project(p2_name)
+ cls.data.projects.append(cls.p2)
+ cls.project_ids.append(cls.p2['id'])
+
+ @test.attr(type='gate')
+ def test_projects_list(self):
+ # List projects
+ resp, list_projects = self.client.list_projects()
+
+ for p in self.project_ids:
+ _, get_project = self.client.get_project(p)
+ self.assertIn(get_project, list_projects)
+
+ @test.attr(type='gate')
+ def test_list_projects_with_domains(self):
+ # List projects with domain
+ self._list_projects_with_params(
+ {'domain_id': self.data.domain['id']}, 'domain_id')
+
+ @test.attr(type='gate')
+ def test_list_projects_with_enabled(self):
+ # List the projects with enabled
+ self._list_projects_with_params({'enabled': False}, 'enabled')
+
+ @test.attr(type='gate')
+ def test_list_projects_with_name(self):
+ # List projects with name
+ self._list_projects_with_params({'name': self.p1_name}, 'name')
+
+ def _list_projects_with_params(self, params, key):
+ resp, body = self.client.list_projects(params)
+ self.assertIn(self.p1[key], map(lambda x: x[key], body))
+ self.assertNotIn(self.p2[key], map(lambda x: x[key], body))
+
+
+class ListProjectsTestXML(ListProjectsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
new file mode 100644
index 0000000..497c5ea
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -0,0 +1,100 @@
+# 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 test
+
+
+class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ def _list_users_with_params(self, params, key, expected, not_expected):
+ # Helper method to list users filtered with params and
+ # assert the response based on expected and not_expected
+ # expected: user expected in the list response
+ # not_expected: user, which should not be present in list response
+ _, body = self.client.get_users(params)
+ self.assertIn(expected[key], map(lambda x: x[key], body))
+ self.assertNotIn(not_expected[key],
+ map(lambda x: x[key], body))
+
+ @classmethod
+ def setUpClass(cls):
+ super(UsersV3TestJSON, cls).setUpClass()
+ alt_user = data_utils.rand_name('test_user')
+ alt_password = data_utils.rand_name('pass')
+ cls.alt_email = alt_user + '@testmail.tm'
+ cls.data.setup_test_domain()
+ # Create user with Domain
+ u1_name = data_utils.rand_name('test_user')
+ _, cls.domain_enabled_user = cls.client.create_user(
+ u1_name, password=alt_password,
+ email=cls.alt_email, domain_id=cls.data.domain['id'])
+ cls.data.v3_users.append(cls.domain_enabled_user)
+ # Create default not enabled user
+ u2_name = data_utils.rand_name('test_user')
+ _, cls.non_domain_enabled_user = cls.client.create_user(
+ u2_name, password=alt_password,
+ email=cls.alt_email, enabled=False)
+ cls.data.v3_users.append(cls.non_domain_enabled_user)
+
+ @test.attr(type='gate')
+ def test_list_user_domains(self):
+ # List users with domain
+ params = {'domain_id': self.data.domain['id']}
+ self._list_users_with_params(params, 'domain_id',
+ self.domain_enabled_user,
+ self.non_domain_enabled_user)
+
+ @test.attr(type='gate')
+ def test_list_users_with_not_enabled(self):
+ # List the users with not enabled
+ params = {'enabled': False}
+ self._list_users_with_params(params, 'enabled',
+ self.non_domain_enabled_user,
+ self.domain_enabled_user)
+
+ @test.attr(type='gate')
+ def test_list_users_with_name(self):
+ # List users with name
+ params = {'name': self.domain_enabled_user['name']}
+ self._list_users_with_params(params, 'name',
+ self.domain_enabled_user,
+ self.non_domain_enabled_user)
+
+ @test.attr(type='gate')
+ def test_list_users(self):
+ # List users
+ _, body = self.client.get_users()
+ fetched_ids = [u['id'] for u in body]
+ missing_users = [u['id'] for u in self.data.v3_users
+ if u['id'] not in fetched_ids]
+ self.assertEqual(0, len(missing_users),
+ "Failed to find user %s in fetched list" %
+ ', '.join(m_user for m_user in missing_users))
+
+ @test.attr(type='gate')
+ def test_get_user(self):
+ # Get a user detail
+ _, user = self.client.get_user(self.data.v3_users[0]['id'])
+ self.assertEqual(self.data.v3_users[0]['id'], user['id'])
+ self.assertEqual(self.data.v3_users[0]['name'], user['name'])
+ self.assertEqual(self.alt_email, user['email'])
+ self.assertEqual(self.data.domain['id'], user['domain_id'])
+
+
+class UsersV3TestXML(UsersV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 79717b1..5890eab 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -13,69 +13,57 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest import test
class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
- def _delete_project(self, project_id):
- resp, _ = self.client.delete_project(project_id)
- self.assertEqual(resp['status'], '204')
- self.assertRaises(
- exceptions.NotFound, self.client.get_project, project_id)
-
- @test.attr(type='gate')
- def test_project_list_delete(self):
- # Create several projects and delete them
- for _ in moves.xrange(3):
- resp, 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')
-
- resp, get_project = self.client.get_project(project['id'])
- self.assertIn(get_project, list_projects)
-
@test.attr(type='gate')
def test_project_create_with_description(self):
# 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')
@test.attr(type='gate')
+ def test_project_create_with_domain(self):
+ # Create project with a domain
+ self.data.setup_test_domain()
+ project_name = data_utils.rand_name('project')
+ resp, project = self.client.create_project(
+ project_name, domain_id=self.data.domain['id'])
+ self.data.projects.append(project)
+ project_id = project['id']
+ self.assertEqual(project_name, project['name'])
+ self.assertEqual(self.data.domain['id'], project['domain_id'])
+ _, body = self.client.get_project(project_id)
+ self.assertEqual(project_name, body['name'])
+ self.assertEqual(self.data.domain['id'], body['domain_id'])
+
+ @test.attr(type='gate')
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 +71,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 +86,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 +108,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 +131,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)
@@ -174,7 +154,7 @@
# 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
@@ -182,15 +162,14 @@
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'])
+ _, 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'])
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
index 03974e4..c8b034f 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -1,4 +1,4 @@
-# Copyright 2014 OpenStack Foundation
+# Copyright 2014 Hewlett-Packard Development Company, L.P
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
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..bd08614 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -30,26 +30,25 @@
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)
+ @test.skip_because(bug="1351026")
@test.attr(type='gate')
def test_rescope_token(self):
"""Rescope a token.
@@ -63,35 +62,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 +138,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..1561a6e 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):
@@ -157,20 +152,17 @@
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'])
# And that we don't find not_delegated_role
self.assertRaises(exceptions.NotFound,
@@ -184,8 +176,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 +244,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..3c25819 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'])
@@ -67,6 +65,28 @@
self.assertEqual('false', str(new_user_get['enabled']).lower())
@test.attr(type='gate')
+ def test_update_user_password(self):
+ # Creating User to check password updation
+ u_name = data_utils.rand_name('user')
+ original_password = data_utils.rand_name('pass')
+ _, user = self.client.create_user(
+ u_name, password=original_password)
+ # Delete the User at the end all test methods
+ self.addCleanup(self.client.delete_user, user['id'])
+ # Update user with new password
+ new_password = data_utils.rand_name('pass1')
+ self.client.update_user_password(user['id'], new_password,
+ original_password)
+ resp, body = self.token.auth(user['id'], new_password)
+ self.assertEqual(201, resp.status)
+ subject_token = resp['x-subject-token']
+ # Perform GET Token to verify and confirm password is updated
+ _, 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)
+
+ @test.attr(type='gate')
def test_list_user_projects(self):
# List the projects that a user has access upon
assigned_project_ids = list()
@@ -107,8 +127,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 +143,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 697057f..8eb7d33 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -121,6 +121,7 @@
self.v3_users = []
self.projects = []
self.v3_roles = []
+ self.domains = []
@property
def test_credentials(self):
@@ -136,17 +137,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)
@@ -154,7 +155,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):
@@ -163,7 +164,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'],
@@ -174,7 +175,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)
@@ -182,9 +183,18 @@
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 setup_test_domain(self):
+ """Set up a test domain."""
+ self.test_domain = data_utils.rand_name('test_domain')
+ self.test_description = data_utils.rand_name('desc')
+ _, self.domain = self.client.create_domain(
+ name=self.test_domain,
+ description=self.test_description)
+ self.domains.append(self.domain)
+
def teardown_all(self):
for user in self.users:
self.client.delete_user(user['id'])
@@ -198,3 +208,6 @@
self.client.delete_project(v3_project['id'])
for v3_role in self.v3_roles:
self.client.delete_role(v3_role['id'])
+ for domain in self.domains:
+ self.client.update_domain(domain['id'], enabled=False)
+ self.client.delete_domain(domain['id'])
diff --git a/tempest/api/identity/test_extension.py b/tempest/api/identity/test_extension.py
index 67f20f4..a06ee53 100644
--- a/tempest/api/identity/test_extension.py
+++ b/tempest/api/identity/test_extension.py
@@ -23,8 +23,7 @@
@test.attr(type='gate')
def test_list_extensions(self):
# List all the extensions
- resp, body = self.non_admin_client.list_extensions()
- self.assertEqual(200, resp.status)
+ _, body = self.non_admin_client.list_extensions()
self.assertNotEmpty(body)
keys = ['name', 'updated', 'alias', 'links',
'namespace', 'description']
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 02d391b..c875b2f 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -100,7 +100,7 @@
cls.alt_tenant_id = cls.alt_img_cli.tenant_id
def _create_image(self):
- image_file = StringIO.StringIO('*' * 1024)
+ image_file = StringIO.StringIO(data_utils.random_bytes())
resp, image = self.create_image(container_format='bare',
disk_format='raw',
is_public=False,
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 8528e42..bf55b89 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -44,7 +44,7 @@
self.assertEqual(val, body.get('properties')[key])
# Now try uploading an image file
- image_file = StringIO.StringIO(('*' * 1024))
+ image_file = StringIO.StringIO(data_utils.random_bytes())
_, body = self.client.update_image(image_id, data=image_file)
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -157,7 +157,7 @@
image. Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = StringIO.StringIO('*' * size)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
_, image = cls.create_image(name=name,
container_format=container_format,
@@ -338,10 +338,9 @@
disk_format, size):
"""
Create a new standard image and return the ID of the newly-registered
- image. Note that the size of the new image is a random number between
- 1024 and 4096
+ image.
"""
- image_file = StringIO.StringIO('*' * size)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
_, image = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index ae777eb..a974ebb 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -52,7 +52,7 @@
self.assertEqual('queued', body['status'])
# Now try uploading an image file
- file_content = '*' * 1024
+ file_content = data_utils.random_bytes()
image_file = StringIO.StringIO(file_content)
self.client.store_image(image_id, image_file)
@@ -86,7 +86,8 @@
# Verifying deletion
_, images = self.client.image_list()
- self.assertNotIn(image_id, images)
+ images_id = [item['id'] for item in images]
+ self.assertNotIn(image_id, images_id)
@test.attr(type='gate')
def test_update_image(self):
@@ -103,8 +104,7 @@
image_id = body['id']
# Now try uploading an image file
- file_content = '*' * 1024
- image_file = StringIO.StringIO(file_content)
+ image_file = StringIO.StringIO(data_utils.random_bytes())
self.client.store_image(image_id, image_file)
# Update Image
@@ -145,7 +145,8 @@
image. Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
+ size = random.randint(1024, 4096)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = data_utils.rand_name('image-')
_, body = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index a307986..9fa54b1 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -46,8 +46,7 @@
raise cls.skipException(msg)
cls.identity_admin_client = cls.os_adm.identity_client
- @test.attr(type='gate')
- def test_quotas(self):
+ def _check_quotas(self, new_quotas):
# Add a tenant to conduct the test
test_tenant = data_utils.rand_name('test_tenant_')
test_description = data_utils.rand_name('desc_')
@@ -56,14 +55,15 @@
description=test_description)
tenant_id = tenant['id']
self.addCleanup(self.identity_admin_client.delete_tenant, tenant_id)
+
# Change quotas for tenant
- new_quotas = {'network': 0, 'security_group': 0}
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(0, quota_set['network'])
- self.assertEqual(0, quota_set['security_group'])
+ for key, value in new_quotas.iteritems():
+ self.assertEqual(value, quota_set[key])
+
# 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'])
@@ -72,12 +72,14 @@
if qs['tenant_id'] == tenant_id:
found = True
self.assertTrue(found)
- # Confirm from APi quotas were changed as requested for tenant
+
+ # 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(0, quota_set['network'])
- self.assertEqual(0, quota_set['security_group'])
+ for key, value in new_quotas.iteritems():
+ self.assertEqual(value, quota_set[key])
+
# Reset quotas to default and confirm
resp, body = self.admin_client.reset_quotas(tenant_id)
self.assertEqual('204', resp['status'])
@@ -85,3 +87,15 @@
self.assertEqual('200', resp['status'])
for q in non_default_quotas['quotas']:
self.assertNotEqual(tenant_id, q['tenant_id'])
+
+ @test.attr(type='gate')
+ def test_quotas(self):
+ new_quotas = {'network': 0, 'security_group': 0}
+ self._check_quotas(new_quotas)
+
+ @test.requires_ext(extension='lbaas', service='network')
+ @test.attr(type='gate')
+ def test_lbaas_quotas(self):
+ new_quotas = {'vip': 1, 'pool': 2,
+ 'member': 3, 'health_monitor': 4}
+ self._check_quotas(new_quotas)
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 087b87a..d75339c 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -85,57 +85,58 @@
@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'])
- # Clean up firewall rules
- for fw_rule in cls.fw_rules:
- cls.client.delete_firewall_rule(fw_rule['id'])
- # Clean up ike policies
- for ikepolicy in cls.ikepolicies:
- cls.client.delete_ikepolicy(ikepolicy['id'])
- # Clean up vpn services
- for vpnservice in cls.vpnservices:
- cls.client.delete_vpnservice(vpnservice['id'])
- # Clean up floating IPs
- for floating_ip in cls.floating_ips:
- cls.client.delete_floatingip(floating_ip['id'])
- # Clean up routers
- for router in cls.routers:
- cls.delete_router(router)
+ if CONF.service_available.neutron:
+ # 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'])
+ # Clean up firewall rules
+ for fw_rule in cls.fw_rules:
+ cls.client.delete_firewall_rule(fw_rule['id'])
+ # Clean up ike policies
+ for ikepolicy in cls.ikepolicies:
+ cls.client.delete_ikepolicy(ikepolicy['id'])
+ # Clean up vpn services
+ for vpnservice in cls.vpnservices:
+ cls.client.delete_vpnservice(vpnservice['id'])
+ # Clean up floating IPs
+ for floating_ip in cls.floating_ips:
+ cls.client.delete_floatingip(floating_ip['id'])
+ # Clean up routers
+ for router in cls.routers:
+ cls.delete_router(router)
- # Clean up health monitors
- for health_monitor in cls.health_monitors:
- cls.client.delete_health_monitor(health_monitor['id'])
- # Clean up members
- for member in cls.members:
- cls.client.delete_member(member['id'])
- # Clean up vips
- for vip in cls.vips:
- cls.client.delete_vip(vip['id'])
- # Clean up pools
- for pool in cls.pools:
- cls.client.delete_pool(pool['id'])
- # Clean up metering label rules
- for metering_label_rule in cls.metering_label_rules:
- cls.admin_client.delete_metering_label_rule(
- metering_label_rule['id'])
- # Clean up metering labels
- for metering_label in cls.metering_labels:
- cls.admin_client.delete_metering_label(metering_label['id'])
- # Clean up ports
- for port in cls.ports:
- cls.client.delete_port(port['id'])
- # Clean up subnets
- for subnet in cls.subnets:
- cls.client.delete_subnet(subnet['id'])
- # Clean up networks
- for network in cls.networks:
- cls.client.delete_network(network['id'])
- cls.clear_isolated_creds()
+ # Clean up health monitors
+ for health_monitor in cls.health_monitors:
+ cls.client.delete_health_monitor(health_monitor['id'])
+ # Clean up members
+ for member in cls.members:
+ cls.client.delete_member(member['id'])
+ # Clean up vips
+ for vip in cls.vips:
+ cls.client.delete_vip(vip['id'])
+ # Clean up pools
+ for pool in cls.pools:
+ cls.client.delete_pool(pool['id'])
+ # Clean up metering label rules
+ for metering_label_rule in cls.metering_label_rules:
+ cls.admin_client.delete_metering_label_rule(
+ metering_label_rule['id'])
+ # Clean up metering labels
+ for metering_label in cls.metering_labels:
+ cls.admin_client.delete_metering_label(metering_label['id'])
+ # Clean up ports
+ for port in cls.ports:
+ cls.client.delete_port(port['id'])
+ # Clean up subnets
+ for subnet in cls.subnets:
+ cls.client.delete_subnet(subnet['id'])
+ # Clean up networks
+ for network in cls.networks:
+ cls.client.delete_network(network['id'])
+ cls.clear_isolated_creds()
super(BaseNetworkTest, cls).tearDownClass()
@classmethod
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_networks.py b/tempest/api/network/test_networks.py
index 78cb221..ac3a072 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -277,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'])
@@ -332,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.
@@ -344,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]
@@ -364,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],
}
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index d1fe15c..0cc3f19 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -22,19 +22,15 @@
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
@@ -47,11 +43,12 @@
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(
@@ -87,6 +84,85 @@
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
@@ -97,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']]
@@ -116,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
@@ -137,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):
@@ -149,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",
@@ -157,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):
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 1ef9aa1..b21aa44 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -17,7 +17,7 @@
import hashlib
import random
import re
-from six import moves
+import six
import time
import zlib
@@ -54,15 +54,36 @@
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
segments = 10
- data_segments = [data + str(i) for i in moves.xrange(segments)]
+ data_segments = [data + str(i) for i in six.moves.xrange(segments)]
# uploading segments
- for i in moves.xrange(segments):
+ for i in six.moves.xrange(segments):
resp, _ = self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
self.assertEqual(resp['status'], '201')
return object_name, data_segments
+ def _copy_object_2d(self, src_object_name, metadata=None):
+ dst_object_name = data_utils.rand_name(name='TestObject')
+ resp, _ = self.object_client.copy_object_2d_way(self.container_name,
+ src_object_name,
+ dst_object_name,
+ metadata=metadata)
+ return dst_object_name, resp
+
+ def _check_copied_obj(self, dst_object_name, src_body,
+ in_meta=None, not_in_meta=None):
+ resp, dest_body = self.object_client.get_object(self.container_name,
+ dst_object_name)
+
+ self.assertEqual(src_body, dest_body)
+ if in_meta:
+ for meta_key in in_meta:
+ self.assertIn('x-object-meta-' + meta_key, resp)
+ if not_in_meta:
+ for meta_key in not_in_meta:
+ self.assertNotIn('x-object-meta-' + meta_key, resp)
+
@test.attr(type='gate')
def test_create_object(self):
# create object
@@ -765,10 +786,7 @@
# change the content type of an existing object
# create object
- object_name = data_utils.rand_name(name='TestObject')
- data = data_utils.arbitrary_string()
- self.object_client.create_object(self.container_name,
- object_name, data)
+ object_name, data = self._create_object()
# get the old content type
resp_tmp, _ = self.object_client.list_object_metadata(
self.container_name, object_name)
@@ -805,20 +823,12 @@
dst_object_name)
self.assertEqual(resp['status'], '201')
self.assertHeaders(resp, 'Object', 'COPY')
-
- self.assertIn('last-modified', resp)
- self.assertIn('x-copied-from', resp)
- self.assertIn('x-copied-from-last-modified', resp)
- self.assertNotEqual(len(resp['last-modified']), 0)
self.assertEqual(
resp['x-copied-from'],
self.container_name + "/" + src_object_name)
- self.assertNotEqual(len(resp['x-copied-from-last-modified']), 0)
# check data
- resp, body = self.object_client.get_object(self.container_name,
- dst_object_name)
- self.assertEqual(body, src_data)
+ self._check_copied_obj(dst_object_name, src_data)
@test.attr(type='smoke')
def test_copy_object_across_containers(self):
@@ -862,15 +872,82 @@
self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
+ @test.attr(type='smoke')
+ def test_copy_object_with_x_fresh_metadata(self):
+ # create source object
+ metadata = {'x-object-meta-src': 'src_value'}
+ src_object_name, data = self._create_object(metadata)
+
+ # copy source object with x_fresh_metadata header
+ metadata = {'X-Fresh-Metadata': 'true'}
+ dst_object_name, resp = self._copy_object_2d(src_object_name,
+ metadata)
+
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ self.assertNotIn('x-object-meta-src', resp)
+ self.assertEqual(resp['x-copied-from'],
+ self.container_name + "/" + src_object_name)
+
+ # check that destination object does NOT have any object-meta
+ self._check_copied_obj(dst_object_name, data, not_in_meta=["src"])
+
+ @test.attr(type='smoke')
+ def test_copy_object_with_x_object_metakey(self):
+ # create source object
+ metadata = {'x-object-meta-src': 'src_value'}
+ src_obj_name, data = self._create_object(metadata)
+
+ # copy source object to destination with x-object-meta-key
+ metadata = {'x-object-meta-test': ''}
+ dst_obj_name, resp = self._copy_object_2d(src_obj_name, metadata)
+
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ expected = {'x-object-meta-test': '',
+ 'x-object-meta-src': 'src_value',
+ 'x-copied-from': self.container_name + "/" + src_obj_name}
+ for key, value in six.iteritems(expected):
+ self.assertIn(key, resp)
+ self.assertEqual(value, resp[key])
+
+ # check destination object
+ self._check_copied_obj(dst_obj_name, data, in_meta=["test", "src"])
+
+ @test.attr(type='smoke')
+ def test_copy_object_with_x_object_meta(self):
+ # create source object
+ metadata = {'x-object-meta-src': 'src_value'}
+ src_obj_name, data = self._create_object(metadata)
+
+ # copy source object to destination with object metadata
+ metadata = {'x-object-meta-test': 'value'}
+ dst_obj_name, resp = self._copy_object_2d(src_obj_name, metadata)
+
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ expected = {'x-object-meta-test': 'value',
+ 'x-object-meta-src': 'src_value',
+ 'x-copied-from': self.container_name + "/" + src_obj_name}
+ for key, value in six.iteritems(expected):
+ self.assertIn(key, resp)
+ self.assertEqual(value, resp[key])
+
+ # check destination object
+ self._check_copied_obj(dst_obj_name, data, in_meta=["test", "src"])
+
@test.attr(type='gate')
def test_object_upload_in_segments(self):
# create object
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
segments = 10
- data_segments = [data + str(i) for i in moves.xrange(segments)]
+ data_segments = [data + str(i) for i in six.moves.xrange(segments)]
# uploading segments
- for i in moves.xrange(segments):
+ for i in six.moves.xrange(segments):
resp, _ = self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
self.assertEqual(resp['status'], '201')
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 446f4ab..531df2d 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -11,6 +11,7 @@
# under the License.
import os.path
+import yaml
from tempest import clients
from tempest.common.utils import data_utils
@@ -84,11 +85,8 @@
pass
for stack_identifier in cls.stacks:
- try:
- cls.client.wait_for_stack_status(
- stack_identifier, 'DELETE_COMPLETE')
- except exceptions.NotFound:
- pass
+ cls.client.wait_for_stack_status(
+ stack_identifier, 'DELETE_COMPLETE')
@classmethod
def _create_keypair(cls, name_start='keypair-heat-'):
@@ -125,7 +123,7 @@
pass
@classmethod
- def load_template(cls, name, ext='yaml'):
+ def read_template(cls, name, ext='yaml'):
loc = ["stacks", "templates", "%s.%s" % (name, ext)]
fullpath = os.path.join(os.path.dirname(__file__), *loc)
@@ -134,6 +132,14 @@
return content
@classmethod
+ def load_template(cls, name, ext='yaml'):
+ loc = ["stacks", "templates", "%s.%s" % (name, ext)]
+ fullpath = os.path.join(os.path.dirname(__file__), *loc)
+
+ with open(fullpath, "r") as f:
+ return yaml.safe_load(f)
+
+ @classmethod
def tearDownClass(cls):
cls._clear_stacks()
cls._clear_keypairs()
diff --git a/tempest/api/orchestration/stacks/test_environment.py b/tempest/api/orchestration/stacks/test_environment.py
index 3911e72..96e1c50 100644
--- a/tempest/api/orchestration/stacks/test_environment.py
+++ b/tempest/api/orchestration/stacks/test_environment.py
@@ -28,7 +28,7 @@
def test_environment_parameter(self):
"""Test passing a stack parameter via the environment."""
stack_name = data_utils.rand_name('heat')
- template = self.load_template('random_string')
+ template = self.read_template('random_string')
environment = {'parameters': {'random_length': 20}}
stack_identifier = self.create_stack(stack_name, template,
@@ -56,7 +56,7 @@
'''
environment = {'resource_registry':
{'My:Random::String': 'my_random.yaml'}}
- files = {'my_random.yaml': self.load_template('random_string')}
+ files = {'my_random.yaml': self.read_template('random_string')}
stack_identifier = self.create_stack(stack_name, template,
environment=environment,
@@ -65,7 +65,10 @@
# random_string.yaml specifies a length of 10
random_value = self.get_stack_output(stack_identifier, 'random_value')
- self.assertEqual(10, len(random_value))
+ random_string_template = self.load_template('random_string')
+ expected_length = random_string_template['parameters'][
+ 'random_length']['default']
+ self.assertEqual(expected_length, len(random_value))
@test.attr(type='gate')
def test_files_provider_resource(self):
@@ -82,7 +85,7 @@
random_value:
value: {get_attr: [random, random_value]}
'''
- files = {'my_random.yaml': self.load_template('random_string')}
+ files = {'my_random.yaml': self.read_template('random_string')}
stack_identifier = self.create_stack(stack_name, template,
files=files)
@@ -90,4 +93,7 @@
# random_string.yaml specifies a length of 10
random_value = self.get_stack_output(stack_identifier, 'random_value')
- self.assertEqual(10, len(random_value))
+ random_string_template = self.load_template('random_string')
+ expected_length = random_string_template['parameters'][
+ 'random_length']['default']
+ self.assertEqual(expected_length, len(random_value))
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index e92b945..27c6196 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -39,7 +39,7 @@
raise cls.skipException("Neutron support is required")
cls.network_client = os.network_client
cls.stack_name = data_utils.rand_name('heat')
- template = cls.load_template('neutron_basic')
+ template = cls.read_template('neutron_basic')
cls.keypair_name = (CONF.orchestration.keypair_name or
cls._create_keypair()['name'])
cls.external_network_id = CONF.network.public_network_id
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 585c90b..a97c561 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -28,7 +28,7 @@
def setUpClass(cls):
super(StacksTestJSON, cls).setUpClass()
cls.stack_name = data_utils.rand_name('heat')
- template = cls.load_template('non_empty_stack')
+ template = cls.read_template('non_empty_stack')
image_id = (CONF.orchestration.image_ref or
cls._create_image()['id'])
# create the stack
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
index a81a540..e3ffdaf 100644
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -23,12 +23,14 @@
class NovaKeyPairResourcesYAMLTest(base.BaseOrchestrationTest):
_tpl_type = 'yaml'
+ _resource = 'resources'
+ _type = 'type'
@classmethod
def setUpClass(cls):
super(NovaKeyPairResourcesYAMLTest, cls).setUpClass()
cls.stack_name = data_utils.rand_name('heat')
- template = cls.load_template('nova_keypair', ext=cls._tpl_type)
+ template = cls.read_template('nova_keypair', ext=cls._tpl_type)
# create the stack, avoid any duplicated key.
cls.stack_identifier = cls.create_stack(
@@ -46,11 +48,18 @@
for resource in resources:
cls.test_resources[resource['logical_resource_id']] = resource
- @test.attr(type='slow')
+ @test.attr(type='gate')
def test_created_resources(self):
"""Verifies created keypair resource."""
- resources = [('KeyPairSavePrivate', 'OS::Nova::KeyPair'),
- ('KeyPairDontSavePrivate', 'OS::Nova::KeyPair')]
+
+ nova_keypair_template = self.load_template('nova_keypair',
+ ext=self._tpl_type)
+ resources = [('KeyPairSavePrivate',
+ nova_keypair_template[self._resource][
+ 'KeyPairSavePrivate'][self._type]),
+ ('KeyPairDontSavePrivate',
+ nova_keypair_template[self._resource][
+ 'KeyPairDontSavePrivate'][self._type])]
for resource_name, resource_type in resources:
resource = self.test_resources.get(resource_name, None)
@@ -59,7 +68,7 @@
self.assertEqual(resource_type, resource['resource_type'])
self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
- @test.attr(type='slow')
+ @test.attr(type='gate')
def test_stack_keypairs_output(self):
resp, stack = self.client.get_stack(self.stack_name)
self.assertEqual('200', resp['status'])
@@ -85,3 +94,5 @@
class NovaKeyPairResourcesAWSTest(NovaKeyPairResourcesYAMLTest):
_tpl_type = 'json'
+ _resource = 'Resources'
+ _type = 'Type'
diff --git a/tempest/api/orchestration/stacks/test_soft_conf.py b/tempest/api/orchestration/stacks/test_soft_conf.py
new file mode 100644
index 0000000..8903d4c
--- /dev/null
+++ b/tempest/api/orchestration/stacks/test_soft_conf.py
@@ -0,0 +1,163 @@
+# 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.orchestration import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest import test
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class TestSoftwareConfig(base.BaseOrchestrationTest):
+
+ def setUp(self):
+ super(TestSoftwareConfig, self).setUp()
+ self.configs = []
+ # Add 2 sets of software configuration
+ self.configs.append(self._config_create('a'))
+ self.configs.append(self._config_create('b'))
+ # Create a deployment using config a's id
+ self._deployment_create(self.configs[0]['id'])
+
+ def _config_create(self, suffix):
+ configuration = {'group': 'script',
+ 'inputs': [],
+ 'outputs': [],
+ 'options': {}}
+ configuration['name'] = 'heat_soft_config_%s' % suffix
+ configuration['config'] = '#!/bin/bash echo init-%s' % suffix
+ api_config = self.client.create_software_config(**configuration)
+ configuration['id'] = api_config['software_config']['id']
+ self.addCleanup(self._config_delete, configuration['id'])
+ self._validate_config(configuration, api_config)
+ return configuration
+
+ def _validate_config(self, configuration, api_config):
+ # Assert all expected keys are present with matching data
+ for k in configuration.keys():
+ self.assertEqual(configuration[k],
+ api_config['software_config'][k])
+
+ def _deployment_create(self, config_id):
+ self.server_id = data_utils.rand_name('dummy-server')
+ self.action = 'ACTION_0'
+ self.status = 'STATUS_0'
+ self.input_values = {}
+ self.output_values = []
+ self.status_reason = 'REASON_0'
+ self.signal_transport = 'NO_SIGNAL'
+ self.deployment = self.client.create_software_deploy(
+ self.server_id, config_id, self.action, self.status,
+ self.input_values, self.output_values, self.status_reason,
+ self.signal_transport)
+ self.deployment_id = self.deployment['software_deployment']['id']
+ self.addCleanup(self._deployment_delete, self.deployment_id)
+
+ def _deployment_delete(self, deploy_id):
+ self.client.delete_software_deploy(deploy_id)
+ # Testing that it is really gone
+ self.assertRaises(
+ exceptions.NotFound, self.client.get_software_deploy,
+ self.deployment_id)
+
+ def _config_delete(self, config_id):
+ self.client.delete_software_config(config_id)
+ # Testing that it is really gone
+ self.assertRaises(
+ exceptions.NotFound, self.client.get_software_config, config_id)
+
+ @test.attr(type='smoke')
+ def test_get_software_config(self):
+ """Testing software config get."""
+ for conf in self.configs:
+ api_config = self.client.get_software_config(conf['id'])
+ self._validate_config(conf, api_config)
+
+ @test.attr(type='smoke')
+ def test_get_deployment_list(self):
+ """Getting a list of all deployments"""
+ deploy_list = self.client.get_software_deploy_list()
+ deploy_ids = [deploy['id'] for deploy in
+ deploy_list['software_deployments']]
+ self.assertIn(self.deployment_id, deploy_ids)
+
+ @test.attr(type='smoke')
+ def test_get_deployment_metadata(self):
+ """Testing deployment metadata get"""
+ metadata = self.client.get_software_deploy_meta(self.server_id)
+ conf_ids = [conf['id'] for conf in metadata['metadata']]
+ self.assertIn(self.configs[0]['id'], conf_ids)
+
+ def _validate_deployment(self, action, status, reason, config_id):
+ deployment = self.client.get_software_deploy(self.deployment_id)
+ self.assertEqual(action, deployment['software_deployment']['action'])
+ self.assertEqual(status, deployment['software_deployment']['status'])
+ self.assertEqual(reason,
+ deployment['software_deployment']['status_reason'])
+ self.assertEqual(config_id,
+ deployment['software_deployment']['config_id'])
+
+ @test.attr(type='smoke')
+ def test_software_deployment_create_validate(self):
+ """Testing software deployment was created as expected."""
+ # Asserting that all fields were created
+ self.assert_fields_in_dict(
+ self.deployment['software_deployment'], 'action', 'config_id',
+ 'id', 'input_values', 'output_values', 'server_id', 'status',
+ 'status_reason')
+ # Testing get for this deployment and verifying parameters
+ self._validate_deployment(self.action, self.status,
+ self.status_reason, self.configs[0]['id'])
+
+ @test.attr(type='smoke')
+ def test_software_deployment_update_no_metadata_change(self):
+ """Testing software deployment update without metadata change."""
+ metadata = self.client.get_software_deploy_meta(self.server_id)
+ # Updating values without changing the configuration ID
+ new_action = 'ACTION_1'
+ new_status = 'STATUS_1'
+ new_reason = 'REASON_1'
+ self.client.update_software_deploy(
+ self.deployment_id, self.server_id, self.configs[0]['id'],
+ new_action, new_status, self.input_values, self.output_values,
+ new_reason, self.signal_transport)
+ # Verifying get and that the deployment was updated as expected
+ self._validate_deployment(new_action, new_status,
+ new_reason, self.configs[0]['id'])
+
+ # Metadata should not be changed at this point
+ test_metadata = self.client.get_software_deploy_meta(self.server_id)
+ for key in metadata['metadata'][0]:
+ self.assertEqual(
+ metadata['metadata'][0][key],
+ test_metadata['metadata'][0][key])
+
+ @test.attr(type='smoke')
+ def test_software_deployment_update_with_metadata_change(self):
+ """Testing software deployment update with metadata change."""
+ metadata = self.client.get_software_deploy_meta(self.server_id)
+ self.client.update_software_deploy(
+ self.deployment_id, self.server_id, self.configs[1]['id'],
+ self.action, self.status, self.input_values,
+ self.output_values, self.status_reason, self.signal_transport)
+ self._validate_deployment(self.action, self.status,
+ self.status_reason, self.configs[1]['id'])
+ # Metadata should now be changed
+ new_metadata = self.client.get_software_deploy_meta(self.server_id)
+ # Its enough to test the ID in this case
+ meta_id = metadata['metadata'][0]['id']
+ test_id = new_metadata['metadata'][0]['id']
+ self.assertNotEqual(meta_id, test_id)
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 5b45d82..d5e66e8 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -64,3 +64,4 @@
# delete the stack
resp = self.client.delete_stack(stack_identifier)
self.assertEqual('204', resp[0]['status'])
+ self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
diff --git a/tempest/api/orchestration/stacks/test_swift_resources.py b/tempest/api/orchestration/stacks/test_swift_resources.py
index 6d53fb2..adab8c3 100644
--- a/tempest/api/orchestration/stacks/test_swift_resources.py
+++ b/tempest/api/orchestration/stacks/test_swift_resources.py
@@ -30,7 +30,7 @@
def setUpClass(cls):
super(SwiftResourcesTestJSON, cls).setUpClass()
cls.stack_name = data_utils.rand_name('heat')
- template = cls.load_template('swift_basic')
+ template = cls.read_template('swift_basic')
os = clients.Manager()
if not CONF.service_available.swift:
raise cls.skipException("Swift support is required")
@@ -49,8 +49,11 @@
def test_created_resources(self):
"""Created stack should be in the list of existing stacks."""
- resources = [('SwiftContainer', 'OS::Swift::Container'),
- ('SwiftContainerWebsite', 'OS::Swift::Container')]
+ swift_basic_template = self.load_template('swift_basic')
+ resources = [('SwiftContainer', swift_basic_template['resources'][
+ 'SwiftContainer']['type']),
+ ('SwiftContainerWebsite', swift_basic_template[
+ 'resources']['SwiftContainerWebsite']['type'])]
for resource_name, resource_type in resources:
resource = self.test_resources.get(resource_name)
self.assertIsInstance(resource, dict)
@@ -84,10 +87,9 @@
self.assertIn(h, headers)
def test_metadata(self):
- metadatas = {
- "web-index": "index.html",
- "web-error": "error.html"
- }
+ swift_basic_template = self.load_template('swift_basic')
+ metadatas = swift_basic_template['resources']['SwiftContainerWebsite'][
+ 'properties']['X-Container-Meta']
swcont_website = self.test_resources.get(
'SwiftContainerWebsite')['physical_resource_id']
headers, _ = self.container_client.list_container_metadata(
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index 5ac2a8d..d422752 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -31,43 +31,44 @@
if not CONF.service_available.cinder:
raise cls.skipException('Cinder support is required')
- def _cinder_verify(self, volume_id):
+ def _cinder_verify(self, volume_id, template):
self.assertIsNotNone(volume_id)
resp, volume = self.volumes_client.get_volume(volume_id)
self.assertEqual(200, resp.status)
self.assertEqual('available', volume.get('status'))
- self.assertEqual(1, volume.get('size'))
- self.assertEqual('a descriptive description',
- volume.get('display_description'))
- self.assertEqual('volume_name',
- volume.get('display_name'))
+ self.assertEqual(template['resources']['volume']['properties'][
+ 'size'], volume.get('size'))
+ self.assertEqual(template['resources']['volume']['properties'][
+ 'description'], volume.get('display_description'))
+ self.assertEqual(template['resources']['volume']['properties'][
+ 'name'], volume.get('display_name'))
- def _outputs_verify(self, stack_identifier):
+ def _outputs_verify(self, stack_identifier, template):
self.assertEqual('available',
self.get_stack_output(stack_identifier, 'status'))
- self.assertEqual('1',
- self.get_stack_output(stack_identifier, 'size'))
- 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'))
+ self.assertEqual(str(template['resources']['volume']['properties'][
+ 'size']), self.get_stack_output(stack_identifier, 'size'))
+ self.assertEqual(template['resources']['volume']['properties'][
+ 'description'], self.get_stack_output(stack_identifier,
+ 'display_description'))
+ self.assertEqual(template['resources']['volume']['properties'][
+ 'name'], self.get_stack_output(stack_identifier, 'display_name'))
@test.attr(type='gate')
def test_cinder_volume_create_delete(self):
"""Create and delete a volume via OS::Cinder::Volume."""
stack_name = data_utils.rand_name('heat')
- template = self.load_template('cinder_basic')
+ template = self.read_template('cinder_basic')
stack_identifier = self.create_stack(stack_name, template)
self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
# Verify with cinder that the volume exists, with matching details
volume_id = self.get_stack_output(stack_identifier, 'volume_id')
- self._cinder_verify(volume_id)
+ cinder_basic_template = self.load_template('cinder_basic')
+ self._cinder_verify(volume_id, cinder_basic_template)
# Verify the stack outputs are as expected
- self._outputs_verify(stack_identifier)
+ self._outputs_verify(stack_identifier, cinder_basic_template)
# Delete the stack and ensure the volume is gone
self.client.delete_stack(stack_identifier)
@@ -86,21 +87,22 @@
def test_cinder_volume_create_delete_retain(self):
"""Ensure the 'Retain' deletion policy is respected."""
stack_name = data_utils.rand_name('heat')
- template = self.load_template('cinder_basic_delete_retain')
+ template = self.read_template('cinder_basic_delete_retain')
stack_identifier = self.create_stack(stack_name, template)
self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
# Verify with cinder that the volume exists, with matching details
volume_id = self.get_stack_output(stack_identifier, 'volume_id')
self.addCleanup(self._cleanup_volume, volume_id)
- self._cinder_verify(volume_id)
+ retain_template = self.load_template('cinder_basic_delete_retain')
+ self._cinder_verify(volume_id, retain_template)
# Verify the stack outputs are as expected
- self._outputs_verify(stack_identifier)
+ self._outputs_verify(stack_identifier, retain_template)
# Delete the stack and ensure the volume is *not* gone
self.client.delete_stack(stack_identifier)
self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
- self._cinder_verify(volume_id)
+ self._cinder_verify(volume_id, retain_template)
# Volume cleanup happens via addCleanup calling _cleanup_volume
diff --git a/tempest/api/telemetry/base.py b/tempest/api/telemetry/base.py
index 2b422fd..b5b2bb1 100644
--- a/tempest/api/telemetry/base.py
+++ b/tempest/api/telemetry/base.py
@@ -34,19 +34,27 @@
cls.telemetry_client = os.telemetry_client
cls.servers_client = os.servers_client
cls.flavors_client = os.flavors_client
+ cls.image_client = os.image_client
+ cls.image_client_v2 = os.image_client_v2
cls.nova_notifications = ['memory', 'vcpus', 'disk.root.size',
'disk.ephemeral.size']
+
+ cls.glance_notifications = ['image.update', 'image.upload',
+ 'image.delete']
+
+ cls.glance_v2_notifications = ['image.download', 'image.serve']
+
cls.server_ids = []
cls.alarm_ids = []
+ cls.image_ids = []
@classmethod
def create_alarm(cls, **kwargs):
resp, body = cls.telemetry_client.create_alarm(
name=data_utils.rand_name('telemetry_alarm'),
type='threshold', **kwargs)
- if resp['status'] == '201':
- cls.alarm_ids.append(body['alarm_id'])
+ cls.alarm_ids.append(body['alarm_id'])
return resp, body
@classmethod
@@ -55,8 +63,15 @@
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'])
+ cls.server_ids.append(body['id'])
+ return resp, body
+
+ @classmethod
+ def create_image(cls, client):
+ resp, body = client.create_image(
+ data_utils.rand_name('image'), container_format='bare',
+ disk_format='raw', visibility='private')
+ cls.image_ids.append(body['id'])
return resp, body
@staticmethod
@@ -71,6 +86,7 @@
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.cleanup_resources(cls.image_client.delete_image, cls.image_ids)
cls.clear_isolated_creds()
super(BaseTelemetryTest, cls).tearDownClass()
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
index 148f5a3..9b15c51 100644
--- a/tempest/api/telemetry/test_telemetry_notification_api.py
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -32,6 +32,7 @@
@test.attr(type="gate")
@testtools.skipIf(not CONF.service_available.nova,
"Nova is not available.")
+ @test.skip_because(bug="1336755")
def test_check_nova_notification(self):
resp, body = self.create_server()
@@ -42,6 +43,38 @@
for metric in self.nova_notifications:
self.await_samples(metric, query)
+ @test.attr(type="smoke")
+ @test.services("image")
+ @testtools.skipIf(not CONF.image_feature_enabled.api_v1,
+ "Glance api v1 is disabled")
+ @test.skip_because(bug='1351627')
+ def test_check_glance_v1_notifications(self):
+ _, body = self.create_image(self.image_client)
+ self.image_client.update_image(body['id'], data='data')
+
+ query = 'resource', 'eq', body['id']
+
+ self.image_client.delete_image(body['id'])
+
+ for metric in self.glance_notifications:
+ self.await_samples(metric, query)
+
+ @test.attr(type="smoke")
+ @test.services("image")
+ @testtools.skipIf(not CONF.image_feature_enabled.api_v2,
+ "Glance api v2 is disabled")
+ @test.skip_because(bug='1351627')
+ def test_check_glance_v2_notifications(self):
+ _, body = self.create_image(self.image_client_v2)
+
+ self.image_client_v2.store_image(body['id'], "file")
+ self.image_client_v2.get_image_file(body['id'])
+
+ query = 'resource', 'eq', body['id']
+
+ for metric in self.glance_v2_notifications:
+ self.await_samples(metric, query)
+
class TelemetryNotificationAPITestXML(TelemetryNotificationAPITestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index e79d23c..d451517 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -36,42 +36,55 @@
cls.volume_client = cls.os_adm.volumes_client
cls.volume_type_id_list = []
- cls.volume_id_list = []
+ cls.volume_id_list_with_prefix = []
+ cls.volume_id_list_without_prefix = []
- # Volume/Type creation (uses backend1_name)
- type1_name = data_utils.rand_name('Type-')
- vol1_name = data_utils.rand_name('Volume-')
- extra_specs1 = {"volume_backend_name": cls.backend1_name}
- resp, cls.type1 = cls.client.create_volume_type(
- type1_name, extra_specs=extra_specs1)
- cls.volume_type_id_list.append(cls.type1['id'])
-
- resp, cls.volume1 = cls.volume_client.create_volume(
- size=1, display_name=vol1_name, volume_type=type1_name)
- cls.volume_id_list.append(cls.volume1['id'])
- cls.volume_client.wait_for_volume_status(cls.volume1['id'],
- 'available')
+ # Volume/Type creation (uses volume_backend_name)
+ cls._create_type_and_volume(cls.backend1_name, False)
+ # Volume/Type creation (uses capabilities:volume_backend_name)
+ cls._create_type_and_volume(cls.backend1_name, True)
if cls.backend1_name != cls.backend2_name:
# Volume/Type creation (uses backend2_name)
- type2_name = data_utils.rand_name('Type-')
- vol2_name = data_utils.rand_name('Volume-')
- extra_specs2 = {"volume_backend_name": cls.backend2_name}
- resp, cls.type2 = cls.client.create_volume_type(
- type2_name, extra_specs=extra_specs2)
- cls.volume_type_id_list.append(cls.type2['id'])
+ cls._create_type_and_volume(cls.backend2_name, False)
+ # Volume/Type creation (uses capabilities:volume_backend_name)
+ cls._create_type_and_volume(cls.backend2_name, True)
- resp, cls.volume2 = cls.volume_client.create_volume(
- size=1, display_name=vol2_name, volume_type=type2_name)
- cls.volume_id_list.append(cls.volume2['id'])
- cls.volume_client.wait_for_volume_status(cls.volume2['id'],
- 'available')
+ @classmethod
+ def _create_type_and_volume(self, backend_name_key, with_prefix):
+ # Volume/Type creation
+ type_name = data_utils.rand_name('Type')
+ vol_name = data_utils.rand_name('Volume')
+ spec_key_with_prefix = "capabilities:volume_backend_name"
+ spec_key_without_prefix = "volume_backend_name"
+ if with_prefix:
+ extra_specs = {spec_key_with_prefix: backend_name_key}
+ else:
+ extra_specs = {spec_key_without_prefix: backend_name_key}
+ resp, self.type = self.client.create_volume_type(
+ type_name, extra_specs=extra_specs)
+ self.volume_type_id_list.append(self.type['id'])
+
+ resp, self.volume = self.volume_client.create_volume(
+ size=1, display_name=vol_name, volume_type=type_name)
+ self.volume_client.wait_for_volume_status(
+ self.volume['id'], 'available')
+ if with_prefix:
+ self.volume_id_list_with_prefix.append(self.volume['id'])
+ else:
+ self.volume_id_list_without_prefix.append(
+ self.volume['id'])
@classmethod
def tearDownClass(cls):
# volumes deletion
- volume_id_list = getattr(cls, 'volume_id_list', [])
- for volume_id in volume_id_list:
+ vid_prefix = getattr(cls, 'volume_id_list_with_prefix', [])
+ for volume_id in vid_prefix:
+ cls.volume_client.delete_volume(volume_id)
+ cls.volume_client.wait_for_resource_deletion(volume_id)
+
+ vid_no_pre = getattr(cls, 'volume_id_list_without_prefix', [])
+ for volume_id in vid_no_pre:
cls.volume_client.delete_volume(volume_id)
cls.volume_client.wait_for_resource_deletion(volume_id)
@@ -84,32 +97,57 @@
@test.attr(type='smoke')
def test_backend_name_reporting(self):
+ # get volume id which created by type without prefix
+ volume_id = self.volume_id_list_without_prefix[0]
+ self._test_backend_name_reporting_by_volume_id(volume_id)
+
+ @test.attr(type='smoke')
+ def test_backend_name_reporting_with_prefix(self):
+ # get volume id which created by type with prefix
+ volume_id = self.volume_id_list_with_prefix[0]
+ self._test_backend_name_reporting_by_volume_id(volume_id)
+
+ @test.attr(type='gate')
+ def test_backend_name_distinction(self):
+ if self.backend1_name == self.backend2_name:
+ raise self.skipException("backends configured with same name")
+ # get volume id which created by type without prefix
+ volume1_id = self.volume_id_list_without_prefix[0]
+ volume2_id = self.volume_id_list_without_prefix[1]
+ self._test_backend_name_distinction(volume1_id, volume2_id)
+
+ @test.attr(type='gate')
+ def test_backend_name_distinction_with_prefix(self):
+ if self.backend1_name == self.backend2_name:
+ raise self.skipException("backends configured with same name")
+ # get volume id which created by type without prefix
+ volume1_id = self.volume_id_list_with_prefix[0]
+ volume2_id = self.volume_id_list_with_prefix[1]
+ self._test_backend_name_distinction(volume1_id, volume2_id)
+
+ def _test_backend_name_reporting_by_volume_id(self, volume_id):
# this test checks if os-vol-attr:host is populated correctly after
# the multi backend feature has been enabled
# if multi-backend is enabled: os-vol-attr:host should be like:
# host@backend_name
- resp, volume = self.volume_client.get_volume(self.volume1['id'])
+ resp, volume = self.volume_client.get_volume(volume_id)
self.assertEqual(200, resp.status)
volume1_host = volume['os-vol-host-attr:host']
msg = ("multi-backend reporting incorrect values for volume %s" %
- self.volume1['id'])
+ volume_id)
self.assertTrue(len(volume1_host.split("@")) > 1, msg)
- @test.attr(type='gate')
- def test_backend_name_distinction(self):
+ def _test_backend_name_distinction(self, volume1_id, volume2_id):
# this test checks that the two volumes created at setUp don't
# belong to the same backend (if they are, than the
# volume backend distinction is not working properly)
- if self.backend1_name == self.backend2_name:
- raise self.skipException("backends configured with same name")
-
- resp, volume = self.volume_client.get_volume(self.volume1['id'])
+ resp, volume = self.volume_client.get_volume(volume1_id)
volume1_host = volume['os-vol-host-attr:host']
- resp, volume = self.volume_client.get_volume(self.volume2['id'])
+ resp, volume = self.volume_client.get_volume(volume2_id)
volume2_host = volume['os-vol-host-attr:host']
msg = ("volumes %s and %s were created in the same backend" %
- (self.volume1['id'], self.volume2['id']))
+ (volume1_id, volume2_id))
self.assertNotEqual(volume1_host, volume2_host, msg)
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 2a9b407..b7de767 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -61,12 +61,23 @@
cls.volumes_extension_client = cls.os.volumes_extension_client
cls.availability_zone_client = (
cls.os.volume_availability_zone_client)
+ # Special fields and resp code for cinder v1
+ cls.special_fields = {'name_field': 'display_name',
+ 'descrip_field': 'display_description',
+ 'create_resp': 200}
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
+ cls.volumes_extension_client = cls.os.volumes_v2_extension_client
+ cls.availability_zone_client = (
+ cls.os.volume_v2_availability_zone_client)
+ # Special fields and resp code for cinder v2
+ cls.special_fields = {'name_field': 'name',
+ 'descrip_field': 'description',
+ 'create_resp': 202}
else:
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
@@ -82,15 +93,15 @@
@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
+ name = data_utils.rand_name('Volume')
+
+ name_field = cls.special_fields['name_field']
+ expect_status = cls.special_fields['create_resp']
+
+ kwargs[name_field] = name
+ resp, volume = cls.volumes_client.create_volume(size, **kwargs)
+ assert expect_status == resp.status
+
cls.volumes.append(volume)
cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
return volume
@@ -142,10 +153,6 @@
_api_version = 1
-class BaseVolumeV2Test(BaseVolumeTest):
- _api_version = 2
-
-
class BaseVolumeV1AdminTest(BaseVolumeV1Test):
"""Base test case class for all Volume Admin API tests."""
@classmethod
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index fe8f96e..25b7b85 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -17,16 +17,15 @@
from tempest import test
-class AvailabilityZoneTestJSON(base.BaseVolumeV1Test):
+class AvailabilityZoneV2TestJSON(base.BaseVolumeTest):
"""
- Tests Availability Zone API List
+ Tests Availability Zone V2 API List
"""
- _interface = 'json'
@classmethod
def setUpClass(cls):
- super(AvailabilityZoneTestJSON, cls).setUpClass()
+ super(AvailabilityZoneV2TestJSON, cls).setUpClass()
cls.client = cls.availability_zone_client
@test.attr(type='gate')
@@ -37,5 +36,13 @@
self.assertTrue(len(availability_zone) > 0)
-class AvailabilityZoneTestXML(AvailabilityZoneTestJSON):
+class AvailabilityZoneV2TestXML(AvailabilityZoneV2TestJSON):
+ _interface = 'xml'
+
+
+class AvailabilityZoneV1TestJSON(AvailabilityZoneV2TestJSON):
+ _api_version = 1
+
+
+class AvailabilityZoneV1TestXML(AvailabilityZoneV1TestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index ce019a2..ff00dd1 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -25,8 +25,7 @@
LOG = logging.getLogger(__name__)
-class ExtensionsTestJSON(base.BaseVolumeV1Test):
- _interface = 'json'
+class ExtensionsV2TestJSON(base.BaseVolumeTest):
@test.attr(type='gate')
def test_list_extensions(self):
@@ -46,5 +45,13 @@
raise self.skipException('There are not any extensions configured')
-class ExtensionsTestXML(ExtensionsTestJSON):
+class ExtensionsV2TestXML(ExtensionsV2TestJSON):
+ _interface = 'xml'
+
+
+class ExtensionsV1TestJSON(ExtensionsV2TestJSON):
+ _api_version = 1
+
+
+class ExtensionsV1TestXML(ExtensionsV1TestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index 0d57d47..0505f19 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -19,13 +19,12 @@
from tempest import test
-class VolumeMetadataTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2MetadataTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumeMetadataTest, cls).setUpClass()
+ super(VolumesV2MetadataTest, cls).setUpClass()
# Create a volume
cls.volume = cls.create_volume()
cls.volume_id = cls.volume['id']
@@ -33,7 +32,7 @@
def tearDown(self):
# Update the metadata to {}
self.volumes_client.update_volume_metadata(self.volume_id, {})
- super(VolumeMetadataTest, self).tearDown()
+ super(VolumesV2MetadataTest, self).tearDown()
@test.attr(type='gate')
def test_create_get_delete_volume_metadata(self):
@@ -117,5 +116,13 @@
self.assertThat(body.items(), matchers.ContainsAll(expect.items()))
-class VolumeMetadataTestXML(VolumeMetadataTest):
+class VolumesV2MetadataTestXML(VolumesV2MetadataTest):
+ _interface = "xml"
+
+
+class VolumesV1MetadataTest(VolumesV2MetadataTest):
+ _api_version = 1
+
+
+class VolumesV1MetadataTestXML(VolumesV1MetadataTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 82d1364..bf61222 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -23,12 +23,11 @@
CONF = config.CONF
-class VolumesTransfersTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2TransfersTest(base.BaseVolumeTest):
@classmethod
def setUpClass(cls):
- super(VolumesTransfersTest, cls).setUpClass()
+ super(VolumesV2TransfersTest, cls).setUpClass()
# Add another tenant to test volume-transfer
if CONF.compute.allow_tenant_isolation:
@@ -110,5 +109,13 @@
self.client.wait_for_volume_status(volume['id'], 'available')
-class VolumesTransfersTestXML(VolumesTransfersTest):
+class VolumesV2TransfersTestXML(VolumesV2TransfersTest):
+ _interface = "xml"
+
+
+class VolumesV1TransfersTest(VolumesV2TransfersTest):
+ _api_version = 1
+
+
+class VolumesV1TransfersTestXML(VolumesV1TransfersTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index cfab0bd..6fef564 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -21,13 +21,12 @@
CONF = config.CONF
-class VolumesActionsTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2ActionsTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesActionsTest, cls).setUpClass()
+ super(VolumesV2ActionsTest, cls).setUpClass()
cls.client = cls.volumes_client
cls.image_client = cls.os.image_client
@@ -47,7 +46,7 @@
cls.servers_client.delete_server(cls.server['id'])
cls.servers_client.wait_for_server_termination(cls.server['id'])
- super(VolumesActionsTest, cls).tearDownClass()
+ super(VolumesV2ActionsTest, cls).tearDownClass()
@test.stresstest(class_setup_per='process')
@test.attr(type='smoke')
@@ -165,5 +164,13 @@
self.assertEqual(False, bool_flag)
-class VolumesActionsTestXML(VolumesActionsTest):
+class VolumesV2ActionsTestXML(VolumesV2ActionsTest):
+ _interface = "xml"
+
+
+class VolumesV1ActionsTest(VolumesV2ActionsTest):
+ _api_version = 1
+
+
+class VolumesV1ActionsTestXML(VolumesV1ActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 2745b95..82208aa 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -23,14 +23,17 @@
CONF = config.CONF
-class VolumesGetTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2GetTest(base.BaseVolumeTest):
@classmethod
def setUpClass(cls):
- super(VolumesGetTest, cls).setUpClass()
+ super(VolumesV2GetTest, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name_field = cls.special_fields['name_field']
+ cls.descrip_field = cls.special_fields['descrip_field']
+ cls.create_resp = cls.special_fields['create_resp']
+
def _delete_volume(self, volume_id):
resp, _ = self.client.delete_volume(volume_id)
self.assertEqual(202, resp.status)
@@ -51,24 +54,24 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'Test'}
# Create a volume
- resp, volume = self.client.create_volume(display_name=v_name,
- metadata=metadata,
- **kwargs)
- self.assertEqual(200, resp.status)
+ kwargs[self.name_field] = v_name
+ kwargs['metadata'] = metadata
+ resp, volume = self.client.create_volume(**kwargs)
+ self.assertEqual(self.create_resp, resp.status)
self.assertIn('id', volume)
self.addCleanup(self._delete_volume, volume['id'])
- self.assertIn('display_name', volume)
- self.assertEqual(volume['display_name'], v_name,
+ self.client.wait_for_volume_status(volume['id'], 'available')
+ self.assertIn(self.name_field, volume)
+ self.assertEqual(volume[self.name_field], v_name,
"The created volume name is not equal "
"to the requested name")
self.assertTrue(volume['id'] is not None,
"Field volume id is empty or not found.")
- self.client.wait_for_volume_status(volume['id'], 'available')
# Get Volume information
resp, fetched_volume = self.client.get_volume(volume['id'])
self.assertEqual(200, resp.status)
self.assertEqual(v_name,
- fetched_volume['display_name'],
+ fetched_volume[self.name_field],
'The fetched Volume name is different '
'from the created Volume')
self.assertEqual(volume['id'],
@@ -90,27 +93,25 @@
# Update Volume
# Test volume update when display_name is same with original value
- resp, update_volume = \
- self.client.update_volume(volume['id'],
- display_name=v_name)
+ params = {self.name_field: v_name}
+ resp, update_volume = self.client.update_volume(volume['id'], **params)
self.assertEqual(200, resp.status)
# Test volume update when display_name is new
new_v_name = data_utils.rand_name('new-Volume')
new_desc = 'This is the new description of volume'
- resp, update_volume = \
- self.client.update_volume(volume['id'],
- display_name=new_v_name,
- display_description=new_desc)
+ params = {self.name_field: new_v_name,
+ self.descrip_field: new_desc}
+ resp, update_volume = self.client.update_volume(volume['id'], **params)
# Assert response body for update_volume method
self.assertEqual(200, resp.status)
- self.assertEqual(new_v_name, update_volume['display_name'])
- self.assertEqual(new_desc, update_volume['display_description'])
+ self.assertEqual(new_v_name, update_volume[self.name_field])
+ self.assertEqual(new_desc, update_volume[self.descrip_field])
# Assert response body for get_volume method
resp, updated_volume = self.client.get_volume(volume['id'])
self.assertEqual(200, resp.status)
self.assertEqual(volume['id'], updated_volume['id'])
- self.assertEqual(new_v_name, updated_volume['display_name'])
- self.assertEqual(new_desc, updated_volume['display_description'])
+ self.assertEqual(new_v_name, updated_volume[self.name_field])
+ self.assertEqual(new_desc, updated_volume[self.descrip_field])
self.assertThat(updated_volume['metadata'].items(),
matchers.ContainsAll(metadata.items()),
'The fetched Volume metadata misses data '
@@ -120,20 +121,18 @@
# then test volume update if display_name is duplicated
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.assertEqual(200, resp.status)
+ params = {self.descrip_field: new_v_desc,
+ 'availability_zone': volume['availability_zone']}
+ resp, new_volume = self.client.create_volume(size=1, **params)
+ self.assertEqual(self.create_resp, 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'])
+
+ params = {self.name_field: volume[self.name_field],
+ self.descrip_field: volume[self.descrip_field]}
+ resp, update_volume = self.client.update_volume(new_volume['id'],
+ **params)
self.assertEqual(200, resp.status)
# NOTE(jdg): Revert back to strict true/false checking
@@ -159,5 +158,13 @@
self._volume_create_get_update_delete(source_volid=origin['id'])
-class VolumesGetTestXML(VolumesGetTest):
+class VolumesV2GetTestXML(VolumesV2GetTest):
+ _interface = "xml"
+
+
+class VolumesV1GetTest(VolumesV2GetTest):
+ _api_version = 1
+
+
+class VolumesV1GetTestXML(VolumesV1GetTest):
_interface = "xml"
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 bc5b1dc..8bd4c88 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -21,15 +21,16 @@
from tempest import test
-class VolumesNegativeTest(base.BaseVolumeV1Test):
- _interface = 'json'
+class VolumesV2NegativeTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesNegativeTest, cls).setUpClass()
+ super(VolumesV2NegativeTest, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name_field = cls.special_fields['name_field']
+
# Create a test shared instance and volume for attach/detach tests
cls.volume = cls.create_volume()
cls.mountpoint = "/dev/vdc"
@@ -237,7 +238,7 @@
@test.attr(type=['negative', 'gate'])
def test_list_volumes_with_nonexistent_name(self):
v_name = data_utils.rand_name('Volume-')
- params = {'display_name': v_name}
+ params = {self.name_field: v_name}
resp, fetched_volume = self.client.list_volumes(params)
self.assertEqual(200, resp.status)
self.assertEqual(0, len(fetched_volume))
@@ -245,7 +246,7 @@
@test.attr(type=['negative', 'gate'])
def test_list_volumes_detail_with_nonexistent_name(self):
v_name = data_utils.rand_name('Volume-')
- params = {'display_name': v_name}
+ params = {self.name_field: v_name}
resp, fetched_volume = self.client.list_volumes_with_detail(params)
self.assertEqual(200, resp.status)
self.assertEqual(0, len(fetched_volume))
@@ -265,5 +266,14 @@
self.assertEqual(0, len(fetched_volume))
-class VolumesNegativeTestXML(VolumesNegativeTest):
+class VolumesV2NegativeTestXML(VolumesV2NegativeTest):
+ _interface = 'xml'
+
+
+class VolumesV1NegativeTest(VolumesV2NegativeTest):
+ _api_version = 1
+ _name = 'display_name'
+
+
+class VolumesV1NegativeTestXML(VolumesV1NegativeTest):
_interface = 'xml'
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 b9ad240..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'},
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 14e9ce9..a16e425 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -48,50 +48,52 @@
}
}
-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,
- },
- # 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']
- }
+ 'server': common_show_server
},
'required': ['server']
}
@@ -179,3 +181,40 @@
'required': ['action', 'request_id', 'user_id', 'project_id',
'start_time', 'message']
}
+
+instance_action_events = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'event': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'finish_time': {'type': 'string'},
+ 'result': {'type': 'string'},
+ 'traceback': {'type': ['string', 'null']}
+ },
+ 'required': ['event', 'start_time', 'finish_time', 'result',
+ 'traceback']
+ }
+}
+
+common_get_instance_action = copy.deepcopy(common_instance_actions)
+
+common_get_instance_action['properties'].update({
+ 'events': instance_action_events})
+# 'events' does not come in response body always so it is not
+# defined as 'required'
+
+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/floating_ips.py b/tempest/api_schema/compute/v2/floating_ips.py
index 782f5c8..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'
@@ -128,3 +128,31 @@
'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 fe53abd..4abadf4 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -25,20 +25,14 @@
'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'},
'OS-DCF:diskConfig': {'type': 'string'}
},
# NOTE: OS-DCF:diskConfig is API extension, and some
# environments return a response without the attribute.
# So it is not 'required'.
- # NOTE: adminPass is not required because it can be deactivated
- # with nova API flag enable_instance_password=False
'required': ['id', 'security_groups', 'links']
}
},
@@ -46,7 +40,13 @@
}
}
-update_server = copy.deepcopy(servers.base_update_server)
+create_server_with_admin_pass = copy.deepcopy(create_server)
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+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 +60,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': {
@@ -178,6 +211,20 @@
'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'}})
@@ -196,3 +243,55 @@
'required': ['instanceActions']
}
}
+
+get_instance_actions_object = copy.deepcopy(servers.common_get_instance_action)
+get_instance_actions_object[
+ 'properties'].update({'instance_uuid': {'type': 'string'}})
+get_instance_actions_object['required'].extend(['instance_uuid'])
+
+get_instance_action = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceAction': get_instance_actions_object
+ },
+ 'required': ['instanceAction']
+ }
+}
+
+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')
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+del rebuild_server['response_body']['properties']['server'][
+ 'properties']['OS-DCF:diskConfig']
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+rescue_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'adminPass': {'type': 'string'}
+ },
+ 'required': ['adminPass']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/volumes.py b/tempest/api_schema/compute/v2/volumes.py
index 84a659c..541d3ff 100644
--- a/tempest/api_schema/compute/v2/volumes.py
+++ b/tempest/api_schema/compute/v2/volumes.py
@@ -26,7 +26,7 @@
'availabilityZone': {'type': 'string'},
'createdAt': {'type': 'string'},
'displayDescription': {'type': ['string', 'null']},
- 'volumeType': {'type': 'string'},
+ 'volumeType': {'type': ['string', 'null']},
'snapshotId': {'type': ['string', 'null']},
'metadata': {'type': 'object'},
'size': {'type': 'integer'},
@@ -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
@@ -74,7 +74,7 @@
'availabilityZone': {'type': 'string'},
'createdAt': {'type': 'string'},
'displayDescription': {'type': ['string', 'null']},
- 'volumeType': {'type': 'string'},
+ 'volumeType': {'type': ['string', 'null']},
'snapshotId': {'type': ['string', 'null']},
'metadata': {'type': 'object'},
'size': {'type': 'integer'},
@@ -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/servers.py b/tempest/api_schema/compute/v3/servers.py
index 4fb2d87..a84ac3c 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -25,13 +25,9 @@
'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'},
'os-access-ips:access_ip_v4': parameter_types.access_ip_v4,
'os-access-ips:access_ip_v6': parameter_types.access_ip_v6
},
@@ -39,13 +35,19 @@
# and some environments return a response without these
# attributes. So they are not 'required'.
'required': ['id', 'os-security-groups:security_groups',
- 'links', 'admin_password']
+ 'links']
}
},
'required': ['server']
}
}
+create_server_with_admin_pass = copy.deepcopy(create_server)
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'admin_password': {'type': 'string'}})
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('admin_password')
+
addresses_v3 = copy.deepcopy(parameter_types.addresses)
addresses_v3['patternProperties']['^[a-zA-Z0-9-_.]+$']['items'][
'properties'].update({
@@ -57,7 +59,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 +73,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]
}
@@ -117,3 +156,57 @@
'required': ['server_actions']
}
}
+
+get_server_actions_object = copy.deepcopy(servers.common_get_instance_action)
+get_server_actions_object[
+ 'properties'].update({'server_uuid': {'type': 'string'}})
+get_server_actions_object['required'].extend(['server_uuid'])
+
+get_server_action = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_action': get_server_actions_object
+ },
+ 'required': ['server_action']
+ }
+}
+
+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')
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'admin_password': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('admin_password')
+
+rescue_server_with_admin_pass = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'admin_password': {'type': 'string'}
+ },
+ 'required': ['admin_password']
+ }
+}
+
+rescue_server = copy.deepcopy(rescue_server_with_admin_pass)
+del rescue_server['response_body']['properties']
+del rescue_server['response_body']['required']
diff --git a/tempest/auth.py b/tempest/auth.py
index 830dca9..5df6224 100644
--- a/tempest/auth.py
+++ b/tempest/auth.py
@@ -13,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import abc
import copy
import datetime
import exceptions
import re
+import six
import urlparse
from tempest import config
@@ -31,6 +33,7 @@
LOG = logging.getLogger(__name__)
+@six.add_metaclass(abc.ABCMeta)
class AuthProvider(object):
"""
Provide authentication
@@ -70,18 +73,21 @@
interface=self.interface, cache=self.cache
)
+ @abc.abstractmethod
def _decorate_request(self, filters, method, url, headers=None, body=None,
auth_data=None):
"""
Decorate request with authentication data
"""
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _get_auth(self):
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _fill_credentials(self, auth_data_body):
- raise NotImplementedError
+ return
def fill_credentials(self):
"""
@@ -130,8 +136,9 @@
self.cache = None
self.credentials.reset()
+ @abc.abstractmethod
def is_expired(self, auth_data):
- raise NotImplementedError
+ return
def auth_request(self, method, url, headers=None, body=None, filters=None):
"""
@@ -188,11 +195,12 @@
self.alt_part = request_part
self.alt_auth_data = auth_data
+ @abc.abstractmethod
def base_url(self, filters, auth_data=None):
"""
Extracts the base_url based on provided filters
"""
- raise NotImplementedError
+ return
class KeystoneAuthProvider(AuthProvider):
@@ -225,11 +233,13 @@
# no change to method or body
return str(_url), _headers, body
+ @abc.abstractmethod
def _auth_client(self):
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _auth_params(self):
- raise NotImplementedError
+ return
def _get_auth(self):
# Bypasses the cache
@@ -321,7 +331,7 @@
if noversion_path != "":
path += "/" + noversion_path
_base_url = _base_url.replace(parts.path, path)
- if filters.get('skip_path', None) is not None:
+ if filters.get('skip_path', None) is not None and parts.path != '':
_base_url = _base_url.replace(parts.path, "/")
return _base_url
diff --git a/tempest/cli/README.rst b/tempest/cli/README.rst
index dcd940b..bc18084 100644
--- a/tempest/cli/README.rst
+++ b/tempest/cli/README.rst
@@ -1,3 +1,5 @@
+.. _cli_field_guide:
+
Tempest Field Guide to CLI tests
================================
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 0571f4f..02f8c05 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -19,6 +19,7 @@
import tempest.cli.output_parser
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -95,14 +96,15 @@
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 '
@@ -112,7 +114,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):
@@ -120,7 +122,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
@@ -129,10 +131,10 @@
cmd, stdout=stdout, stderr=stderr)
result, result_err = proc.communicate()
if not fail_ok and proc.returncode != 0:
- raise CommandFailed(proc.returncode,
- cmd,
- result,
- stderr=result_err)
+ raise exceptions.CommandFailed(proc.returncode,
+ cmd,
+ result,
+ result_err)
return result
def assertTableStruct(self, items, field_names):
@@ -145,11 +147,3 @@
self.assertTrue(lines[0].startswith(beginning),
msg=('Beginning of first line has invalid content: %s'
% lines[:3]))
-
-
-class CommandFailed(subprocess.CalledProcessError):
- # adds output attribute for python2.6
- def __init__(self, returncode, cmd, output, stderr=""):
- super(CommandFailed, self).__init__(returncode, cmd)
- self.output = output
- self.stderr = stderr
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index e9a0cee..04971c1 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -15,17 +15,17 @@
import logging
import re
-import subprocess
import testtools
-import tempest.cli
+from tempest import cli
from tempest import config
+from tempest import exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyCinderClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyCinderClientTest(cli.ClientTestBase):
"""Basic, read-only tests for Cinder CLI client.
Checks return values and output of read-only commands.
@@ -41,7 +41,7 @@
super(SimpleReadOnlyCinderClientTest, cls).setUpClass()
def test_cinder_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.cinder,
'this-does-not-exist')
@@ -50,16 +50,23 @@
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,
+ self.assertRaises(exceptions.CommandFailed,
self.cinder,
'list',
params='--all-tenants bad')
@@ -85,44 +92,59 @@
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):
- self.cinder('qos-list')
+ qos_list = self.parser.listing(self.cinder('qos-list'))
+ self.assertTableStruct(qos_list, ['ID', 'Name', 'Consumer', 'specs'])
def test_cinder_encryption_type_list(self):
- self.cinder('encryption-type-list')
+ 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')
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index 9869483..90cdc55 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -14,10 +14,10 @@
# under the License.
import re
-import subprocess
-import tempest.cli
+from tempest import cli
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
CONF = config.CONF
@@ -25,7 +25,7 @@
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyGlanceClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyGlanceClientTest(cli.ClientTestBase):
"""Basic, read-only tests for Glance CLI client.
Checks return values and output of read-only commands.
@@ -41,7 +41,7 @@
super(SimpleReadOnlyGlanceClientTest, cls).setUpClass()
def test_glance_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.glance,
'this-does-not-exist')
@@ -76,7 +76,7 @@
commands = set(commands)
wanted_commands = set(('image-create', 'image-delete', 'help',
'image-download', 'image-show', 'image-update',
- 'member-add', 'member-create', 'member-delete',
+ 'member-create', 'member-delete',
'member-list'))
self.assertFalse(wanted_commands - commands)
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index dda65c1..9218fcd 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -14,10 +14,10 @@
# under the License.
import re
-import subprocess
-import tempest.cli
+from tempest import cli
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
CONF = config.CONF
@@ -26,7 +26,7 @@
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyKeystoneClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyKeystoneClientTest(cli.ClientTestBase):
"""Basic, read-only tests for Keystone CLI client.
Checks return values and output of read-only commands.
@@ -35,7 +35,7 @@
"""
def test_admin_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.keystone,
'this-does-not-exist')
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index c1d58b5..87f6b67 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -14,10 +14,10 @@
# under the License.
import re
-import subprocess
from tempest import cli
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest import test
@@ -43,13 +43,14 @@
@test.attr(type='smoke')
def test_neutron_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.neutron,
'this-does-not-exist')
@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):
@@ -88,7 +89,7 @@
def _test_neutron_lbaas_command(self, command):
try:
self.neutron(command)
- except cli.CommandFailed as e:
+ except exceptions.CommandFailed as e:
if '404 Not Found' not in e.stderr:
self.fail('%s: Unexpected failure.' % command)
@@ -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 1c1ddf1..7085cc9 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -13,12 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import subprocess
-
import testtools
-import tempest.cli
+from tempest import cli
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -27,7 +26,7 @@
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyNovaClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyNovaClientTest(cli.ClientTestBase):
"""
This is a first pass at a simple read only python-novaclient test. This
@@ -49,7 +48,7 @@
super(SimpleReadOnlyNovaClientTest, cls).setUpClass()
def test_admin_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.nova,
'this-does-nova-exist')
@@ -86,11 +85,11 @@
self.nova('endpoints')
def test_admin_flavor_acces_list(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.nova,
'flavor-access-list')
# Failed to get access list for public flavor type
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.nova,
'flavor-access-list',
params='--flavor m1.tiny')
@@ -127,7 +126,7 @@
self.nova('list')
self.nova('list', params='--all-tenants 1')
self.nova('list', params='--all-tenants 0')
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.nova,
'list',
params='--all-tenants bad')
diff --git a/tempest/cli/simple_read_only/test_nova_manage.py b/tempest/cli/simple_read_only/test_nova_manage.py
index f1fee2e..dae0cf8 100644
--- a/tempest/cli/simple_read_only/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/test_nova_manage.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import subprocess
-
-import tempest.cli
+from tempest import cli
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -24,7 +23,7 @@
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyNovaManageTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyNovaManageTest(cli.ClientTestBase):
"""
This is a first pass at a simple read only nova-manage test. This
@@ -48,7 +47,7 @@
super(SimpleReadOnlyNovaManageTest, cls).setUpClass()
def test_admin_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.nova_manage,
'this-does-nova-exist')
diff --git a/tempest/cli/simple_read_only/test_sahara.py b/tempest/cli/simple_read_only/test_sahara.py
index 36cc324..2c6e0e2 100644
--- a/tempest/cli/simple_read_only/test_sahara.py
+++ b/tempest/cli/simple_read_only/test_sahara.py
@@ -12,12 +12,12 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
import logging
-import subprocess
+import re
from tempest import cli
from tempest import config
+from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -42,7 +42,7 @@
@test.attr(type='negative')
def test_sahara_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.sahara,
'this-does-not-exist')
@@ -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
index 6d6caa7..069a384 100644
--- a/tempest/cli/simple_read_only/test_swift.py
+++ b/tempest/cli/simple_read_only/test_swift.py
@@ -14,15 +14,15 @@
# under the License.
import re
-import subprocess
-import tempest.cli
+from tempest import cli
from tempest import config
+from tempest import exceptions
CONF = config.CONF
-class SimpleReadOnlySwiftClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlySwiftClientTest(cli.ClientTestBase):
"""Basic, read-only tests for Swift CLI client.
Checks return values and output of read-only commands.
@@ -38,7 +38,7 @@
super(SimpleReadOnlySwiftClientTest, cls).setUpClass()
def test_swift_fake_action(self):
- self.assertRaises(subprocess.CalledProcessError,
+ self.assertRaises(exceptions.CommandFailed,
self.swift,
'this-does-not-exist')
diff --git a/tempest/clients.py b/tempest/clients.py
index 4050a20..0edcdf4 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -50,7 +50,10 @@
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_group_default_rules_client import \
+ SecurityGroupDefaultRulesClientJSON
from tempest.services.compute.json.security_groups_client import \
SecurityGroupsClientJSON
from tempest.services.compute.json.servers_client import ServersClientJSON
@@ -105,6 +108,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
@@ -177,7 +181,15 @@
ExtensionsClientJSON as VolumeExtensionClientJSON
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.v2.json.availability_zone_client import \
+ VolumeV2AvailabilityZoneClientJSON
+from tempest.services.volume.v2.json.extensions_client import \
+ ExtensionsV2ClientJSON as VolumeV2ExtensionClientJSON
from tempest.services.volume.v2.json.volumes_client import VolumesV2ClientJSON
+from tempest.services.volume.v2.xml.availability_zone_client import \
+ VolumeV2AvailabilityZoneClientXML
+from tempest.services.volume.v2.xml.extensions_client import \
+ ExtensionsV2ClientXML as VolumeV2ExtensionClientXML
from tempest.services.volume.v2.xml.volumes_client import VolumesV2ClientXML
from tempest.services.volume.xml.admin.volume_hosts_client import \
VolumeHostsClientXML
@@ -220,6 +232,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(
@@ -264,6 +278,8 @@
self.auth_provider)
self.volumes_extension_client = VolumeExtensionClientXML(
self.auth_provider)
+ self.volumes_v2_extension_client = VolumeV2ExtensionClientXML(
+ self.auth_provider)
if CONF.service_available.ceilometer:
self.telemetry_client = TelemetryClientXML(
self.auth_provider)
@@ -271,6 +287,8 @@
self.token_v3_client = V3TokenClientXML()
self.volume_availability_zone_client = \
VolumeAvailabilityZoneClientXML(self.auth_provider)
+ self.volume_v2_availability_zone_client = \
+ VolumeV2AvailabilityZoneClientXML(self.auth_provider)
elif self.interface == 'json':
self.certificates_client = CertificatesClientJSON(
@@ -288,6 +306,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)
@@ -354,6 +374,8 @@
self.auth_provider)
self.volumes_extension_client = VolumeExtensionClientJSON(
self.auth_provider)
+ self.volumes_v2_extension_client = VolumeV2ExtensionClientJSON(
+ self.auth_provider)
self.hosts_v3_client = HostsV3ClientJSON(self.auth_provider)
self.database_flavors_client = DatabaseFlavorsClientJSON(
self.auth_provider)
@@ -370,6 +392,8 @@
self.negative_client.service = service
self.volume_availability_zone_client = \
VolumeAvailabilityZoneClientJSON(self.auth_provider)
+ self.volume_v2_availability_zone_client = \
+ VolumeV2AvailabilityZoneClientJSON(self.auth_provider)
else:
msg = "Unsupported interface type `%s'" % interface
@@ -400,6 +424,8 @@
self.data_processing_client = DataProcessingClient(
self.auth_provider)
self.migrations_client = MigrationsClientJSON(self.auth_provider)
+ self.security_group_default_rules_client = (
+ SecurityGroupDefaultRulesClientJSON(self.auth_provider))
class AltManager(Manager):
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 1d46028..3616a82 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -28,6 +28,7 @@
import argparse
import tempest.auth
+from tempest import config
from tempest import exceptions
from tempest.services.compute.json import flavors_client
from tempest.services.compute.json import servers_client
@@ -169,7 +170,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'])
@@ -192,7 +193,9 @@
self.check_users()
self.check_objects()
self.check_servers()
- self.check_volumes()
+ # 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.
@@ -200,6 +203,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'])
@@ -215,6 +219,9 @@
def check_objects(self):
"""Check that the objects created are still there."""
+ if not self.res.get('objects'):
+ return
+ LOG.info("checking objects")
for obj in self.res['objects']:
client = client_for_user(obj['owner'])
r, contents = client.objects.get_object(
@@ -224,6 +231,9 @@
def check_servers(self):
"""Check that the servers are still up and running."""
+ if not self.res.get('servers'):
+ return
+ LOG.info("checking servers")
for server in self.res['servers']:
client = client_for_user(server['owner'])
found = _get_server_by_name(client, server['name'])
@@ -234,12 +244,19 @@
r, found = client.servers.get_server(found['id'])
# get the ipv4 address
addr = found['addresses']['private'][0]['addr']
- self.assertEqual(os.system("ping -c 1 " + addr), 0,
- "Server %s is not pingable at %s" % (
- server['name'], addr))
+ for count in range(60):
+ return_code = os.system("ping -c1 " + addr)
+ if return_code is 0:
+ break
+ self.assertNotEqual(count, 59,
+ "Server %s is not pingable at %s" % (
+ server['name'], addr))
def check_volumes(self):
"""Check that the volumes are still there and attached."""
+ if not self.res.get('volumes'):
+ return
+ LOG.info("checking volumes")
for volume in self.res['volumes']:
client = client_for_user(volume['owner'])
found = _get_volume_by_name(client, volume['name'])
@@ -267,6 +284,8 @@
def create_objects(objects):
+ if not objects:
+ return
LOG.info("Creating objects")
for obj in objects:
LOG.debug("Object %s" % obj)
@@ -284,33 +303,66 @@
#######################
+def _resolve_image(image, imgtype):
+ name = image[imgtype]
+ fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
+ return name, fname
+
+
+def _get_image_by_name(client, name):
+ r, body = client.images.image_list()
+ for image in body:
+ if name == image['name']:
+ return image
+ return None
+
+
def create_images(images):
+ if not images:
+ return
+ LOG.info("Creating images")
for image in images:
client = client_for_user(image['owner'])
# only upload a new image if the name isn't there
- r, body = client.images.image_list()
- names = [x['name'] for x in body]
- if image['name'] in names:
+ if _get_image_by_name(client, image['name']):
+ LOG.info("Image '%s' already exists" % image['name'])
continue
# 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'))
+
+
+def destroy_images(images):
+ if not images:
+ return
+ LOG.info("Destroying images")
+ for image in images:
+ client = client_for_user(image['owner'])
+
+ response = _get_image_by_name(client, image['name'])
+ if not response:
+ LOG.info("Image '%s' does not exists" % image['name'])
+ continue
+ client.images.delete_image(response['id'])
#######################
@@ -327,14 +379,6 @@
return None
-def _get_image_by_name(client, name):
- r, body = client.images.image_list()
- for image in body:
- if name == image['name']:
- return image
- return None
-
-
def _get_flavor_by_name(client, name):
r, body = client.flavors.list_flavors()
for flavor in body:
@@ -344,15 +388,39 @@
def create_servers(servers):
+ if not servers:
+ return
+ LOG.info("Creating servers")
for server in servers:
client = client_for_user(server['owner'])
if _get_server_by_name(client, server['name']):
+ LOG.info("Server '%s' already exists" % server['name'])
continue
image_id = _get_image_by_name(client, server['image'])['id']
flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
- client.servers.create_server(server['name'], image_id, flavor_id)
+ resp, body = client.servers.create_server(server['name'], image_id,
+ flavor_id)
+ server_id = body['id']
+ client.servers.wait_for_server_status(server_id, 'ACTIVE')
+
+
+def destroy_servers(servers):
+ if not servers:
+ return
+ LOG.info("Destroying servers")
+ for server in servers:
+ client = client_for_user(server['owner'])
+
+ response = _get_server_by_name(client, server['name'])
+ if not response:
+ LOG.info("Server '%s' does not exist" % server['name'])
+ continue
+
+ client.servers.delete_server(response['id'])
+ client.servers.wait_for_server_termination(response['id'],
+ ignore_error=True)
#######################
@@ -407,8 +475,28 @@
create_objects(RES['objects'])
create_images(RES['images'])
create_servers(RES['servers'])
- create_volumes(RES['volumes'])
- attach_volumes(RES['volumes'])
+ # 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 destroy_resources():
+ LOG.info("Destroying Resources")
+ # Destroy in inverse order of create
+
+ # Future
+ # detach_volumes
+ # destroy_volumes
+
+ destroy_servers(RES['servers'])
+ destroy_images(RES['images'])
+ # destroy_objects
+
+ # destroy_users
+ # destroy_tenants
+
+ LOG.warn("Destroy mode incomplete")
def get_options():
@@ -423,6 +511,17 @@
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')
+ parser.add_argument(
+ '-c', '--config-file',
+ metavar='/etc/tempest.conf',
+ help='path to javelin2(tempest) config file')
+
# auth bits, letting us also just source the devstack openrc
parser.add_argument('--os-username',
metavar='<auth-user-name>',
@@ -442,6 +541,8 @@
print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
parser.print_help()
sys.exit(1)
+ if OPTS.config_file:
+ config.CONF.set_config_path(OPTS.config_file)
def setup_logging(debug=True):
@@ -469,15 +570,20 @@
if OPTS.mode == 'create':
create_resources()
+ # Make sure the resources we just created actually work
+ checker = JavelinCheck(USERS, RES)
+ checker.check()
elif OPTS.mode == 'check':
collect_users(RES['users'])
checker = JavelinCheck(USERS, RES)
checker.check()
elif OPTS.mode == 'destroy':
- LOG.warn("Destroy mode not yet implemented")
+ collect_users(RES['users'])
+ destroy_resources()
else:
LOG.error('Unknown mode %s' % OPTS.mode)
return 1
+ LOG.info('javelin2 successfully finished')
return 0
if __name__ == "__main__":
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
index a1f567b..3450e1f 100644
--- a/tempest/cmd/resources.yaml
+++ b/tempest/cmd/resources.yaml
@@ -27,6 +27,7 @@
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
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 0834cff..673da4f 100755
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -29,13 +29,12 @@
CONF = config.CONF
RAW_HTTP = httplib2.Http()
-CONF_FILE = None
-OUTFILE = sys.stdout
+CONF_PARSER = None
def _get_config_file():
default_config_dir = os.path.join(os.path.abspath(
- os.path.dirname(os.path.dirname(__file__))), "etc")
+ os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "etc")
default_config_file = "tempest.conf"
conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir)
@@ -46,14 +45,9 @@
def change_option(option, group, value):
- config_parse = moves.configparser.SafeConfigParser()
- config_parse.optionxform = str
- config_parse.readfp(CONF_FILE)
- if not config_parse.has_section(group):
- config_parse.add_section(group)
- config_parse.set(group, option, str(value))
- global OUTFILE
- config_parse.write(OUTFILE)
+ if not CONF_PARSER.has_section(group):
+ CONF_PARSER.add_section(group)
+ CONF_PARSER.set(group, option, str(value))
def print_and_or_update(option, group, value, update):
@@ -288,6 +282,9 @@
if update:
change_option(codename_match[cfgname],
'service_available', True)
+ # If we are going to enable this we should allow
+ # extension checks.
+ avail_services.append(codename_match[cfgname])
else:
avail_services.append(codename_match[cfgname])
return avail_services
@@ -321,12 +318,16 @@
opts = parse_args()
update = opts.update
replace = opts.replace_ext
- global CONF_FILE
- global OUTFILE
+ global CONF_PARSER
+
+ outfile = sys.stdout
if update:
- CONF_FILE = _get_config_file()
+ conf_file = _get_config_file()
if opts.output:
- OUTFILE = open(opts.output, 'w+')
+ outfile = open(opts.output, 'w+')
+ CONF_PARSER = moves.configparser.SafeConfigParser()
+ CONF_PARSER.optionxform = str
+ CONF_PARSER.readfp(conf_file)
os = clients.ComputeAdminManager(interface='json')
services = check_service_availability(os, update)
results = {}
@@ -341,9 +342,10 @@
verify_nova_api_versions(os, update)
verify_cinder_api_versions(os, update)
display_results(results, update, replace)
- if CONF_FILE:
- CONF_FILE.close()
- OUTFILE.close()
+ if update:
+ conf_file.close()
+ CONF_PARSER.write(outfile)
+ outfile.close()
if __name__ == "__main__":
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
index 6720847..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():
@@ -84,7 +84,7 @@
"-i %(pkey)s %(file1)s %(dest)s" % {'pkey': pkey,
'file1': file_from,
'dest': dest}
- args = shlex.split(cmd)
+ args = shlex.split(cmd.encode('utf-8'))
subprocess_args = {'stdout': subprocess.PIPE,
'stderr': subprocess.STDOUT}
proc = subprocess.Popen(args, **subprocess_args)
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
new file mode 100644
index 0000000..dc4f049
--- /dev/null
+++ b/tempest/common/cred_provider.py
@@ -0,0 +1,44 @@
+# (c) 2014 Deutsche Telekom AG
+# 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 abc
+import six
+
+from tempest import config
+from tempest.openstack.common import log as logging
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class CredentialProvider(object):
+ def __init__(self, name, tempest_client=True, interface='json',
+ password='pass', network_resources=None):
+ self.name = name
+
+ @abc.abstractmethod
+ def get_primary_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_admin_creds(self):
+ return
+
+ @abc.abstractmethod
+ def get_alt_creds(self):
+ return
+
+ @abc.abstractmethod
+ def clear_isolated_creds(self):
+ return
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index 4a7921f..996c365 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -69,10 +69,24 @@
elif self.target == 'Object':
if 'etag' not in actual:
return NonExistentHeader('etag')
- elif self.method == 'PUT' or self.method == 'COPY':
+ if 'last-modified' not in actual:
+ return NonExistentHeader('last-modified')
+ elif self.method == 'PUT':
if self.target == 'Object':
if 'etag' not in actual:
return NonExistentHeader('etag')
+ if 'last-modified' not in actual:
+ return NonExistentHeader('last-modified')
+ elif self.method == 'COPY':
+ if self.target == 'Object':
+ if 'etag' not in actual:
+ return NonExistentHeader('etag')
+ if 'last-modified' not in actual:
+ return NonExistentHeader('last-modified')
+ if 'x-copied-from' not in actual:
+ return NonExistentHeader('x-copied-from')
+ if 'x-copied-from-last-modified' not in actual:
+ return NonExistentHeader('x-copied-from-last-modified')
return None
@@ -122,11 +136,17 @@
return InvalidFormat(key, value)
elif key == 'content-type' and not value:
return InvalidFormat(key, value)
+ elif key == 'x-copied-from' and not re.match("\S+/\S+", value):
+ return InvalidFormat(key, value)
+ elif key == 'x-copied-from-last-modified' and not value:
+ return InvalidFormat(key, value)
elif key == 'x-trans-id' and \
not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value):
return InvalidFormat(key, value)
elif key == 'date' and not value:
return InvalidFormat(key, value)
+ elif key == 'last-modified' and not value:
+ return InvalidFormat(key, value)
elif key == 'accept-ranges' and not value == 'bytes':
return InvalidFormat(key, value)
elif key == 'etag' and not value.isalnum():
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index 8166de5..98b0116 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -16,6 +16,7 @@
from tempest import auth
from tempest import clients
+from tempest.common import cred_provider
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
@@ -25,15 +26,16 @@
LOG = logging.getLogger(__name__)
-class IsolatedCreds(object):
+class IsolatedCreds(cred_provider.CredentialProvider):
def __init__(self, name, tempest_client=True, interface='json',
password='pass', network_resources=None):
+ super(IsolatedCreds, self).__init__(name, tempest_client, interface,
+ password, network_resources)
self.network_resources = network_resources
self.isolated_creds = {}
self.isolated_net_resources = {}
self.ports = []
- self.name = name
self.tempest_client = tempest_client
self.interface = interface
self.password = password
@@ -57,7 +59,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 +69,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 +82,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 +94,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,7 +103,7 @@
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
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index a0a88dd..5a29ea0 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -34,6 +34,11 @@
return randbits
+def rand_url():
+ randbits = str(random.randint(1, 0x7fffffff))
+ return 'https://url-' + randbits + '.com'
+
+
def rand_int_id(start=0, end=0x7fffffff):
return random.randint(start, end)
@@ -66,3 +71,11 @@
if not base_text:
base_text = 'test'
return ''.join(itertools.islice(itertools.cycle(base_text), size))
+
+
+def random_bytes(size=1024):
+ """
+ Return size randomly selected bytes as a string.
+ """
+ return ''.join([chr(random.randint(0, 255))
+ for i in range(size)])
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d8474a0..d242c14 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -22,16 +22,6 @@
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):
@@ -81,10 +71,12 @@
'/'.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)
+ if 'fault' in body:
+ raise exceptions.BuildErrorException(body['fault'],
+ server_id=server_id)
+ else:
+ raise exceptions.BuildErrorException(server_id=server_id)
timed_out = int(time.time()) - start_time >= timeout
@@ -99,11 +91,9 @@
'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 6475844..db54269 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -268,12 +268,14 @@
default=['all'],
help='A list of enabled compute extensions with a special '
'entry all which indicates every extension is enabled. '
- 'Each extension should be specified with alias name'),
+ 'Each extension should be specified with alias name. '
+ 'Empty list indicates all extensions are disabled'),
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. '
- 'Each extension should be specified with alias name'),
+ 'Each extension should be specified with alias name. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('change_password',
default=False,
help="Does the test environment support changing the admin "
@@ -284,6 +286,9 @@
cfg.BoolOpt('pause',
default=True,
help="Does the test environment support pausing?"),
+ cfg.BoolOpt('shelve',
+ default=True,
+ help="Does the test environment support shelving/unshelving?"),
cfg.BoolOpt('suspend',
default=True,
help="Does the test environment support suspend/resume?"),
@@ -314,7 +319,12 @@
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '
- 'mode?')
+ 'mode?'),
+ cfg.BoolOpt('enable_instance_password',
+ default=True,
+ help='Enables returning of the instance password by the '
+ 'relevant server API calls such as create, rebuild '
+ 'or rescue.')
]
@@ -441,7 +451,8 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled network extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('ipv6_subnet_attributes',
default=False,
help="Allow the execution of IPv6 subnet tests that use "
@@ -546,7 +557,8 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled volume extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('api_v1',
default=True,
help="Is the v1 volume API enabled"),
@@ -942,6 +954,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',
@@ -1082,18 +1097,22 @@
cfg.CONF.set_default('domain_name', self.identity.admin_domain_name,
group='compute-admin')
- def __init__(self, parse_conf=True):
+ def __init__(self, parse_conf=True, config_path=None):
"""Initialize a configuration from a conf directory and conf file."""
super(TempestConfigPrivate, self).__init__()
config_files = []
failsafe_path = "/etc/tempest/" + self.DEFAULT_CONFIG_FILE
- # Environment variables override defaults...
- conf_dir = os.environ.get('TEMPEST_CONFIG_DIR',
- self.DEFAULT_CONFIG_DIR)
- conf_file = os.environ.get('TEMPEST_CONFIG', self.DEFAULT_CONFIG_FILE)
+ if config_path:
+ path = config_path
+ else:
+ # Environment variables override defaults...
+ conf_dir = os.environ.get('TEMPEST_CONFIG_DIR',
+ self.DEFAULT_CONFIG_DIR)
+ conf_file = os.environ.get('TEMPEST_CONFIG',
+ self.DEFAULT_CONFIG_FILE)
- path = os.path.join(conf_dir, conf_file)
+ path = os.path.join(conf_dir, conf_file)
if not os.path.isfile(path):
path = failsafe_path
@@ -1115,6 +1134,7 @@
class TempestConfigProxy(object):
_config = None
+ _path = None
_extra_log_defaults = [
'keystoneclient.session=INFO',
@@ -1131,9 +1151,12 @@
def __getattr__(self, attr):
if not self._config:
self._fix_log_levels()
- self._config = TempestConfigPrivate()
+ self._config = TempestConfigPrivate(config_path=self._path)
return getattr(self._config, attr)
+ def set_config_path(self, path):
+ self._path = path
+
CONF = TempestConfigProxy()
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 4eb1cea..9d443cc 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -211,3 +211,17 @@
class InvalidStructure(TempestException):
message = "Invalid structure of table with details"
+
+
+class CommandFailed(Exception):
+ def __init__(self, returncode, cmd, output, stderr):
+ super(CommandFailed, self).__init__()
+ self.returncode = returncode
+ self.cmd = cmd
+ self.stdout = output
+ self.stderr = stderr
+
+ def __str__(self):
+ return ("Command '%s' returned non-zero exit status %d.\n"
+ "stdout:\n%s\n"
+ "stderr:\n%s" % (self.cmd, self.returncode, self.stdout, self.stderr))
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 4e35aaa..93329bc 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -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/gettextutils.py b/tempest/openstack/common/gettextutils.py
index 6102e67..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
@@ -42,7 +41,7 @@
"""Create translator functions
"""
- def __init__(self, domain, lazy=False, localedir=None):
+ def __init__(self, domain, localedir=None):
"""Establish a set of translation functions for the domain.
:param domain: Name of translation domain,
@@ -55,7 +54,6 @@
:type localedir: str
"""
self.domain = domain
- self.lazy = lazy
if localedir is None:
localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
self.localedir = localedir
@@ -75,16 +73,19 @@
"""
if domain is None:
domain = self.domain
- if self.lazy:
- return functools.partial(Message, domain=domain)
- t = gettext.translation(
- domain,
- localedir=self.localedir,
- fallback=True,
- )
- if six.PY3:
- return t.gettext
- return t.ugettext
+ 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):
@@ -147,19 +148,11 @@
your project is importing _ directly instead of using the
gettextutils.install() way of importing the _ function.
"""
- # FIXME(dhellmann): This function will be removed in oslo.i18n,
- # because the TranslatorFactory makes it superfluous.
- global _, _LI, _LW, _LE, _LC, USE_LAZY
- tf = TranslatorFactory('tempest', lazy=True)
- _ = tf.primary
- _LI = tf.log_info
- _LW = tf.log_warning
- _LE = tf.log_error
- _LC = tf.log_critical
+ global USE_LAZY
USE_LAZY = True
-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
@@ -170,26 +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:
- from six import moves
- tf = TranslatorFactory(domain, lazy=True)
- moves.builtins.__dict__['_'] = tf.primary
- 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):
@@ -373,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/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/scenario/README.rst b/tempest/scenario/README.rst
index 835ba99..5a287d6 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -1,3 +1,5 @@
+.. _scenario_field_guide:
+
Tempest Field Guide to Scenario tests
=====================================
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index e6593db..3cfc698 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -22,6 +22,7 @@
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
@@ -52,6 +53,32 @@
LOG_cinder_client.addHandler(log.NullHandler())
+class ScenarioTest(tempest.test.BaseTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super(ScenarioTest, cls).setUpClass()
+ cls.isolated_creds = isolated_creds.IsolatedCreds(
+ cls.__name__, tempest_client=True,
+ network_resources=cls.network_resources)
+ cls.manager = clients.Manager(
+ credentials=cls.credentials()
+ )
+
+ @classmethod
+ def _get_credentials(cls, get_creds, ctype):
+ if CONF.compute.allow_tenant_isolation:
+ creds = get_creds()
+ else:
+ creds = auth.get_default_credentials(ctype)
+ return creds
+
+ @classmethod
+ def credentials(cls):
+ return cls._get_credentials(cls.isolated_creds.get_primary_creds,
+ 'user')
+
+
class OfficialClientTest(tempest.test.BaseTestCase):
"""
Official Client test base class for scenario testing.
@@ -84,8 +111,11 @@
cls.orchestration_client = cls.manager.orchestration_client
cls.data_processing_client = cls.manager.data_processing_client
cls.ceilometer_client = cls.manager.ceilometer_client
- cls.resource_keys = {}
- cls.os_resources = []
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.isolated_creds.clear_isolated_creds()
+ super(OfficialClientTest, cls).tearDownClass()
@classmethod
def _get_credentials(cls, get_creds, ctype):
@@ -110,72 +140,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',
@@ -227,8 +270,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
@@ -288,6 +334,7 @@
for ruleset in rulesets:
sg_rule = client.security_group_rules.create(secgroup_id,
**ruleset)
+ self.addCleanup(self.delete_wrapper, sg_rule)
rules.append(sg_rule)
return rules
@@ -301,7 +348,7 @@
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)
+ self.addCleanup(self.delete_wrapper, secgroup)
# Add rules to the security group
self._create_loginable_secgroup_rule_nova(client, secgroup.id)
@@ -309,7 +356,17 @@
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:
@@ -343,19 +400,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, volume_type=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:
@@ -365,7 +428,12 @@
snapshot_id=snapshot_id,
imageRef=imageRef,
volume_type=volume_type)
- self.set_resource(name, volume)
+ 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)
@@ -381,7 +449,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)
@@ -396,7 +465,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):
@@ -590,9 +659,12 @@
'key_name': self.keypair.id
}
self.instance = self.create_server(
- wait=False, create_kwargs=create_kwargs)
+ wait_on_boot=False, create_kwargs=create_kwargs)
- self.set_resource('instance', self.instance)
+ 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)
@@ -617,7 +689,6 @@
def terminate_instance(self):
self.instance.delete()
- self.remove_resource('instance')
self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
self.wait_provisioning_state(
self.node.uuid,
@@ -643,11 +714,6 @@
self.status_timeout(
self.volume_client.volumes, self.volume.id, status)
- def _wait_for_volume_deletion(self):
- self.delete_timeout(
- self.volume_client.volumes, self.volume.id,
- not_found_exception=cinder_exceptions.NotFound)
-
def nova_boot(self):
self.keypair = self.create_keypair()
create_kwargs = {'key_name': self.keypair.name}
@@ -698,10 +764,6 @@
volume = self.volume_client.volumes.get(self.volume.id)
self.assertEqual('available', volume.status)
- def cinder_delete_encrypted(self):
- self.volume_client.volumes.delete(self.volume.id)
- self._wait_for_volume_deletion()
-
class NetworkScenarioTest(OfficialClientTest):
"""
@@ -740,7 +802,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):
@@ -816,7 +878,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-'):
@@ -829,7 +891,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):
@@ -852,7 +914,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):
@@ -897,7 +959,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):
@@ -912,7 +974,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):
@@ -931,7 +993,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,
@@ -1073,7 +1135,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):
@@ -1132,6 +1194,7 @@
client=client,
**sg_rule['security_group_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)
@@ -1230,7 +1293,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_dashboard_basic_ops.py b/tempest/scenario/test_dashboard_basic_ops.py
index 6418a73..4fcc70a 100644
--- a/tempest/scenario/test_dashboard_basic_ops.py
+++ b/tempest/scenario/test_dashboard_basic_ops.py
@@ -24,7 +24,7 @@
CONF = config.CONF
-class TestDashboardBasicOps(manager.OfficialClientTest):
+class TestDashboardBasicOps(manager.ScenarioTest):
"""
This is a basic scenario test:
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index f223cbf..366cd93 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -29,7 +29,6 @@
* 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
- * Deletes the encrypted volume
"""
def launch_instance(self):
@@ -49,21 +48,16 @@
self.nova_volume_attach()
self.nova_volume_detach()
- def delete_volume(self):
- self.cinder_delete_encrypted()
-
@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()
- self.delete_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()
- self.delete_volume()
+ 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 826da48..8191984 100644
--- a/tempest/scenario/test_load_balancer_basic.py
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -74,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 = {
@@ -99,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 = {
@@ -111,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:
@@ -148,7 +141,7 @@
server_or_ip=ip,
private_key=private_key)
- # Write a backend's responce into a file
+ # 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"""
@@ -211,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):
@@ -227,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)
@@ -246,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)
@@ -257,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),
@@ -271,6 +258,13 @@
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 10 requests on the floating ip associated with the VIP
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 0406217..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)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index f1cd320..431de9a 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -50,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 = {
@@ -79,10 +71,8 @@
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)
def _check_network_connectivity(self, should_connect=True):
username = CONF.compute.image_ssh_user
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index c84d4b9..7dc817d 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -96,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')
@@ -144,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': [
@@ -154,7 +147,6 @@
'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):
@@ -171,7 +163,6 @@
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):
@@ -204,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
@@ -226,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 dd89dc0..8058b3d 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -165,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):
@@ -173,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',
@@ -188,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
@@ -238,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):
@@ -269,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):
@@ -355,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,
@@ -385,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)
@@ -399,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 54f1d9e..38686d9 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -74,23 +74,17 @@
'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:
self.get_remote_client(
@@ -108,4 +102,4 @@
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 d41490a..7dd662d 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -65,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):
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 20561ae..be27024 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -74,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):
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 4905dbf..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
@@ -69,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')
@@ -100,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:
diff --git a/tempest/services/__init__.py b/tempest/services/__init__.py
index e7bec60..e69de29 100644
--- a/tempest/services/__init__.py
+++ b/tempest/services/__init__.py
@@ -1,37 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""
-Base Service class, which acts as a descriptor for an OpenStack service
-in the test environment
-"""
-
-
-class Service(object):
-
- def __init__(self, config):
- """
- Initializes the service.
-
- :param config: `tempest.config.Config` object
- """
- self.config = config
-
- def get_client(self):
- """
- Returns a client object that may be used to query
- the service API.
- """
- raise NotImplementedError
diff --git a/tempest/services/baremetal/v1/base_v1.py b/tempest/services/baremetal/v1/base_v1.py
index 61342eb..9c753c2 100644
--- a/tempest/services/baremetal/v1/base_v1.py
+++ b/tempest/services/baremetal/v1/base_v1.py
@@ -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):
"""
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/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 0028eea..cd195f4 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -130,6 +130,7 @@
"""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):
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/security_group_default_rules_client.py b/tempest/services/compute/json/security_group_default_rules_client.py
new file mode 100644
index 0000000..6d29837
--- /dev/null
+++ b/tempest/services/compute/json/security_group_default_rules_client.py
@@ -0,0 +1,74 @@
+# 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.
+
+import json
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class SecurityGroupDefaultRulesClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(SecurityGroupDefaultRulesClientJSON,
+ self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def create_security_default_group_rule(self, ip_protocol, from_port,
+ to_port, **kwargs):
+ """
+ Creating security group default rules.
+ ip_protocol : ip_protocol (icmp, tcp, udp).
+ from_port: Port at start of range.
+ to_port : Port at end of range.
+ cidr : CIDR for address range.
+ """
+ post_body = {
+ 'ip_protocol': ip_protocol,
+ 'from_port': from_port,
+ 'to_port': to_port,
+ 'cidr': kwargs.get('cidr'),
+ }
+ post_body = json.dumps({'security_group_default_rule': post_body})
+ url = 'os-security-group-default-rules'
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rule']
+
+ def delete_security_group_default_rule(self,
+ security_group_default_rule_id):
+ """Deletes the provided Security Group default rule."""
+ resp, body = self.delete('os-security-group-default-rules/%s' % str(
+ security_group_default_rule_id))
+ self.expected_success(204, resp.status)
+ return resp, body
+
+ def list_security_group_default_rules(self):
+ """List all Security Group default rules."""
+ resp, body = self.get('os-security-group-default-rules')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rules']
+
+ def get_security_group_default_rule(self, security_group_default_rule_id):
+ """Return the details of provided Security Group default rule."""
+ resp, body = self.get('os-security-group-default-rules/%s' % str(
+ security_group_default_rule_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rule']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index e1661c0..a4e3641 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -93,7 +93,11 @@
# with return reservation id set True
if 'reservation_id' in body:
return resp, body
- self.validate_response(schema.create_server, resp, body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ create_schema = schema.create_server_with_admin_pass
+ else:
+ create_schema = schema.create_server
+ self.validate_response(create_schema, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
@@ -134,6 +138,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):
@@ -163,6 +168,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,
@@ -267,7 +273,12 @@
if 'disk_config' in kwargs:
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', None, **kwargs)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild', 'server',
+ rebuild_schema, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -448,7 +459,8 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'adminPass', None, **kwargs)
+ return self.action(server_id, 'rescue', 'adminPass',
+ schema.rescue_server, **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
@@ -472,6 +484,7 @@
resp, body = self.get("servers/%s/os-instance-actions/%s" %
(str(server_id), str(request_id)))
body = json.loads(body)
+ self.validate_response(schema.get_instance_action, resp, body)
return resp, body['instanceAction']
def force_delete_server(self, server_id, **kwargs):
@@ -524,6 +537,7 @@
"""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):
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 11258a6..c3fd355 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -96,7 +96,11 @@
# with return reservation id set True
if 'servers_reservation' in body:
return resp, body['servers_reservation']
- self.validate_response(schema.create_server, resp, body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ create_schema = schema.create_server_with_admin_pass
+ else:
+ create_schema = schema.create_server
+ self.validate_response(create_schema, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
@@ -136,6 +140,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):
@@ -165,6 +170,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,
@@ -270,7 +276,12 @@
if 'disk_config' in kwargs:
kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', None, **kwargs)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild', 'server',
+ rebuild_schema, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -443,8 +454,16 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'admin_password',
- None, **kwargs)
+ post_body = json.dumps({'rescue': kwargs})
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rescue_schema = schema.rescue_server_with_admin_pass
+ else:
+ rescue_schema = schema.rescue_server
+ body = json.loads(body)
+ self.validate_response(rescue_schema, resp, body)
+ return resp, body
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
@@ -469,6 +488,7 @@
resp, body = self.get("servers/%s/os-server-actions/%s" %
(str(server_id), str(request_id)))
body = json.loads(body)
+ self.validate_response(schema.get_server_action, resp, body)
return resp, body['server_action']
def force_delete_server(self, server_id, **kwargs):
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/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index c2c7fd1..7acbae7 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -25,29 +25,42 @@
super(DataProcessingClient, self).__init__(auth_provider)
self.service = CONF.data_processing.catalog_type
- @classmethod
- def _request_and_parse(cls, req_fun, uri, res_name, *args, **kwargs):
- """Make a request using specified req_fun and parse response.
+ def _request_and_check_resp(self, request_func, uri, resp_status):
+ """Make a request using specified request_func and check response
+ status code.
+
+ It returns pair: resp and response body.
+ """
+ resp, body = request_func(uri)
+ self.expected_success(resp_status, resp.status)
+ return resp, body
+
+ def _request_check_and_parse_resp(self, request_func, uri, resp_status,
+ resource_name, *args, **kwargs):
+ """Make a request using specified request_func, check response status
+ code and parse response body.
It returns pair: resp and parsed resource(s) body.
"""
- resp, body = req_fun(uri, headers={
- 'Content-Type': 'application/json'
- }, *args, **kwargs)
+ headers = {'Content-Type': 'application/json'}
+ resp, body = request_func(uri, headers=headers, *args, **kwargs)
+ self.expected_success(resp_status, resp.status)
body = json.loads(body)
- return resp, body[res_name]
+ return resp, body[resource_name]
def list_node_group_templates(self):
"""List all node group templates for a user."""
uri = 'node-group-templates'
- return self._request_and_parse(self.get, uri, 'node_group_templates')
+ return self._request_check_and_parse_resp(self.get, uri,
+ 200, 'node_group_templates')
def get_node_group_template(self, tmpl_id):
"""Returns the details of a single node group template."""
uri = 'node-group-templates/%s' % tmpl_id
- return self._request_and_parse(self.get, uri, 'node_group_template')
+ return self._request_check_and_parse_resp(self.get, uri,
+ 200, 'node_group_template')
def create_node_group_template(self, name, plugin_name, hadoop_version,
node_processes, flavor_id,
@@ -67,20 +80,22 @@
'flavor_id': flavor_id,
'node_configs': node_configs or dict(),
})
- return self._request_and_parse(self.post, uri, 'node_group_template',
- body=json.dumps(body))
+ return self._request_check_and_parse_resp(self.post, uri, 202,
+ 'node_group_template',
+ body=json.dumps(body))
def delete_node_group_template(self, tmpl_id):
"""Deletes the specified node group template by id."""
uri = 'node-group-templates/%s' % tmpl_id
- return self.delete(uri)
+ return self._request_and_check_resp(self.delete, uri, 204)
def list_plugins(self):
"""List all enabled plugins."""
uri = 'plugins'
- return self._request_and_parse(self.get, uri, 'plugins')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, 'plugins')
def get_plugin(self, plugin_name, plugin_version=None):
"""Returns the details of a single plugin."""
@@ -88,19 +103,21 @@
uri = 'plugins/%s' % plugin_name
if plugin_version:
uri += '/%s' % plugin_version
- return self._request_and_parse(self.get, uri, 'plugin')
+ return self._request_check_and_parse_resp(self.get, uri, 200, 'plugin')
def list_cluster_templates(self):
"""List all cluster templates for a user."""
uri = 'cluster-templates'
- return self._request_and_parse(self.get, uri, 'cluster_templates')
+ return self._request_check_and_parse_resp(self.get, uri,
+ 200, 'cluster_templates')
def get_cluster_template(self, tmpl_id):
"""Returns the details of a single cluster template."""
uri = 'cluster-templates/%s' % tmpl_id
- return self._request_and_parse(self.get, uri, 'cluster_template')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, 'cluster_template')
def create_cluster_template(self, name, plugin_name, hadoop_version,
node_groups, cluster_configs=None,
@@ -119,26 +136,29 @@
'node_groups': node_groups,
'cluster_configs': cluster_configs or dict(),
})
- return self._request_and_parse(self.post, uri, 'cluster_template',
- body=json.dumps(body))
+ return self._request_check_and_parse_resp(self.post, uri, 202,
+ 'cluster_template',
+ body=json.dumps(body))
def delete_cluster_template(self, tmpl_id):
"""Deletes the specified cluster template by id."""
uri = 'cluster-templates/%s' % tmpl_id
- return self.delete(uri)
+ return self._request_and_check_resp(self.delete, uri, 204)
def list_data_sources(self):
"""List all data sources for a user."""
uri = 'data-sources'
- return self._request_and_parse(self.get, uri, 'data_sources')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, 'data_sources')
def get_data_source(self, source_id):
"""Returns the details of a single data source."""
uri = 'data-sources/%s' % source_id
- return self._request_and_parse(self.get, uri, 'data_source')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, 'data_source')
def create_data_source(self, name, data_source_type, url, **kwargs):
"""Creates data source with specified params.
@@ -153,57 +173,62 @@
'type': data_source_type,
'url': url
})
- return self._request_and_parse(self.post, uri, 'data_source',
- body=json.dumps(body))
+ return self._request_check_and_parse_resp(self.post, uri,
+ 202, 'data_source',
+ body=json.dumps(body))
def delete_data_source(self, source_id):
"""Deletes the specified data source by id."""
uri = 'data-sources/%s' % source_id
- return self.delete(uri)
+ return self._request_and_check_resp(self.delete, uri, 204)
def list_job_binary_internals(self):
"""List all job binary internals for a user."""
uri = 'job-binary-internals'
- return self._request_and_parse(self.get, uri, 'binaries')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, 'binaries')
def get_job_binary_internal(self, job_binary_id):
"""Returns the details of a single job binary internal."""
uri = 'job-binary-internals/%s' % job_binary_id
- return self._request_and_parse(self.get, uri, 'job_binary_internal')
+ return self._request_check_and_parse_resp(self.get, uri,
+ 200, 'job_binary_internal')
def create_job_binary_internal(self, name, data):
"""Creates job binary internal with specified params."""
uri = 'job-binary-internals/%s' % name
- return self._request_and_parse(self.put, uri, 'job_binary_internal',
- data)
+ return self._request_check_and_parse_resp(self.put, uri, 202,
+ 'job_binary_internal', data)
def delete_job_binary_internal(self, job_binary_id):
"""Deletes the specified job binary internal by id."""
uri = 'job-binary-internals/%s' % job_binary_id
- return self.delete(uri)
+ return self._request_and_check_resp(self.delete, uri, 204)
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)
+ return self._request_and_check_resp(self.get, uri, 200)
def list_job_binaries(self):
"""List all job binaries for a user."""
uri = 'job-binaries'
- return self._request_and_parse(self.get, uri, 'binaries')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, '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')
+ return self._request_check_and_parse_resp(self.get,
+ uri, 200, 'job_binary')
def create_job_binary(self, name, url, extra=None, **kwargs):
"""Creates job binary with specified params.
@@ -218,17 +243,53 @@
'url': url,
'extra': extra or dict(),
})
- return self._request_and_parse(self.post, uri, 'job_binary',
- body=json.dumps(body))
+ return self._request_check_and_parse_resp(self.post, uri,
+ 202, '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)
+ return self._request_and_check_resp(self.delete, uri, 204)
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)
+ return self._request_and_check_resp(self.get, uri, 200)
+
+ def list_jobs(self):
+ """List all jobs for a user."""
+
+ uri = 'jobs'
+ return self._request_check_and_parse_resp(self.get, uri, 200, 'jobs')
+
+ def get_job(self, job_id):
+ """Returns the details of a single job."""
+
+ uri = 'jobs/%s' % job_id
+ return self._request_check_and_parse_resp(self.get, uri, 200, 'job')
+
+ def create_job(self, name, job_type, mains, libs=None, **kwargs):
+ """Creates job with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object.
+ """
+ uri = 'jobs'
+ body = kwargs.copy()
+ body.update({
+ 'name': name,
+ 'type': job_type,
+ 'mains': mains,
+ 'libs': libs or list(),
+ })
+ return self._request_check_and_parse_resp(self.post, uri, 202,
+ 'job', body=json.dumps(body))
+
+ def delete_job(self, job_id):
+ """Deletes the specified job by id."""
+
+ uri = 'jobs/%s' % job_id
+ return self._request_and_check_resp(self.delete, uri, 204)
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index b0cab8e..ac65f81 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -37,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):
@@ -48,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']
@@ -70,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
@@ -122,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'])
@@ -134,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):
@@ -149,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):
@@ -178,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
@@ -210,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."""
@@ -236,11 +271,13 @@
}
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']
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 6829333..d57b931 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -14,6 +14,7 @@
# under the License.
import json
+import urllib
from tempest.common import rest_client
from tempest import config
@@ -49,12 +50,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 +73,49 @@
}
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 update_user_password(self, user_id, password, original_password):
+ """Updates a user password."""
+ update_user = {
+ 'password': password,
+ 'original_password': original_password
+ }
+ update_user = json.dumps({'user': update_user})
+ resp, _ = self.post('users/%s/password' % user_id, update_user)
+ self.expected_success(204, resp.status)
+ return resp
+
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):
+ def get_users(self, params=None):
"""Get the list of users."""
- resp, body = self.get("users")
+ url = 'users'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ 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 +131,21 @@
}
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")
+ def list_projects(self, params=None):
+ url = "projects"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ 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 +159,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 +183,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 +208,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 +236,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 +266,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 +281,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 +289,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 +305,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 +327,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 +390,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 +398,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 +434,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 +442,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 +466,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 +486,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 +508,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 +516,7 @@
"""HEAD Check if role is delegated by a trust."""
resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
% (trust_id, role_id))
+ self.expected_success(200, 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
index f95d00f..c078765 100644
--- a/tempest/services/identity/v3/json/region_client.py
+++ b/tempest/services/identity/v3/json/region_client.py
@@ -1,4 +1,4 @@
-# Copyright 2014 OpenStack Foundation
+# Copyright 2014 Hewlett-Packard Development Company, L.P
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
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..c2bd77e 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -14,6 +14,7 @@
# under the License.
import json
+import urllib
from lxml import etree
@@ -76,6 +77,14 @@
array.append(common.xml_to_json(child))
return array
+ def _parse_users(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "user":
+ array.append(common.xml_to_json(child))
+ return array
+
def _parse_array(self, node):
array = []
for child in node.getchildren():
@@ -104,12 +113,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 +135,49 @@
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 update_user_password(self, user_id, password, original_password):
+ """Updates a user password."""
+ update_user = common.Element("user",
+ xmlns=XMLNS,
+ password=password,
+ original_password=original_password)
+ resp, _ = self.post('users/%s/password' % user_id,
+ str(common.Document(update_user)))
+ self.expected_success(204, resp.status)
+ return resp
+
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):
+ def get_users(self, params=None):
"""Get the list of users."""
- resp, body = self.get("users")
- body = self._parse_array(etree.fromstring(body))
+ url = 'users'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = self._parse_users(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 +193,17 @@
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):
+ def list_projects(self, params=None):
"""Get the list of projects."""
- resp, body = self.get("projects")
+ url = 'projects'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = self._parse_projects(etree.fromstring(body))
return resp, body
@@ -188,18 +222,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 +245,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 +270,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 +297,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 +327,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 +344,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 +352,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 +367,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 +389,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 +451,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 +459,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 +495,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 +503,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
index 9f9161d..f854138 100644
--- a/tempest/services/identity/v3/xml/region_client.py
+++ b/tempest/services/identity/v3/xml/region_client.py
@@ -1,4 +1,4 @@
-# Copyright 2014 OpenStack Foundation
+# Copyright 2014 Hewlett-Packard Development Company, L.P
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
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 886ce7b..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,11 +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/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 81792c4..4ee8302 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -28,6 +28,7 @@
'vips': 'lb',
'health_monitors': 'lb',
'members': 'lb',
+ 'ipsecpolicies': 'vpn',
'vpnservices': 'vpn',
'ikepolicies': 'vpn',
'ipsecpolicies': 'vpn',
@@ -47,6 +48,7 @@
resource_plural_map = {
'security_groups': 'security_groups',
'security_group_rules': 'security_group_rules',
+ 'ipsecpolicy': 'ipsecpolicies',
'ikepolicy': 'ikepolicies',
'ipsecpolicy': 'ipsecpolicies',
'quotas': 'quotas',
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index c459f28..46b0ec4 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -181,7 +181,11 @@
fail_regexp = re.compile(failure_pattern)
while True:
- resp, body = self.get_stack(stack_identifier)
+ try:
+ resp, body = self.get_stack(stack_identifier)
+ except exceptions.NotFound:
+ if status == 'DELETE_COMPLETE':
+ return
stack_name = body['stack_name']
stack_status = body['stack_status']
if stack_status == status:
@@ -259,3 +263,140 @@
'parameters': parameters,
}
return self._validate_template(post_body)
+
+ def create_software_config(self, name=None, config=None, group=None,
+ inputs=None, outputs=None, options=None):
+ headers, body = self._prep_software_config_create(
+ name, config, group, inputs, outputs, options)
+
+ url = 'software_configs'
+ resp, body = self.post(url, headers=headers, body=body)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def get_software_config(self, conf_id):
+ """Returns a software configuration resource."""
+ url = 'software_configs/%s' % str(conf_id)
+ resp, body = self.get(url)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def delete_software_config(self, conf_id):
+ """Deletes a specific software configuration."""
+ url = 'software_configs/%s' % str(conf_id)
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp)
+
+ def create_software_deploy(self, server_id=None, config_id=None,
+ action=None, status=None,
+ input_values=None, output_values=None,
+ status_reason=None, signal_transport=None):
+ """Creates or updates a software deployment."""
+ headers, body = self._prep_software_deploy_update(
+ None, server_id, config_id, action, status, input_values,
+ output_values, status_reason, signal_transport)
+
+ url = 'software_deployments'
+ resp, body = self.post(url, headers=headers, body=body)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def update_software_deploy(self, deploy_id=None, server_id=None,
+ config_id=None, action=None, status=None,
+ input_values=None, output_values=None,
+ status_reason=None, signal_transport=None):
+ """Creates or updates a software deployment."""
+ headers, body = self._prep_software_deploy_update(
+ deploy_id, server_id, config_id, action, status, input_values,
+ output_values, status_reason, signal_transport)
+
+ url = 'software_deployments/%s' % str(deploy_id)
+ resp, body = self.put(url, headers=headers, body=body)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def get_software_deploy_list(self):
+ """Returns a list of all deployments."""
+ url = 'software_deployments'
+ resp, body = self.get(url)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def get_software_deploy(self, deploy_id):
+ """Returns a specific software deployment."""
+ url = 'software_deployments/%s' % str(deploy_id)
+ resp, body = self.get(url)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def get_software_deploy_meta(self, server_id):
+ """Return a config metadata for a specific server."""
+ url = 'software_deployments/metadata/%s' % server_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp)
+ body = json.loads(body)
+ return body
+
+ def delete_software_deploy(self, deploy_id):
+ """Deletes a specific software deployment."""
+ url = 'software_deployments/%s' % str(deploy_id)
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp)
+
+ def _prep_software_config_create(self, name=None, conf=None, group=None,
+ inputs=None, outputs=None, options=None):
+ """Prepares a software configuration body."""
+ post_body = {}
+ if name is not None:
+ post_body["name"] = name
+ if conf is not None:
+ post_body["config"] = conf
+ if group is not None:
+ post_body["group"] = group
+ if inputs is not None:
+ post_body["inputs"] = inputs
+ if outputs is not None:
+ post_body["outputs"] = outputs
+ if options is not None:
+ post_body["options"] = options
+ body = json.dumps(post_body)
+
+ headers = self.get_headers()
+ return headers, body
+
+ def _prep_software_deploy_update(self, deploy_id=None, server_id=None,
+ config_id=None, action=None, status=None,
+ input_values=None, output_values=None,
+ status_reason=None,
+ signal_transport=None):
+ """Prepares a deployment create or update (if an id was given)."""
+ post_body = {}
+
+ if deploy_id is not None:
+ post_body["id"] = deploy_id
+ if server_id is not None:
+ post_body["server_id"] = server_id
+ if config_id is not None:
+ post_body["config_id"] = config_id
+ if action is not None:
+ post_body["action"] = action
+ if status is not None:
+ post_body["status"] = status
+ if input_values is not None:
+ post_body["input_values"] = input_values
+ if output_values is not None:
+ post_body["output_values"] = output_values
+ if status_reason is not None:
+ post_body["status_reason"] = status_reason
+ if signal_transport is not None:
+ post_body["signal_transport"] = signal_transport
+ body = json.dumps(post_body)
+
+ headers = self.get_headers()
+ return headers, 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/availability_zone_client.py b/tempest/services/volume/json/availability_zone_client.py
index 6839d3a..f2e7c5c 100644
--- a/tempest/services/volume/json/availability_zone_client.py
+++ b/tempest/services/volume/json/availability_zone_client.py
@@ -21,10 +21,10 @@
CONF = config.CONF
-class VolumeAvailabilityZoneClientJSON(rest_client.RestClient):
+class BaseVolumeAvailabilityZoneClientJSON(rest_client.RestClient):
def __init__(self, auth_provider):
- super(VolumeAvailabilityZoneClientJSON, self).__init__(
+ super(BaseVolumeAvailabilityZoneClientJSON, self).__init__(
auth_provider)
self.service = CONF.volume.catalog_type
@@ -32,3 +32,9 @@
resp, body = self.get('os-availability-zone')
body = json.loads(body)
return resp, body['availabilityZoneInfo']
+
+
+class VolumeAvailabilityZoneClientJSON(BaseVolumeAvailabilityZoneClientJSON):
+ """
+ Volume V1 availability zone client.
+ """
diff --git a/tempest/services/volume/json/extensions_client.py b/tempest/services/volume/json/extensions_client.py
index 9e182ea..e3ff00b 100644
--- a/tempest/services/volume/json/extensions_client.py
+++ b/tempest/services/volume/json/extensions_client.py
@@ -21,10 +21,10 @@
CONF = config.CONF
-class ExtensionsClientJSON(rest_client.RestClient):
+class BaseExtensionsClientJSON(rest_client.RestClient):
def __init__(self, auth_provider):
- super(ExtensionsClientJSON, self).__init__(auth_provider)
+ super(BaseExtensionsClientJSON, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
def list_extensions(self):
@@ -32,3 +32,9 @@
resp, body = self.get(url)
body = json.loads(body)
return resp, body['extensions']
+
+
+class ExtensionsClientJSON(BaseExtensionsClientJSON):
+ """
+ Volume V1 extensions client.
+ """
diff --git a/tempest/services/volume/v2/json/availability_zone_client.py b/tempest/services/volume/v2/json/availability_zone_client.py
new file mode 100644
index 0000000..047ba1b
--- /dev/null
+++ b/tempest/services/volume/v2/json/availability_zone_client.py
@@ -0,0 +1,26 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.json import availability_zone_client
+
+
+class VolumeV2AvailabilityZoneClientJSON(
+ availability_zone_client.BaseVolumeAvailabilityZoneClientJSON):
+
+ def __init__(self, auth_provider):
+ super(VolumeV2AvailabilityZoneClientJSON, self).__init__(
+ auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/services/volume/v2/json/extensions_client.py b/tempest/services/volume/v2/json/extensions_client.py
new file mode 100644
index 0000000..cc5244c
--- /dev/null
+++ b/tempest/services/volume/v2/json/extensions_client.py
@@ -0,0 +1,24 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.json import extensions_client
+
+
+class ExtensionsV2ClientJSON(extensions_client.BaseExtensionsClientJSON):
+
+ def __init__(self, auth_provider):
+ super(ExtensionsV2ClientJSON, self).__init__(auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/services/volume/v2/xml/availability_zone_client.py b/tempest/services/volume/v2/xml/availability_zone_client.py
new file mode 100644
index 0000000..68ca39b
--- /dev/null
+++ b/tempest/services/volume/v2/xml/availability_zone_client.py
@@ -0,0 +1,26 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.xml import availability_zone_client
+
+
+class VolumeV2AvailabilityZoneClientXML(
+ availability_zone_client.BaseVolumeAvailabilityZoneClientXML):
+
+ def __init__(self, auth_provider):
+ super(VolumeV2AvailabilityZoneClientXML, self).__init__(
+ auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/services/volume/v2/xml/extensions_client.py b/tempest/services/volume/v2/xml/extensions_client.py
new file mode 100644
index 0000000..13f333c
--- /dev/null
+++ b/tempest/services/volume/v2/xml/extensions_client.py
@@ -0,0 +1,24 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.xml import extensions_client
+
+
+class ExtensionsV2ClientXML(extensions_client.BaseExtensionsClientXML):
+
+ def __init__(self, auth_provider):
+ super(ExtensionsV2ClientXML, self).__init__(auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/services/volume/xml/availability_zone_client.py b/tempest/services/volume/xml/availability_zone_client.py
index e4a004a..a883ef5 100644
--- a/tempest/services/volume/xml/availability_zone_client.py
+++ b/tempest/services/volume/xml/availability_zone_client.py
@@ -22,11 +22,11 @@
CONF = config.CONF
-class VolumeAvailabilityZoneClientXML(rest_client.RestClient):
+class BaseVolumeAvailabilityZoneClientXML(rest_client.RestClient):
TYPE = "xml"
def __init__(self, auth_provider):
- super(VolumeAvailabilityZoneClientXML, self).__init__(
+ super(BaseVolumeAvailabilityZoneClientXML, self).__init__(
auth_provider)
self.service = CONF.volume.catalog_type
@@ -37,3 +37,9 @@
resp, body = self.get('os-availability-zone')
availability_zone = self._parse_array(etree.fromstring(body))
return resp, availability_zone
+
+
+class VolumeAvailabilityZoneClientXML(BaseVolumeAvailabilityZoneClientXML):
+ """
+ Volume V1 availability zone client.
+ """
diff --git a/tempest/services/volume/xml/extensions_client.py b/tempest/services/volume/xml/extensions_client.py
index 2986fcd..fe8b7cb 100644
--- a/tempest/services/volume/xml/extensions_client.py
+++ b/tempest/services/volume/xml/extensions_client.py
@@ -22,11 +22,11 @@
CONF = config.CONF
-class ExtensionsClientXML(rest_client.RestClient):
+class BaseExtensionsClientXML(rest_client.RestClient):
TYPE = "xml"
def __init__(self, auth_provider):
- super(ExtensionsClientXML, self).__init__(auth_provider)
+ super(BaseExtensionsClientXML, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
def _parse_array(self, node):
@@ -40,3 +40,9 @@
resp, body = self.get(url)
body = self._parse_array(etree.fromstring(body))
return resp, body
+
+
+class ExtensionsClientXML(BaseExtensionsClientXML):
+ """
+ Volume V1 extensions client.
+ """
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 0a63679..4f1f56c 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -1,3 +1,5 @@
+.. _stress_field_guide:
+
Tempest Field Guide to Stress Tests
===================================
diff --git a/tempest/test.py b/tempest/test.py
index f44fdf9..95ae23f 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -215,6 +215,8 @@
'network': CONF.network_feature_enabled.api_extensions,
'object': CONF.object_storage_feature_enabled.discoverable_apis,
}
+ if len(config_dict[service]) == 0:
+ return False
if config_dict[service][0] == 'all':
return True
if extension_name in config_dict[service]:
diff --git a/tempest/tests/README.rst b/tempest/tests/README.rst
index 33d321f..e54d4c0 100644
--- a/tempest/tests/README.rst
+++ b/tempest/tests/README.rst
@@ -1,3 +1,5 @@
+.. _unit_tests_field_guide:
+
Tempest Field Guide to Unit tests
=================================
diff --git a/tempest/tests/cli/test_command_failed.py b/tempest/tests/cli/test_command_failed.py
new file mode 100644
index 0000000..36a4fc8
--- /dev/null
+++ b/tempest/tests/cli/test_command_failed.py
@@ -0,0 +1,30 @@
+# 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 import exceptions
+from tempest.tests import base
+
+
+class TestOutputParser(base.TestCase):
+
+ def test_command_failed_exception(self):
+ returncode = 1
+ cmd = "foo"
+ stdout = "output"
+ stderr = "error"
+ try:
+ raise exceptions.CommandFailed(returncode, cmd, stdout, stderr)
+ except exceptions.CommandFailed as e:
+ self.assertIn(str(returncode), str(e))
+ self.assertIn(cmd, str(e))
+ self.assertIn(stdout, str(e))
+ self.assertIn(stderr, str(e))
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 4bed0c2..536cbcf 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -58,6 +58,6 @@
class FakePrivate(config.TempestConfigPrivate):
- def __init__(self):
+ def __init__(self, parse_conf=True, config_path=None):
cfg.CONF([], default_config_files=[])
self._set_attrs()
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 5a334c5..3dc2199 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -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:
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index 1dcddad..6a2e335 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -59,12 +59,24 @@
obviously don't test not implemented method or the ones which strongly
depends on them.
"""
- _auth_provider_class = auth.AuthProvider
- def test_check_credentials_class(self):
- self.assertRaises(NotImplementedError,
- self.auth_provider.check_credentials,
- auth.Credentials())
+ class FakeAuthProviderImpl(auth.AuthProvider):
+ def _decorate_request():
+ pass
+
+ def _fill_credentials():
+ pass
+
+ def _get_auth():
+ pass
+
+ def base_url():
+ pass
+
+ def is_expired():
+ pass
+
+ _auth_provider_class = FakeAuthProviderImpl
def test_check_credentials_bad_type(self):
self.assertFalse(self.auth_provider.check_credentials([]))
@@ -74,16 +86,6 @@
auth_provider = self._auth(credentials={})
self.assertIsInstance(auth_provider.credentials, auth.Credentials)
- def test_instantiate_with_bad_credentials_type(self):
- """
- Assure that credentials with bad type fail with TypeError
- """
- self.assertRaises(TypeError, self._auth, [])
-
- def test_auth_data_property(self):
- self.assertRaises(NotImplementedError, getattr, self.auth_provider,
- 'auth_data')
-
def test_auth_data_property_when_cache_exists(self):
self.auth_provider.cache = 'foo'
self.useFixture(mockpatch.PatchObject(self.auth_provider,
@@ -110,9 +112,10 @@
self.assertIsNone(self.auth_provider.alt_part)
self.assertIsNone(self.auth_provider.alt_auth_data)
- def test_fill_credentials(self):
- self.assertRaises(NotImplementedError,
- self.auth_provider.fill_credentials)
+ def test_auth_class(self):
+ self.assertRaises(TypeError,
+ auth.AuthProvider,
+ fake_credentials.FakeCredentials)
class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp):
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_hacking.py b/tempest/tests/test_hacking.py
index 91c3274..52fdf7e 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -99,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/thirdparty/README.rst b/tempest/thirdparty/README.rst
index 53cb54b..b0bfdf7 100644
--- a/tempest/thirdparty/README.rst
+++ b/tempest/thirdparty/README.rst
@@ -1,3 +1,5 @@
+.. _third_party_field_guide:
+
Tempest Field Guide to Third Party API tests
============================================
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index 7713931..2c68d6b 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -177,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()
diff --git a/test-requirements.txt b/test-requirements.txt
index 13ef291..cd8154b 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,6 +1,5 @@
hacking>=0.9.2,<0.10
# needed for doc build
-docutils==0.9.1
sphinx>=1.1.2,!=1.2.0,<1.3
python-subunit>=0.0.18
oslosphinx
diff --git a/tools/subunit-trace.py b/tools/subunit-trace.py
index c6f8eab..8ad59bb 100755
--- a/tools/subunit-trace.py
+++ b/tools/subunit-trace.py
@@ -263,7 +263,7 @@
parser = argparse.ArgumentParser()
parser.add_argument('--no-failure-debug', '-n', action='store_true',
dest='print_failures', help='Disable printing failure '
- 'debug infomation in realtime')
+ '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')
diff --git a/tox.ini b/tox.ini
index 7f69fad..9c32121 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,23 +6,28 @@
[testenv]
setenv = VIRTUAL_ENV={envdir}
OS_TEST_PATH=./tempest/test_discover
+ PYTHONHASHSEED=0
usedevelop = True
install_command = pip install -U {opts} {packages}
[testenv:py26]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py33]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py27]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:cover]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
[testenv:all]
@@ -84,6 +89,11 @@
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]
commands =
flake8 {posargs}