Merge "make -e full run tempest scenarios serially"
diff --git a/README.rst b/README.rst
index 9d19c23..3d7c804 100644
--- a/README.rst
+++ b/README.rst
@@ -209,15 +209,9 @@
Python 3.x
----------
-Starting during the Liberty release development cycle work began on enabling
-Tempest to run under both Python 2.7 and Python 3.4. Tempest strives to fully
-support running with Python 3.4 and newer. A gating unit test job was added to
-also run Tempest's unit tests under Python 3. This means that the Tempest
-code at least imports under Python 3.4 and things that have unit test coverage
-will work on Python 3.4. However, because large parts of Tempest are
-self-verifying there might be uncaught issues running on Python 3. So until
-there is a gating job which does a full Tempest run using Python 3 there
-isn't any guarantee that running Tempest under Python 3 is bug free.
+Starting during the Pike cycle Tempest has a gating CI job that runs tempest
+with Python 3. Any tempest release after 15.0.0 should fully support running
+under Python 3 as well as Python 2.7.
Legacy run method
-----------------
diff --git a/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml b/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml
new file mode 100644
index 0000000..73900ca
--- /dev/null
+++ b/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Adds a new cli option to tempest run, --combine, which is used to indicate
+ you want the subunit stream output combined with the previous run's in
+ the testr repository
diff --git a/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml b/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml
new file mode 100644
index 0000000..9927971
--- /dev/null
+++ b/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Add server tags APIs to the servers_client library.
+ This feature enables the possibility of upating, deleting
+ and checking existence of a tag on a server, as well
+ as updating and deleting all tags on a server.
+
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index a9daba8..5a38acc 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -37,7 +37,6 @@
# Non admin tenant ID
cls.tenant_id = cls.flavors_client.tenant_id
- cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
cls.vcpus = 1
cls.disk = 10
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 33d5d73..12e4587 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -39,7 +39,6 @@
super(FlavorsAccessNegativeTestJSON, cls).resource_setup()
cls.tenant_id = cls.flavors_client.tenant_id
- cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
cls.vcpus = 1
cls.disk = 10
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index aa75348..18655cb 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -30,7 +30,6 @@
def setup_clients(cls):
super(MigrationsAdminTest, cls).setup_clients()
cls.client = cls.os_adm.migrations_client
- cls.flavors_admin_client = cls.os_adm.flavors_client
@decorators.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f')
def test_list_migrations(self):
@@ -54,8 +53,8 @@
def _flavor_clean_up(self, flavor_id):
try:
- self.flavors_admin_client.delete_flavor(flavor_id)
- self.flavors_admin_client.wait_for_resource_deletion(flavor_id)
+ self.admin_flavors_client.delete_flavor(flavor_id)
+ self.admin_flavors_client.wait_for_resource_deletion(flavor_id)
except exceptions.NotFound:
pass
@@ -68,9 +67,9 @@
# First we have to create a flavor that we can delete so make a copy
# of the normal flavor from which we'd create a server.
- flavor = self.flavors_admin_client.show_flavor(
+ flavor = self.admin_flavors_client.show_flavor(
self.flavor_ref)['flavor']
- flavor = self.flavors_admin_client.create_flavor(
+ flavor = self.admin_flavors_client.create_flavor(
name=data_utils.rand_name('test_resize_flavor_'),
ram=flavor['ram'],
disk=flavor['disk'],
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 13a411b..adb49a5 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -34,7 +34,6 @@
super(ServersAdminNegativeTestJSON, cls).setup_clients()
cls.client = cls.os_adm.servers_client
cls.non_adm_client = cls.servers_client
- cls.flavors_client = cls.os_adm.flavors_client
cls.quotas_client = cls.os_adm.quotas_client
@classmethod
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 20646d1..fd5e50e 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -236,7 +236,6 @@
@classmethod
def setup_clients(cls):
super(ServersWithSpecificFlavorTestJSON, cls).setup_clients()
- cls.flavor_client = cls.os_adm.flavors_client
cls.client = cls.servers_client
@classmethod
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index dfda51b..cf4ed85 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -49,7 +49,7 @@
# We do not know the exact network configuration, but an instance
# should at least have a single public or private address
self.assertGreaterEqual(len(addresses), 1)
- for network_name, network_addresses in addresses.items():
+ for network_addresses in addresses.values():
self.assertGreaterEqual(len(network_addresses), 1)
for address in network_addresses:
self.assertTrue(address['addr'])
diff --git a/tempest/api/compute/servers/test_server_tags.py b/tempest/api/compute/servers/test_server_tags.py
new file mode 100644
index 0000000..20e2cee
--- /dev/null
+++ b/tempest/api/compute/servers/test_server_tags.py
@@ -0,0 +1,108 @@
+# Copyright 2017 AT&T Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import six
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+
+class ServerTagsTestJSON(base.BaseV2ComputeTest):
+
+ min_microversion = '2.26'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(ServerTagsTestJSON, cls).skip_checks()
+ if not test.is_extension_enabled('os-server-tags', 'compute'):
+ msg = "os-server-tags extension is not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerTagsTestJSON, cls).setup_clients()
+ cls.client = cls.servers_client
+
+ @classmethod
+ def resource_setup(cls):
+ super(ServerTagsTestJSON, cls).resource_setup()
+ cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+ def _update_server_tags(self, server_id, tags):
+ if not isinstance(tags, (list, tuple)):
+ tags = [tags]
+ for tag in tags:
+ self.client.update_tag(server_id, tag)
+ self.addCleanup(self.client.delete_all_tags, server_id)
+
+ @decorators.idempotent_id('8d95abe2-c658-4c42-9a44-c0258500306b')
+ def test_create_delete_tag(self):
+ # Check that no tags exist.
+ fetched_tags = self.client.list_tags(self.server['id'])['tags']
+ self.assertEmpty(fetched_tags)
+
+ # Add server tag to the server.
+ assigned_tag = data_utils.rand_name('tag')
+ self._update_server_tags(self.server['id'], assigned_tag)
+
+ # Check that added tag exists.
+ fetched_tags = self.client.list_tags(self.server['id'])['tags']
+ self.assertEqual([assigned_tag], fetched_tags)
+
+ # Remove assigned tag from server and check that it was removed.
+ self.client.delete_tag(self.server['id'], assigned_tag)
+ fetched_tags = self.client.list_tags(self.server['id'])['tags']
+ self.assertEmpty(fetched_tags)
+
+ @decorators.idempotent_id('a2c1af8c-127d-417d-974b-8115f7e3d831')
+ def test_update_all_tags(self):
+ # Add server tags to the server.
+ tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
+ self._update_server_tags(self.server['id'], tags)
+
+ # Replace tags with new tags and check that they are present.
+ new_tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
+ replaced_tags = self.client.update_all_tags(
+ self.server['id'], new_tags)['tags']
+ six.assertCountEqual(self, new_tags, replaced_tags)
+
+ # List the tags and check that the tags were replaced.
+ fetched_tags = self.client.list_tags(self.server['id'])['tags']
+ six.assertCountEqual(self, new_tags, fetched_tags)
+
+ @decorators.idempotent_id('a63b2a74-e918-4b7c-bcab-10c855f3a57e')
+ def test_delete_all_tags(self):
+ # Add server tags to the server.
+ assigned_tags = [data_utils.rand_name('tag'),
+ data_utils.rand_name('tag')]
+ self._update_server_tags(self.server['id'], assigned_tags)
+
+ # Delete tags from the server and check that they were deleted.
+ self.client.delete_all_tags(self.server['id'])
+ fetched_tags = self.client.list_tags(self.server['id'])['tags']
+ self.assertEmpty(fetched_tags)
+
+ @decorators.idempotent_id('81279a66-61c3-4759-b830-a2dbe64cbe08')
+ def test_check_tag_existence(self):
+ # Add server tag to the server.
+ assigned_tag = data_utils.rand_name('tag')
+ self._update_server_tags(self.server['id'], assigned_tag)
+
+ # Check that added tag exists. Throws a 404 if not found, else a 204,
+ # which was already checked by the schema validation.
+ self.client.check_tag_existence(self.server['id'], assigned_tag)
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index b22a434..1418b3f 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -176,7 +176,7 @@
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
- server['id'], self.image_ref_alt)
+ server['id'], self.image_ref)
@test.related_bug('1660878', status_code=409)
@test.attr(type=['negative'])
@@ -198,7 +198,7 @@
self.assertRaises(lib_exc.NotFound,
self.client.rebuild_server,
nonexistent_server,
- self.image_ref_alt)
+ self.image_ref)
@test.attr(type=['negative'])
@decorators.idempotent_id('fd57f159-68d6-4c2a-902b-03070828a87e')
diff --git a/tempest/api/compute/test_versions.py b/tempest/api/compute/test_versions.py
index c9f0724..dcab067 100644
--- a/tempest/api/compute/test_versions.py
+++ b/tempest/api/compute/test_versions.py
@@ -14,11 +14,13 @@
from tempest.api.compute import base
from tempest.lib import decorators
+from tempest import test
class TestVersions(base.BaseV2ComputeTest):
@decorators.idempotent_id('6c0a0990-43b6-4529-9b61-5fd8daf7c55c')
+ @test.attr(type='smoke')
def test_list_api_versions(self):
"""Test that a get of the unversioned url returns the choices doc.
@@ -37,6 +39,7 @@
"The first listed version should be v2.0")
@decorators.idempotent_id('b953a29e-929c-4a8e-81be-ec3a7e03cb76')
+ @test.attr(type='smoke')
def test_get_version_details(self):
"""Test individual version endpoints info works.
diff --git a/tempest/api/identity/admin/v2/test_endpoints.py b/tempest/api/identity/admin/v2/test_endpoints.py
index df55d2f..0ea2eb3 100644
--- a/tempest/api/identity/admin/v2/test_endpoints.py
+++ b/tempest/api/identity/admin/v2/test_endpoints.py
@@ -27,10 +27,10 @@
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
s_description = data_utils.rand_name('description')
- cls.service_data = cls.services_client.create_service(
+ service_data = cls.services_client.create_service(
name=s_name, type=s_type,
description=s_description)['OS-KSADM:service']
- cls.service_id = cls.service_data['id']
+ cls.service_id = service_data['id']
cls.service_ids.append(cls.service_id)
# Create endpoints so as to use for LIST and GET test cases
cls.setup_endpoints = list()
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 686743b..9a0b3e4 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -33,11 +33,10 @@
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
s_description = data_utils.rand_name('description')
- cls.service_data = (
+ service_data = (
cls.services_client.create_service(name=s_name, type=s_type,
description=s_description))
- cls.service_data = cls.service_data['service']
- cls.service_id = cls.service_data['id']
+ cls.service_id = service_data['service']['id']
cls.service_ids.append(cls.service_id)
# Create endpoints so as to use for LIST and GET test cases
cls.setup_endpoints = list()
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 53c2b1f..8e00193 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -35,11 +35,11 @@
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
s_description = data_utils.rand_name('description')
- cls.service_data = (
+ service_data = (
cls.services_client.create_service(name=s_name, type=s_type,
description=s_description)
['service'])
- cls.service_id = cls.service_data['id']
+ cls.service_id = service_data['id']
cls.service_ids.append(cls.service_id)
@classmethod
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index b7b6596..9bee24a 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -15,11 +15,14 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest import config
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest import test
+CONF = config.CONF
+
class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
@@ -348,6 +351,15 @@
# domain role to a global one
self._create_implied_role(domain_role1['id'], self.role['id'])
+ if CONF.identity_feature_enabled.forbid_global_implied_dsr:
+ # The contrary is not true: we can't create an inference rule
+ # from a global role to a domain role
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.roles_client.create_role_inference_rule,
+ self.role['id'],
+ domain_role1['id'])
+
@decorators.idempotent_id('3859df7e-5b78-4e4d-b10e-214c8953842a')
def test_assignments_for_domain_roles(self):
domain_role = self.setup_test_role(domain_id=self.domain['id'])
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 54b844a..b36bf5c 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -78,11 +78,20 @@
subunit-trace output filter. But, if you would prefer a subunit v2 stream be
output to STDOUT use the **--subunit** flag
+Combining Runs
+==============
+
+There are certain situations in which you want to split a single run of tempest
+across 2 executions of tempest run. (for example to run part of the tests
+serially and others in parallel) To accomplish this but still treat the results
+as a single run you can leverage the **--combine** option which will append
+the current run's results with the previous runs.
"""
import io
import os
import sys
+import tempfile
import threading
from cliff import command
@@ -165,6 +174,12 @@
else:
print("No .testr.conf file was found for local execution")
sys.exit(2)
+ if parsed_args.combine:
+ temp_stream = tempfile.NamedTemporaryFile()
+ return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
+ temp_stream, sys.stderr)
+ if return_code > 0:
+ sys.exit(return_code)
regex = self._build_regex(parsed_args)
if parsed_args.list_tests:
@@ -173,6 +188,16 @@
else:
options = self._build_options(parsed_args)
returncode = self._run(regex, options)
+ if returncode > 0:
+ sys.exit(returncode)
+
+ if parsed_args.combine:
+ return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
+ temp_stream, sys.stderr)
+ if return_code > 0:
+ sys.exit(return_code)
+ returncode = run_argv(['tempest', 'load', temp_stream.name],
+ sys.stdin, sys.stdout, sys.stderr)
sys.exit(returncode)
def get_description(self):
@@ -231,6 +256,10 @@
# output args
parser.add_argument("--subunit", action='store_true',
help='Enable subunit v2 output')
+ parser.add_argument("--combine", action='store_true',
+ help='Combine the output of this run with the '
+ "previous run's as a combined stream in the "
+ "testr repository after it finish")
parser.set_defaults(parallel=True)
return parser
diff --git a/tempest/config.py b/tempest/config.py
index 83c5c0e..651c32e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -225,6 +225,13 @@
deprecated_for_removal=True,
deprecated_reason="All supported version of OpenStack now "
"supports the 'reseller' feature"),
+ # TODO(rodrigods): This is a feature flag for bug 1590578 which is fixed
+ # in Newton and Ocata. This option can be removed after Mitaka is end of
+ # life.
+ cfg.BoolOpt('forbid_global_implied_dsr',
+ default=False,
+ help='Does the environment forbid global roles implying '
+ 'domain specific ones?'),
cfg.BoolOpt('security_compliance',
default=False,
help='Does the environment have the security compliance '
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index bc5d18e..d873402 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -1,4 +1,5 @@
# Copyright 2016 IBM Corp.
+# Copyright 2017 AT&T Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -45,3 +46,41 @@
# list response schema wasn't changed for v2.26 so use v2.1
list_servers = copy.deepcopy(servers21.list_servers)
+
+list_tags = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'tags': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'string'
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['tags']
+ }
+}
+
+update_all_tags = copy.deepcopy(list_tags)
+
+delete_all_tags = {'status_code': [204]}
+
+check_tag_existence = {'status_code': [204]}
+
+update_tag = {
+ 'status_code': [201, 204],
+ 'response_header': {
+ 'type': 'object',
+ 'properties': {
+ 'location': {
+ 'type': 'string'
+ }
+ },
+ 'required': ['location']
+ }
+}
+
+delete_tag = {'status_code': [204]}
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 5e65bee..657c0c1 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -111,6 +111,7 @@
except (EOFError,
socket.error, socket.timeout,
paramiko.SSHException) as e:
+ ssh.close()
if self._is_timed_out(_start_time):
LOG.exception("Failed to establish authenticated ssh"
" connection to %s@%s after %d attempts",
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index adff244..c167d81 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -1,5 +1,6 @@
# Copyright 2012 OpenStack Foundation
# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# Copyright 2017 AT&T Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -732,3 +733,92 @@
self.validate_response(security_groups_schema.list_security_groups,
resp, body)
return rest_client.ResponseBody(resp, body)
+
+ def list_tags(self, server_id):
+ """Lists all tags for a server.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#list-tags
+ """
+ url = 'servers/%s/tags' % server_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.list_tags, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_all_tags(self, server_id, tags):
+ """Replaces all tags on specified server with the new set of tags.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#replace-tags
+
+ :param tags: List of tags to replace current server tags with.
+ """
+ url = 'servers/%s/tags' % server_id
+ put_body = {'tags': tags}
+ resp, body = self.put(url, json.dumps(put_body))
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.update_all_tags, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_all_tags(self, server_id):
+ """Deletes all tags from the specified server.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#delete-all-tags
+ """
+ url = 'servers/%s/tags' % server_id
+ resp, body = self.delete(url)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.delete_all_tags, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_tag_existence(self, server_id, tag):
+ """Checks tag existence on the server.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#check-tag-existence
+
+ :param tag: Check for existence of tag on specified server.
+ """
+ url = 'servers/%s/tags/%s' % (server_id, tag)
+ resp, body = self.get(url)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.check_tag_existence, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_tag(self, server_id, tag):
+ """Adds a single tag to the server if server has no specified tag.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#add-a-single-tag
+
+ :param tag: Tag to be added to the specified server.
+ """
+ url = 'servers/%s/tags/%s' % (server_id, tag)
+ resp, body = self.put(url, None)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.update_tag, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_tag(self, server_id, tag):
+ """Deletes a single tag from the specified server.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#delete-a-single-tag
+
+ :param tag: Tag to be removed from the specified server.
+ """
+ url = 'servers/%s/tags/%s' % (server_id, tag)
+ resp, body = self.delete(url)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.delete_tag, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index e58031b..e5f5f68 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -912,7 +912,7 @@
# The target login is assumed to have been configured for
# key-based authentication by cloud-init.
try:
- for net_name, ip_addresses in server['addresses'].items():
+ for ip_addresses in server['addresses'].values():
for ip_address in ip_addresses:
self.check_vm_connectivity(ip_address['addr'],
username,
@@ -926,14 +926,15 @@
def _check_remote_connectivity(self, source, dest, should_succeed=True,
nic=None):
- """check ping server via source ssh connection
+ """assert ping server via source ssh connection
+
+ Note: This is an internal method. Use check_remote_connectivity
+ instead.
:param source: RemoteClient: an ssh connection from which to ping
:param dest: and IP to ping against
:param should_succeed: boolean should ping succeed or not
:param nic: specific network interface to ping from
- :returns: boolean -- should_succeed == ping
- :returns: ping is false if ping failed
"""
def ping_remote():
try:
@@ -948,6 +949,25 @@
CONF.validation.ping_timeout,
1)
+ def check_remote_connectivity(self, source, dest, should_succeed=True,
+ nic=None):
+ """assert ping server via source ssh connection
+
+ :param source: RemoteClient: an ssh connection from which to ping
+ :param dest: and IP to ping against
+ :param should_succeed: boolean should ping succeed or not
+ :param nic: specific network interface to ping from
+ """
+ result = self._check_remote_connectivity(source, dest, should_succeed,
+ nic)
+ source_host = source.ssh_client.host
+ if should_succeed:
+ msg = "Timed out waiting for %s to become reachable from %s" \
+ % (dest, source_host)
+ else:
+ msg = "%s is reachable from %s" % (dest, source_host)
+ self.assertTrue(result, msg)
+
def _create_security_group(self, security_group_rules_client=None,
tenant_id=None,
namestart='secgroup-smoke',
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 6c01038..b0b516a 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -82,7 +82,7 @@
aggregate = self.aggregates_client.set_metadata(aggregate['id'],
metadata=meta)
- for key, value in meta.items():
+ for key in meta.keys():
self.assertEqual(meta[key],
aggregate['aggregate']['metadata'][key])
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 27c45cb..5fee801 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -94,7 +94,7 @@
raise exceptions.TimeoutException(msg)
def _get_floating_ip_in_server_addresses(self, floating_ip, server):
- for network_name, addresses in server['addresses'].items():
+ for addresses in server['addresses'].values():
for address in addresses:
if (address['OS-EXT-IPS:type'] == 'floating' and
address['addr'] == floating_ip['ip']):
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 3e6c043..e6a2e9d 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -347,20 +347,8 @@
ip_address, private_key=private_key)
for remote_ip in address_list:
- if should_connect:
- msg = ("Timed out waiting for %s to become "
- "reachable") % remote_ip
- else:
- msg = "ip address %s is reachable" % remote_ip
- try:
- self.assertTrue(self._check_remote_connectivity
- (ssh_source, remote_ip, should_connect),
- msg)
- except Exception:
- LOG.exception("Unable to access {dest} via ssh to "
- "floating-ip {src}".format(dest=remote_ip,
- src=floating_ip))
- raise
+ self.check_remote_connectivity(ssh_source, remote_ip,
+ should_connect)
@test.attr(type='smoke')
@decorators.idempotent_id('f323b3ba-82f8-4db7-8ea6-6a895869ec49')
@@ -653,21 +641,21 @@
self.check_public_network_connectivity(
should_connect=True, msg="before updating "
"admin_state_up of instance port to False")
- self._check_remote_connectivity(ssh_client, dest=server_pip,
- should_succeed=True)
+ self.check_remote_connectivity(ssh_client, dest=server_pip,
+ should_succeed=True)
self.ports_client.update_port(port_id, admin_state_up=False)
self.check_public_network_connectivity(
should_connect=False, msg="after updating "
"admin_state_up of instance port to False",
should_check_floating_ip_status=False)
- self._check_remote_connectivity(ssh_client, dest=server_pip,
- should_succeed=False)
+ self.check_remote_connectivity(ssh_client, dest=server_pip,
+ should_succeed=False)
self.ports_client.update_port(port_id, admin_state_up=True)
self.check_public_network_connectivity(
should_connect=True, msg="after updating "
"admin_state_up of instance port to True")
- self._check_remote_connectivity(ssh_client, dest=server_pip,
- should_succeed=True)
+ self.check_remote_connectivity(ssh_client, dest=server_pip,
+ should_succeed=True)
@decorators.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa')
@test.attr(type='slow')
@@ -841,15 +829,15 @@
spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"])
peer = self._create_server(self.new_net)
peer_address = peer['addresses'][self.new_net['name']][0]['addr']
- self._check_remote_connectivity(ssh_client, dest=peer_address,
- nic=spoof_nic, should_succeed=True)
+ self.check_remote_connectivity(ssh_client, dest=peer_address,
+ nic=spoof_nic, should_succeed=True)
ssh_client.set_mac_address(spoof_nic, spoof_mac)
new_mac = ssh_client.get_mac_address(nic=spoof_nic)
self.assertEqual(spoof_mac, new_mac)
- self._check_remote_connectivity(ssh_client, dest=peer_address,
- nic=spoof_nic, should_succeed=False)
+ self.check_remote_connectivity(ssh_client, dest=peer_address,
+ nic=spoof_nic, should_succeed=False)
self.ports_client.update_port(spoof_port["id"],
port_security_enabled=False,
security_groups=[])
- self._check_remote_connectivity(ssh_client, dest=peer_address,
- nic=spoof_nic, should_succeed=True)
+ self.check_remote_connectivity(ssh_client, dest=peer_address,
+ nic=spoof_nic, should_succeed=True)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 30173a1..d8a1363 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -110,7 +110,7 @@
@staticmethod
def define_server_ips(srv):
ips = {'4': None, '6': []}
- for net_name, nics in srv['addresses'].items():
+ for nics in srv['addresses'].values():
for nic in nics:
if nic['version'] == 6:
ips['6'].append(nic['addr'])
@@ -191,25 +191,18 @@
self.assertTrue(test_utils.call_until_true(srv2_v6_addr_assigned,
CONF.validation.ping_timeout, 1))
- self._check_connectivity(sshv4_1, ips_from_api_2['4'])
- self._check_connectivity(sshv4_2, ips_from_api_1['4'])
+ self.check_remote_connectivity(sshv4_1, ips_from_api_2['4'])
+ self.check_remote_connectivity(sshv4_2, ips_from_api_1['4'])
for i in range(n_subnets6):
- self._check_connectivity(sshv4_1,
- ips_from_api_2['6'][i])
- self._check_connectivity(sshv4_1,
- self.subnets_v6[i]['gateway_ip'])
- self._check_connectivity(sshv4_2,
- ips_from_api_1['6'][i])
- self._check_connectivity(sshv4_2,
- self.subnets_v6[i]['gateway_ip'])
-
- def _check_connectivity(self, source, dest):
- self.assertTrue(
- self._check_remote_connectivity(source, dest),
- "Timed out waiting for %s to become reachable from %s" %
- (dest, source.ssh_client.host)
- )
+ self.check_remote_connectivity(sshv4_1,
+ ips_from_api_2['6'][i])
+ self.check_remote_connectivity(sshv4_1,
+ self.subnets_v6[i]['gateway_ip'])
+ self.check_remote_connectivity(sshv4_2,
+ ips_from_api_1['6'][i])
+ self.check_remote_connectivity(sshv4_2,
+ self.subnets_v6[i]['gateway_ip'])
@test.attr(type='slow')
@decorators.idempotent_id('2c92df61-29f0-4eaa-bee3-7c65bef62a43')
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 806c17c..fa12f33 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -368,20 +368,12 @@
access_point_ssh, private_key=private_key)
return access_point_ssh
- def _check_connectivity(self, access_point, ip, should_succeed=True):
- if should_succeed:
- msg = "Timed out waiting for %s to become reachable" % ip
- else:
- msg = "%s is reachable" % ip
- self.assertTrue(self._check_remote_connectivity(access_point, ip,
- should_succeed), msg)
-
def _test_in_tenant_block(self, tenant):
access_point_ssh = self._connect_to_access_point(tenant)
for server in tenant.servers:
- self._check_connectivity(access_point=access_point_ssh,
- ip=self._get_server_ip(server),
- should_succeed=False)
+ self.check_remote_connectivity(source=access_point_ssh,
+ dest=self._get_server_ip(server),
+ should_succeed=False)
def _test_in_tenant_allow(self, tenant):
ruleset = dict(
@@ -396,8 +388,8 @@
)
access_point_ssh = self._connect_to_access_point(tenant)
for server in tenant.servers:
- self._check_connectivity(access_point=access_point_ssh,
- ip=self._get_server_ip(server))
+ self.check_remote_connectivity(source=access_point_ssh,
+ dest=self._get_server_ip(server))
def _test_cross_tenant_block(self, source_tenant, dest_tenant):
# if public router isn't defined, then dest_tenant access is via
@@ -405,8 +397,8 @@
access_point_ssh = self._connect_to_access_point(source_tenant)
ip = self._get_server_ip(dest_tenant.access_point,
floating=self.floating_ip_access)
- self._check_connectivity(access_point=access_point_ssh, ip=ip,
- should_succeed=False)
+ self.check_remote_connectivity(source=access_point_ssh, dest=ip,
+ should_succeed=False)
def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
"""check for each direction:
@@ -427,7 +419,7 @@
access_point_ssh = self._connect_to_access_point(source_tenant)
ip = self._get_server_ip(dest_tenant.access_point,
floating=self.floating_ip_access)
- self._check_connectivity(access_point_ssh, ip)
+ self.check_remote_connectivity(access_point_ssh, ip)
# test that reverse traffic is still blocked
self._test_cross_tenant_block(dest_tenant, source_tenant)
@@ -444,7 +436,7 @@
access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
ip = self._get_server_ip(source_tenant.access_point,
floating=self.floating_ip_access)
- self._check_connectivity(access_point_ssh_2, ip)
+ self.check_remote_connectivity(access_point_ssh_2, ip)
def _verify_mac_addr(self, tenant):
"""Verify that VM has the same ip, mac as listed in port"""
@@ -538,9 +530,9 @@
# Check connectivity failure with default security group
try:
access_point_ssh = self._connect_to_access_point(new_tenant)
- self._check_connectivity(access_point=access_point_ssh,
- ip=self._get_server_ip(server),
- should_succeed=False)
+ self.check_remote_connectivity(source=access_point_ssh,
+ dest=self._get_server_ip(server),
+ should_succeed=False)
server_id = server['id']
port_id = self.admin_manager.ports_client.list_ports(
device_id=server_id)['ports'][0]['id']
@@ -548,9 +540,9 @@
# update port with new security group and check connectivity
self.ports_client.update_port(port_id, security_groups=[
new_tenant.security_groups['new_sg']['id']])
- self._check_connectivity(
- access_point=access_point_ssh,
- ip=self._get_server_ip(server))
+ self.check_remote_connectivity(
+ source=access_point_ssh,
+ dest=self._get_server_ip(server))
except Exception:
for tenant in self.tenants.values():
self._log_console_output(servers=tenant.servers)
@@ -615,16 +607,16 @@
self.ports_client.update_port(port_id,
port_security_enabled=True,
security_groups=[])
- self._check_connectivity(access_point=access_point_ssh,
- ip=self._get_server_ip(server),
- should_succeed=False)
+ self.check_remote_connectivity(source=access_point_ssh,
+ dest=self._get_server_ip(server),
+ should_succeed=False)
self.ports_client.update_port(port_id,
port_security_enabled=False,
security_groups=[])
- self._check_connectivity(
- access_point=access_point_ssh,
- ip=self._get_server_ip(server))
+ self.check_remote_connectivity(
+ source=access_point_ssh,
+ dest=self._get_server_ip(server))
except Exception:
for tenant in self.tenants.values():
self._log_console_output(servers=tenant.servers)
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index b563ab2..8d391c1 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -1,4 +1,5 @@
# Copyright 2015 IBM Corp.
+# Copyright 2017 AT&T Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -14,6 +15,9 @@
import copy
+import mock
+
+from tempest.lib.services.compute import base_compute_client
from tempest.lib.services.compute import servers_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -186,6 +190,9 @@
FAKE_REBUILD_SERVER = copy.deepcopy(FAKE_SERVER_GET)
FAKE_REBUILD_SERVER['server']['adminPass'] = 'fake-admin-pass'
+ FAKE_TAGS = ["foo", "bar"]
+ REPLACE_FAKE_TAGS = ["baz", "qux"]
+
server_id = FAKE_SERVER_GET['server']['id']
network_id = 'a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb'
@@ -194,6 +201,7 @@
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = servers_client.ServersClient(
fake_auth, 'compute', 'regionOne')
+ self.addCleanup(mock.patch.stopall)
def test_list_servers_with_str_body(self):
self._test_list_servers()
@@ -1031,3 +1039,113 @@
{'security_groups': self.FAKE_SECURITY_GROUPS},
server_id=self.server_id,
)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_list_tags_str_body(self, _):
+ self._test_list_tags()
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_list_tags_byte_body(self, _):
+ self._test_list_tags(bytes_body=True)
+
+ def _test_list_tags(self, bytes_body=False):
+ expected = {"tags": self.FAKE_TAGS}
+ self.check_service_client_function(
+ self.client.list_tags,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ server_id=self.server_id,
+ to_utf=bytes_body)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_update_all_tags_str_body(self, _):
+ self._test_update_all_tags()
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_update_all_tags_byte_body(self, _):
+ self._test_update_all_tags(bytes_body=True)
+
+ def _test_update_all_tags(self, bytes_body=False):
+ expected = {"tags": self.REPLACE_FAKE_TAGS}
+ self.check_service_client_function(
+ self.client.update_all_tags,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ expected,
+ server_id=self.server_id,
+ tags=self.REPLACE_FAKE_TAGS,
+ to_utf=bytes_body)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_delete_all_tags(self, _):
+ self.check_service_client_function(
+ self.client.delete_all_tags,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ server_id=self.server_id,
+ status=204)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_check_tag_existence_str_body(self, _):
+ self._test_check_tag_existence()
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_check_tag_existence_byte_body(self, _):
+ self._test_check_tag_existence(bytes_body=True)
+
+ def _test_check_tag_existence(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_tag_existence,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {},
+ server_id=self.server_id,
+ tag=self.FAKE_TAGS[0],
+ status=204,
+ to_utf=bytes_body)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_update_tag_str_body(self, _):
+ self._test_update_tag()
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_update_tag_byte_body(self, _):
+ self._test_update_tag(bytes_body=True)
+
+ def _test_update_tag(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_tag,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ server_id=self.server_id,
+ tag=self.FAKE_TAGS[0],
+ status=201,
+ headers={'location': 'fake_location'},
+ to_utf=bytes_body)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_delete_tag_str_body(self, _):
+ self._test_delete_tag()
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.26'))
+ def test_delete_tag_byte_body(self, _):
+ self._test_delete_tag(bytes_body=True)
+
+ def _test_delete_tag(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_tag,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ server_id=self.server_id,
+ tag=self.FAKE_TAGS[0],
+ status=204,
+ to_utf=bytes_body)
diff --git a/test-requirements.txt b/test-requirements.txt
index 844d32c..936d5aa 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-hacking<0.13,>=0.12.0 # Apache-2.0
+hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0
# needed for doc build
sphinx>=1.5.1 # BSD
oslosphinx>=4.7.0 # Apache-2.0