Merge "remove skip for test_connectivity_between_vms_on_different_networks"
diff --git a/.zuul.yaml b/.zuul.yaml
index 8ab3028..fd3aa2a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -144,8 +144,11 @@
# TODO(gmann): needs to migrate this to zuulv3
- job:
- name: tempest-scenario-multinode-lvm-multibackend
+ name: tempest-scenario-all
parent: legacy-dsvm-base-multinode
+ description: |
+ This job will run all scenario tests including slow tests
+ with lvm multibackend setup. This job will not run any API tests.
run: playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
post-run: playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
timeout: 10800
@@ -336,8 +339,7 @@
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
- tempest-tox-plugin-sanity-check
- - tempest-scenario-multinode-lvm-multibackend:
- voting: false
+ - tempest-scenario-all:
irrelevant-files:
- ^(test-|)requirements.txt$
- ^.*\.rst$
@@ -347,6 +349,7 @@
- ^setup.cfg$
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
+ - ^tempest/api/.*$
- nova-cells-v1:
irrelevant-files:
- ^(test-|)requirements.txt$
@@ -357,12 +360,21 @@
- ^setup.cfg$
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
+ - nova-live-migration:
+ voting: false
+ irrelevant-files:
+ - ^(test-|)requirements.txt$
+ - ^.*\.rst$
+ - ^doc/.*$
+ - ^etc/.*$
+ - ^releasenotes/.*$
+ - ^setup.cfg$
+ - ^tempest/hacking/.*$
+ - ^tempest/tests/.*$
gate:
jobs:
- nova-multiattach
- experimental:
- jobs:
- - nova-live-migration:
+ - tempest-scenario-all:
irrelevant-files:
- ^(test-|)requirements.txt$
- ^.*\.rst$
@@ -372,6 +384,9 @@
- ^setup.cfg$
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
+ - ^tempest/api/.*$
+ experimental:
+ jobs:
- tempest-cinder-v2-api:
irrelevant-files:
- ^(test-|)requirements.txt$
diff --git a/HACKING.rst b/HACKING.rst
index bb55ac5..2a7ae1d 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -106,7 +106,7 @@
test method. You specify the services with the ``tempest.common.utils.services``
decorator. For example:
-@utils.services('compute', 'image')
+``@utils.services('compute', 'image')``
Valid service tag names are the same as the list of directories in tempest.api
that have tests.
@@ -118,6 +118,59 @@
in ``tempest.api.compute`` would require a service tag for those services,
however they do not need to be tagged as ``compute``.
+Test Attributes
+---------------
+Tempest leverages `test attributes`_ which are a simple but effective way of
+distinguishing between different "types" of API tests. A test can be "tagged"
+with such attributes using the ``decorators.attr`` decorator, for example::
+
+ @decorators.attr(type=['negative'])
+ def test_aggregate_create_aggregate_name_length_less_than_1(self):
+ [...]
+
+These test attributes can be used for test selection via regular expressions.
+For example, ``(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)`` runs all the tests
+in the ``scenario`` test module, *except* for those tagged with the ``slow``
+attribute (via a negative lookahead in the regular expression). These
+attributes are used in Tempest's ``tox.ini`` as well as Tempest's Zuul job
+definitions for specifying particular batches of Tempest test suites to run.
+
+.. _test attributes: https://testtools.readthedocs.io/en/latest/for-test-authors.html?highlight=attr#test-attributes
+
+Negative Attribute
+^^^^^^^^^^^^^^^^^^
+The ``type='negative'`` attribute is used to signify that a test is a negative
+test, which is a test that handles invalid input gracefully. This attribute
+should be applied to all negative test scenarios.
+
+This attribute must be applied to each test that belongs to a negative test
+class, i.e. a test class name ending with "Negative.*" substring.
+
+.. todo::
+
+ Add a hacking check for ensuring that all classes that contain substring
+ "Negative" have the negative attribute decorator applied above each test.
+
+Slow Attribute
+^^^^^^^^^^^^^^
+The ``type='slow'`` attribute is used to signify that a test takes a long time
+to run, relatively speaking. This attribute is usually applied to
+:ref:`scenario tests <scenario_field_guide>`, which involve a complicated
+series of API operations, the total runtime of which can be relatively long.
+This long runtime has performance implications on `Zuul`_ jobs, which is why
+the ``slow`` attribute is leveraged to run slow tests on a selective basis,
+to keep total `Zuul`_ job runtime down to a reasonable time frame.
+
+.. _Zuul: https://docs.openstack.org/infra/zuul/
+
+Smoke Attribute
+^^^^^^^^^^^^^^^
+The ``type='smoke'`` attribute is used to signify that a test is a so-called
+smoke test, which is a type of test that tests the most vital OpenStack
+functionality, like listing servers or flavors or creating volumes. The
+attribute should be sparingly applied to only the tests that sanity-check the
+most essential functionality of an OpenStack cloud.
+
Test fixtures and resources
---------------------------
Test level resources should be cleaned-up after the test execution. Clean-up
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index ea868ae..6dd00d3 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -358,22 +358,30 @@
.. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
+ * `2.53`_
+
+ .. _2.53: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-pike
+
* `2.54`_
- .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id4
+ .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
* `2.55`_
- .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
+ .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
* `2.57`_
- .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id51
+ .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52
* `2.60`_
.. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-queens
+ * `2.61`_
+
+ .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55
+
* `2.63`_
.. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index ddae6e2..b22503b 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -1,21 +1,21 @@
Tempest Test Removal Procedure
==============================
-Historically tempest was the only way of doing functional testing and
-integration testing in OpenStack. This was mostly only an artifact of tempest
+Historically, Tempest was the only way of doing functional testing and
+integration testing in OpenStack. This was mostly only an artifact of Tempest
being the only proven pattern for doing this, not an artifact of a design
-decision. However, moving forward as functional testing is being spun up in
-each individual project we really only want tempest to be the integration test
-suite it was intended to be; testing the high level interactions between
-projects through REST API requests. In this model there are probably existing
-tests that aren't the best fit living in tempest. However, since tempest is
+decision. However, moving forward, as functional testing is being spun up in
+each individual project, we really only want Tempest to be the integration test
+suite it was intended to be: testing the high-level interactions between
+projects through REST API requests. In this model, there are probably existing
+tests that aren't the best fit living in Tempest. However, since Tempest is
largely still the only gating test suite in this space we can't carelessly rip
out everything from the tree. This document outlines the procedure which was
developed to ensure we minimize the risk for removing something of value from
-the tempest tree.
+the Tempest tree.
-This procedure might seem overly conservative and slow paced, but this is by
-design to try and ensure we don't remove something that is actually providing
+This procedure might seem overly conservative and slow-paced, but this is by
+design to try to ensure we don't remove something that is actually providing
value. Having potential duplication between testing is not a big deal
especially compared to the alternative of removing something which is actually
providing value and is actively catching bugs, or blocking incorrect patches
@@ -27,24 +27,24 @@
3 prong rule for removal
^^^^^^^^^^^^^^^^^^^^^^^^
-In the proposal etherpad we'll be looking for answers to 3 questions
+In the proposal etherpad we'll be looking for answers to 3 questions:
#. The tests proposed for removal must have equiv. coverage in a different
project's test suite (whether this is another gating test project, or an in
tree functional test suite). For API tests preferably the other project will
- have a similar source of friction in place to prevent breaking api changes
- so that we don't regress and let breaking api changes slip through the
+ have a similar source of friction in place to prevent breaking API changes
+ so that we don't regress and let breaking API changes slip through the
gate.
#. The test proposed for removal has a failure rate < 0.50% in the gate over
the past release (the value and interval will likely be adjusted in the
future)
.. _`prong #3`:
-#. There must not be an external user/consumer of tempest
+#. There must not be an external user/consumer of Tempest
that depends on the test proposed for removal
The answers to 1 and 2 are easy to verify. For 1 just provide a link to the new
-test location. If you are linking to the tempest removal patch please also put
+test location. If you are linking to the Tempest removal patch please also put
a Depends-On in the commit message for the commit which moved the test into
another repo.
@@ -91,32 +91,32 @@
#. paste the output table with numbers and the mysql command you ran to
generate it into the etherpad.
-Eventually a cli interface will be created to make that a bit more friendly.
+Eventually a CLI interface will be created to make that a bit more friendly.
Also a dashboard is in the works so we don't need to manually run the command.
The intent of the 2nd prong is to verify that moving the test into a project
-specific testing is preventing bugs (assuming the tempest tests were catching
-issues) from bubbling up a layer into tempest jobs. If we're seeing failure
+specific testing is preventing bugs (assuming the Tempest tests were catching
+issues) from bubbling up a layer into Tempest jobs. If we're seeing failure
rates above a certain threshold in the gate checks that means the functional
testing isn't really being effective in catching that bug (and therefore
-blocking it from landing) and having the testing run in tempest still has
+blocking it from landing) and having the testing run in Tempest still has
value.
However for the 3rd prong verification is a bit more subjective. The original
intent of this prong was mostly for refstack/defcore and also for things that
running on the stable branches. We don't want to remove any tests if that
-would break our api consistency checking between releases, or something that
-defcore/refstack is depending on being in tempest. It's worth pointing out
+would break our API consistency checking between releases, or something that
+defcore/refstack is depending on being in Tempest. It's worth pointing out
that if a test is used in defcore as part of interop testing then it will
-probably have continuing value being in tempest as part of the
+probably have continuing value being in Tempest as part of the
integration/integrated tests in general. This is one area where some overlap
-is expected between testing in projects and tempest, which is not a bad thing.
+is expected between testing in projects and Tempest, which is not a bad thing.
Discussing the 3rd prong
""""""""""""""""""""""""
There are 2 approaches to addressing the 3rd prong. Either it can be raised
-during a qa meeting during the tempest discussion. Please put it on the agenda
+during a qa meeting during the Tempest discussion. Please put it on the agenda
well ahead of the scheduled meeting. Since the meeting time will be well known
ahead of time anyone who depends on the tests will have ample time beforehand
to outline any concerns on the before the meeting. To give ample time for
@@ -133,17 +133,17 @@
Exceptions to this procedure
----------------------------
-For the most part all tempest test removals have to go through this procedure
+For the most part all Tempest test removals have to go through this procedure
there are a couple of exceptions though:
-#. The class of testing has been decided to be outside the scope of tempest.
+#. The class of testing has been decided to be outside the scope of Tempest.
#. A revert for a patch which added a broken test, or testing which didn't
actually run in the gate (basically any revert for something which
shouldn't have been added)
#. Tests that would become out of scope as a consequence of an API change,
as described in `API Compatibility`_.
Such tests cannot live in Tempest because of the branchless nature of
- Tempest. Such test must still honor `prong #3`_.
+ Tempest. Such tests must still honor `prong #3`_.
For the first exception type the only types of testing in tree which have been
declared out of scope at this point are:
@@ -160,8 +160,8 @@
Tempest Scope
^^^^^^^^^^^^^
-Starting in the liberty cycle tempest has defined a set of projects which
-are defined as in scope for direct testing in tempest. As of today that list
+Starting in the liberty cycle Tempest, has defined a set of projects which
+are defined as in scope for direct testing in Tempest. As of today that list
is:
* Keystone
@@ -171,18 +171,18 @@
* Neutron
* Swift
-anything that lives in tempest which doesn't test one of these projects can be
+Anything that lives in Tempest which doesn't test one of these projects can be
removed assuming there is equivalent testing elsewhere. Preferably using the
`tempest plugin mechanism`_
-to maintain continuity after migrating the tests out of tempest.
+to maintain continuity after migrating the tests out of Tempest.
.. _tempest plugin mechanism: https://docs.openstack.org/tempest/latest/plugin.html
API Compatibility
"""""""""""""""""
-If an API introduces a non-discoverable, backward incompatible change, and
-such change is not backported to all versions supported by Tempest, tests for
+If an API introduces a non-discoverable, backward-incompatible change, and
+such a change is not backported to all versions supported by Tempest, tests for
that API cannot live in Tempest anymore.
This is because tests would not be able to know or control which API response
to expect, and thus would not be able to enforce a specific behavior.
diff --git a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
index 03f64f9..57b4074 100644
--- a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
+++ b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
@@ -47,8 +47,8 @@
set -x
export PYTHONUNBUFFERED=true
export DEVSTACK_GATE_TEMPEST=1
- # Run scenario and nova migration tests with concurrency 2
- export DEVSTACK_GATE_TEMPEST_REGEX='(^tempest\.(scenario|api\.compute\.admin\.test_(live_|)migration))'
+ # Run all scenario tests including slow tests with concurrency 2
+ export DEVSTACK_GATE_TEMPEST_REGEX='(^tempest\.(scenario))'
export TEMPEST_CONCURRENCY=2
export DEVSTACK_GATE_NEUTRON=1
export DEVSTACK_GATE_TLSPROXY=1
diff --git a/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
new file mode 100644
index 0000000..d67cdb8
--- /dev/null
+++ b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ The ``update_service`` API is added to the ``services_client`` compute
+ library. This API is introduced in microversion 2.53 and supersedes
+ the following APIs:
+
+ * ``PUT /os-services/disable`` (``disable_service``)
+ * ``PUT /os-services/disable-log-reason`` (``disable_log_reason``)
+ * ``PUT /os-services/enable`` (``enable_service``)
+ * ``PUT /os-services/force-down`` (``update_forced_down``)
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
index 027af25..9f014e6 100644
--- a/tempest/api/compute/admin/test_flavors_microversions.py
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -41,3 +41,11 @@
self.flavors_client.list_flavors(detail=True)['flavors']
# Checking list API response schema
self.flavors_client.list_flavors()['flavors']
+
+
+class FlavorsV261TestJSON(FlavorsV255TestJSON):
+ min_microversion = '2.61'
+ max_microversion = 'latest'
+
+ # NOTE(gmann): This class tests the flavors APIs
+ # response schema for the 2.61 microversion.
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 201670a..993c8ec 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -13,12 +13,14 @@
# under the License.
from tempest.api.compute import base
+from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Services API. List and Enable/Disable require admin privileges."""
+ max_microversion = '2.52'
@classmethod
def setup_clients(cls):
@@ -35,7 +37,8 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d0884a69-f693-4e79-a9af-232d15643bf7')
def test_get_service_by_invalid_params(self):
- # return all services if send the request with invalid parameter
+ # Expect all services to be returned when the request contains invalid
+ # parameters.
services = self.client.list_services()['services']
services_xxx = (self.client.list_services(xxx='nova-compute')
['services'])
@@ -58,3 +61,45 @@
services = self.client.list_services(host='xxx',
binary=binary_name)['services']
self.assertEmpty(services)
+
+
+class ServicesAdminNegativeV253TestJSON(ServicesAdminNegativeTestJSON):
+ min_microversion = '2.53'
+ max_microversion = 'latest'
+
+ # NOTE(felipemonteiro): This class tests the services APIs response schema
+ # for the 2.53 microversion. Schema testing is done for `list_services`
+ # tests.
+
+ @classmethod
+ def resource_setup(cls):
+ super(ServicesAdminNegativeV253TestJSON, cls).resource_setup()
+ # Nova returns 400 if `binary` is not nova-compute.
+ cls.binary = 'nova-compute'
+ cls.fake_service_id = data_utils.rand_uuid()
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('508671aa-c929-4479-bd10-8680d40dd0a6')
+ def test_enable_service_with_invalid_service_id(self):
+ self.assertRaises(lib_exc.NotFound,
+ self.client.update_service,
+ service_id=self.fake_service_id,
+ status='enabled')
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('a9eeeade-42b3-419f-87aa-c9342aa068cf')
+ def test_disable_service_with_invalid_service_id(self):
+ self.assertRaises(lib_exc.NotFound,
+ self.client.update_service,
+ service_id=self.fake_service_id,
+ status='disabled')
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('f46a9d91-1e85-4b96-8e7a-db7706fa2e9a')
+ def test_disable_log_reason_with_invalid_service_id(self):
+ # disabled_reason requires that status='disabled' be provided.
+ self.assertRaises(lib_exc.NotFound,
+ self.client.update_service,
+ service_id=self.fake_service_id,
+ status='disabled',
+ disabled_reason='maintenance')
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index f9050a8..81635ca 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -84,6 +84,6 @@
@decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91')
def test_create_keypair_invalid_name(self):
# Keypairs with name being an invalid name should not be created
- k_name = 'key_/.\@:'
+ k_name = r'key_/.\@:'
self.assertRaises(lib_exc.BadRequest, self.create_keypair,
k_name)
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 14aecfd..3dffd01 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -227,7 +227,7 @@
@decorators.idempotent_id('24a89b0c-0d55-4a28-847f-45075f19b27b')
def test_list_servers_filtered_by_name_regex(self):
# list of regex that should match s1, s2 and s3
- regexes = ['^.*\-instance\-[0-9]+$', '^.*\-instance\-.*$']
+ regexes = [r'^.*\-instance\-[0-9]+$', r'^.*\-instance\-.*$']
for regex in regexes:
params = {'name': regex}
body = self.client.list_servers(**params)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 9fc5af0..350e8ba 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -369,6 +369,42 @@
server = self.client.show_server(self.server_id)['server']
self.assertEqual(self.flavor_ref, server['flavor']['id'])
+ @decorators.idempotent_id('fbbf075f-a812-4022-bc5c-ccb8047eef12')
+ @decorators.related_bug('1737599')
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @utils.services('volume')
+ def test_resize_server_revert_with_volume_attached(self):
+ # Tests attaching a volume to a server instance and then resizing
+ # the instance. Once the instance is resized, revert the resize which
+ # should move the instance and volume attachment back to the original
+ # compute host.
+
+ # Create a blank volume and attach it to the server created in setUp.
+ volume = self.create_volume()
+ server = self.client.show_server(self.server_id)['server']
+ self.attach_volume(server, volume)
+ # Now resize the server with the blank volume attached.
+ self.client.resize_server(self.server_id, self.flavor_ref_alt)
+ # Explicitly delete the server to get a new one for later
+ # tests. Avoids resize down race issues.
+ self.addCleanup(self.delete_server, self.server_id)
+ waiters.wait_for_server_status(
+ self.client, self.server_id, 'VERIFY_RESIZE')
+ # Now revert the resize which should move the instance and it's volume
+ # attachment back to the original source compute host.
+ self.client.revert_resize_server(self.server_id)
+ waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+ # Make sure everything still looks OK.
+ server = self.client.show_server(self.server_id)['server']
+ # The flavor id is not returned in the server response after
+ # microversion 2.46 so handle that gracefully.
+ if server['flavor'].get('id'):
+ self.assertEqual(self.flavor_ref, server['flavor']['id'])
+ attached_volumes = server['os-extended-volumes:volumes_attached']
+ self.assertEqual(1, len(attached_volumes))
+ self.assertEqual(volume['id'], attached_volumes[0]['id'])
+
@decorators.idempotent_id('b963d4f1-94b3-4c40-9e97-7b583f46e470')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
'Snapshotting not available, backup not possible.')
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 97a1f36..72b6be4 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -121,11 +121,7 @@
# Create a domain with a user and a group in it
domain = self.setup_test_domain()
user = self.create_test_user(domain_id=domain['id'])
- group = self.groups_client.create_group(
- name=data_utils.rand_name('group'),
- domain_id=domain['id'])['group']
- self.addCleanup(test_utils.call_and_ignore_notfound_exc,
- self.groups_client.delete_group, group['id'])
+ group = self.setup_test_group(domain_id=domain['id'])
# Delete the domain
self.delete_domain(domain['id'])
# Check the domain, its users and groups are gone
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 507810b..37ce266 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -30,50 +30,46 @@
@decorators.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129')
def test_group_create_update_get(self):
+ # Verify group creation works.
name = data_utils.rand_name('Group')
description = data_utils.rand_name('Description')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'],
- description=description)['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
+ group = self.setup_test_group(name=name, domain_id=self.domain['id'],
+ description=description)
self.assertEqual(group['name'], name)
self.assertEqual(group['description'], description)
+ self.assertEqual(self.domain['id'], group['domain_id'])
- new_name = data_utils.rand_name('UpdateGroup')
- new_desc = data_utils.rand_name('UpdateDescription')
+ # Verify updating name and description works.
+ first_name_update = data_utils.rand_name('UpdateGroup')
+ first_desc_update = data_utils.rand_name('UpdateDescription')
updated_group = self.groups_client.update_group(
- group['id'], name=new_name, description=new_desc)['group']
- self.assertEqual(updated_group['name'], new_name)
- self.assertEqual(updated_group['description'], new_desc)
+ group['id'], name=first_name_update,
+ description=first_desc_update)['group']
+ self.assertEqual(updated_group['name'], first_name_update)
+ self.assertEqual(updated_group['description'], first_desc_update)
+ # Verify that the updated values are reflected after performing show.
new_group = self.groups_client.show_group(group['id'])['group']
self.assertEqual(group['id'], new_group['id'])
- self.assertEqual(new_name, new_group['name'])
- self.assertEqual(new_desc, new_group['description'])
+ self.assertEqual(first_name_update, new_group['name'])
+ self.assertEqual(first_desc_update, new_group['description'])
- @decorators.idempotent_id('b66eb441-b08a-4a6d-81ab-fef71baeb26c')
- def test_group_update_with_few_fields(self):
- name = data_utils.rand_name('Group')
- old_description = data_utils.rand_name('Description')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'],
- description=old_description)['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
-
- new_name = data_utils.rand_name('UpdateGroup')
+ # Verify that updating a single field for a group (name) leaves the
+ # other fields (description, domain_id) unchanged.
+ second_name_update = data_utils.rand_name(
+ self.__class__.__name__ + 'UpdateGroup')
updated_group = self.groups_client.update_group(
- group['id'], name=new_name)['group']
- self.assertEqual(new_name, updated_group['name'])
- # Verify that 'description' is not being updated or deleted.
- self.assertEqual(old_description, updated_group['description'])
+ group['id'], name=second_name_update)['group']
+ self.assertEqual(second_name_update, updated_group['name'])
+ # Verify that 'description' and 'domain_id' were not updated or
+ # deleted.
+ self.assertEqual(first_desc_update, updated_group['description'])
+ self.assertEqual(self.domain['id'], updated_group['domain_id'])
@decorators.attr(type='smoke')
@decorators.idempotent_id('1598521a-2f36-4606-8df9-30772bd51339')
def test_group_users_add_list_delete(self):
- name = data_utils.rand_name('Group')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'])['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
+ group = self.setup_test_group(domain_id=self.domain['id'])
# add user into group
users = []
for _ in range(3):
@@ -100,11 +96,8 @@
# create two groups, and add user into them
groups = []
for _ in range(2):
- name = data_utils.rand_name('Group')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'])['group']
+ group = self.setup_test_group(domain_id=self.domain['id'])
groups.append(group)
- self.addCleanup(self.groups_client.delete_group, group['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
user_groups = self.users_client.list_user_groups(user['id'])['groups']
@@ -118,12 +111,7 @@
group_ids = list()
fetched_ids = list()
for _ in range(3):
- name = data_utils.rand_name('Group')
- description = data_utils.rand_name('Description')
- group = self.groups_client.create_group(
- name=name, domain_id=self.domain['id'],
- description=description)['group']
- self.addCleanup(self.groups_client.delete_group, group['id'])
+ group = self.setup_test_group(domain_id=self.domain['id'])
group_ids.append(group['id'])
# List and Verify Groups
# When domain specific drivers are enabled the operations
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 0845407..8ae43d6 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -19,7 +19,6 @@
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
CONF = config.CONF
@@ -28,30 +27,6 @@
credentials = ['primary', 'admin', 'alt']
- @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
- def test_tokens(self):
- # Valid user's token is authenticated
- # Create a User
- u_name = data_utils.rand_name('user')
- u_desc = '%s-description' % u_name
- u_password = data_utils.rand_password()
- user = self.create_test_user(
- name=u_name, description=u_desc, password=u_password)
- # Perform Authentication
- resp = self.token.auth(user_id=user['id'],
- password=u_password).response
- subject_token = resp['x-subject-token']
- self.client.check_token_existence(subject_token)
- # Perform GET Token
- token_details = self.client.show_token(subject_token)['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
- self.client.delete_token(subject_token)
- self.assertRaises(lib_exc.NotFound, self.client.check_token_existence,
- subject_token)
-
@decorators.idempotent_id('565fa210-1da1-4563-999b-f7b5b67cf112')
def test_rescope_token(self):
"""Rescope a token.
@@ -201,10 +176,7 @@
role_id = self.setup_test_role()['id']
# Create a group.
- group_name = data_utils.rand_name('Group')
- group_id = self.groups_client.create_group(
- name=group_name, domain_id=domain_id)['group']['id']
- self.addCleanup(self.groups_client.delete_group, group_id)
+ group_id = self.setup_test_group(domain_id=domain_id)['id']
# Add the alt user to the group.
self.groups_client.add_group_user(group_id, alt_user_id)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 68f2c07..282343c 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -292,6 +292,20 @@
self.delete_domain, domain['id'])
return domain
+ def setup_test_group(self, **kwargs):
+ """Set up a test group."""
+ if 'name' not in kwargs:
+ kwargs['name'] = data_utils.rand_name(
+ self.__class__.__name__ + '_test_project')
+ if 'description' not in kwargs:
+ kwargs['description'] = data_utils.rand_name(
+ self.__class__.__name__ + '_test_description')
+ group = self.groups_client.create_group(**kwargs)['group']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.groups_client.delete_group, group['id'])
+ return group
+
class BaseApplicationCredentialsV3Test(BaseIdentityV3Test):
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index 4c72d82..f13aa10 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -91,3 +91,28 @@
self.assertIsNotNone(subject_name, 'Expected user name in token.')
self.assertEqual(resp['methods'][0], 'password')
+
+ @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
+ def test_token_auth_creation_existence_deletion(self):
+ # Tests basic token auth functionality in a way that is compatible with
+ # pre-provisioned credentials. The default user is used for token
+ # authentication.
+
+ # Valid user's token is authenticated
+ user = self.os_primary.credentials
+ # Perform Authentication
+ resp = self.non_admin_token.auth(
+ user_id=user.user_id, password=user.password).response
+ subject_token = resp['x-subject-token']
+ self.non_admin_client.check_token_existence(subject_token)
+ # Perform GET Token
+ token_details = self.non_admin_client.show_token(
+ subject_token)['token']
+ self.assertEqual(resp['x-subject-token'], subject_token)
+ self.assertEqual(token_details['user']['id'], user.user_id)
+ self.assertEqual(token_details['user']['name'], user.username)
+ # Perform Delete Token
+ self.non_admin_client.delete_token(subject_token)
+ self.assertRaises(lib_exc.NotFound,
+ self.non_admin_client.check_token_existence,
+ subject_token)
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 6849653..e79f8c3 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -59,7 +59,7 @@
# Try to create a third network while the quota is two
with self.assertRaisesRegex(
lib_exc.Conflict,
- "Quota exceeded for resources: \['network'\].*"):
+ r"Quota exceeded for resources: \['network'\].*"):
n3 = self.networks_client.create_network()
self.addCleanup(self.networks_client.delete_network,
n3['network']['id'])
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 20c3538..9907497 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -62,8 +62,16 @@
return message_id
@decorators.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
- def test_list_messages(self):
- self._create_user_message()
+ def test_list_show_messages(self):
+ message_id = self._create_user_message()
+ self.addCleanup(self.messages_client.delete_message, message_id)
+
+ # show message
+ message = self.messages_client.show_message(message_id)['message']
+ for key in MESSAGE_KEYS:
+ self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
+
+ # list messages
messages = self.messages_client.list_messages()['messages']
self.assertIsInstance(messages, list)
for message in messages:
@@ -71,16 +79,6 @@
self.assertIn(key, message.keys(),
'Missing expected key %s' % key)
- @decorators.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
- def test_show_message(self):
- message_id = self._create_user_message()
- self.addCleanup(self.messages_client.delete_message, message_id)
-
- message = self.messages_client.show_message(message_id)['message']
-
- for key in MESSAGE_KEYS:
- self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
-
@decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
def test_delete_message(self):
message_id = self._create_user_message()
diff --git a/tempest/api/volume/admin/test_volume_retype_with_migration.py b/tempest/api/volume/admin/test_volume_retype.py
similarity index 67%
rename from tempest/api/volume/admin/test_volume_retype_with_migration.py
rename to tempest/api/volume/admin/test_volume_retype.py
index 025c1be..1c56eb2 100644
--- a/tempest/api/volume/admin/test_volume_retype_with_migration.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import abc
from oslo_log import log as logging
@@ -23,31 +24,7 @@
LOG = logging.getLogger(__name__)
-class VolumeRetypeWithMigrationTest(base.BaseVolumeAdminTest):
-
- @classmethod
- def skip_checks(cls):
- super(VolumeRetypeWithMigrationTest, cls).skip_checks()
-
- if not CONF.volume_feature_enabled.multi_backend:
- raise cls.skipException("Cinder multi-backend feature disabled.")
-
- if len(set(CONF.volume.backend_names)) < 2:
- raise cls.skipException("Requires at least two different "
- "backend names")
-
- @classmethod
- def resource_setup(cls):
- super(VolumeRetypeWithMigrationTest, cls).resource_setup()
- # read backend name from a list.
- backend_src = CONF.volume.backend_names[0]
- backend_dst = CONF.volume.backend_names[1]
-
- extra_specs_src = {"volume_backend_name": backend_src}
- extra_specs_dst = {"volume_backend_name": backend_dst}
-
- cls.src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src)
- cls.dst_vol_type = cls.create_volume_type(extra_specs=extra_specs_dst)
+class VolumeRetypeTest(base.BaseVolumeAdminTest):
def _wait_for_internal_volume_cleanup(self, vol):
# When retyping a volume, Cinder creates an internal volume in the
@@ -70,43 +47,11 @@
fetched_vol['id'])
break
- def _retype_volume(self, volume):
- keys_with_no_change = ('id', 'size', 'description', 'name', 'user_id',
- 'os-vol-tenant-attr:tenant_id')
- keys_with_change = ('volume_type', 'os-vol-host-attr:host')
+ @abc.abstractmethod
+ def _verify_migration(self, source_vol, dest_vol):
+ pass
- volume_source = self.admin_volume_client.show_volume(
- volume['id'])['volume']
-
- self.volumes_client.retype_volume(
- volume['id'],
- new_type=self.dst_vol_type['name'],
- migration_policy='on-demand')
- self.addCleanup(self._wait_for_internal_volume_cleanup, volume)
- waiters.wait_for_volume_retype(self.volumes_client, volume['id'],
- self.dst_vol_type['name'])
-
- volume_dest = self.admin_volume_client.show_volume(
- volume['id'])['volume']
-
- # Check the volume information after the migration.
- self.assertEqual('success',
- volume_dest['os-vol-mig-status-attr:migstat'])
- self.assertEqual('success', volume_dest['migration_status'])
-
- for key in keys_with_no_change:
- self.assertEqual(volume_source[key], volume_dest[key])
-
- for key in keys_with_change:
- self.assertNotEqual(volume_source[key], volume_dest[key])
-
- @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
- def test_available_volume_retype_with_migration(self):
- src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
- self._retype_volume(src_vol)
-
- @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
- def test_volume_from_snapshot_retype_with_migration(self):
+ def _create_volume_from_snapshot(self):
# Create a volume in the first backend
src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
@@ -121,5 +66,115 @@
self.snapshots_client.delete_snapshot(snapshot['id'])
self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+ return src_vol
+
+ def _retype_volume(self, volume, migration_policy):
+
+ volume_source = self.admin_volume_client.show_volume(
+ volume['id'])['volume']
+
+ self.volumes_client.retype_volume(
+ volume['id'],
+ new_type=self.dst_vol_type['name'],
+ migration_policy=migration_policy)
+ self.addCleanup(self._wait_for_internal_volume_cleanup, volume)
+ waiters.wait_for_volume_retype(self.volumes_client, volume['id'],
+ self.dst_vol_type['name'])
+
+ volume_dest = self.admin_volume_client.show_volume(
+ volume['id'])['volume']
+
+ self._verify_migration(volume_source, volume_dest)
+
+
+class VolumeRetypeWithMigrationTest(VolumeRetypeTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumeRetypeTest, cls).skip_checks()
+
+ if not CONF.volume_feature_enabled.multi_backend:
+ raise cls.skipException("Cinder multi-backend feature disabled.")
+
+ if len(set(CONF.volume.backend_names)) < 2:
+ raise cls.skipException("Requires at least two different "
+ "backend names")
+
+ @classmethod
+ def resource_setup(cls):
+ super(VolumeRetypeWithMigrationTest, cls).resource_setup()
+ # read backend name from a list.
+ backend_src = CONF.volume.backend_names[0]
+ backend_dst = CONF.volume.backend_names[1]
+
+ extra_specs_src = {"volume_backend_name": backend_src}
+ extra_specs_dst = {"volume_backend_name": backend_dst}
+
+ cls.src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src)
+ cls.dst_vol_type = cls.create_volume_type(extra_specs=extra_specs_dst)
+
+ def _verify_migration(self, volume_source, volume_dest):
+
+ keys_with_no_change = ('id', 'size', 'description', 'name',
+ 'user_id', 'os-vol-tenant-attr:tenant_id')
+ keys_with_change = ('volume_type', 'os-vol-host-attr:host')
+
+ # Check the volume information after the migration.
+ self.assertEqual('success',
+ volume_dest['os-vol-mig-status-attr:migstat'])
+ self.assertEqual('success', volume_dest['migration_status'])
+
+ for key in keys_with_no_change:
+ self.assertEqual(volume_source[key], volume_dest[key])
+
+ for key in keys_with_change:
+ self.assertNotEqual(volume_source[key], volume_dest[key])
+
+ self.assertEqual(volume_dest['volume_type'], self.dst_vol_type['name'])
+
+ @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
+ def test_available_volume_retype_with_migration(self):
+ src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+ self._retype_volume(src_vol, migration_policy='on-demand')
+
+ @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
+ def test_volume_from_snapshot_retype_with_migration(self):
+ src_vol = self._create_volume_from_snapshot()
+
# Migrate the volume from snapshot to the second backend
- self._retype_volume(src_vol)
+ self._retype_volume(src_vol, migration_policy='on-demand')
+
+
+class VolumeRetypeWithoutMigrationTest(VolumeRetypeTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(VolumeRetypeWithoutMigrationTest, cls).resource_setup()
+ cls.src_vol_type = cls.create_volume_type('volume-type-1')
+ cls.dst_vol_type = cls.create_volume_type('volume-type-2')
+
+ def _verify_migration(self, volume_source, volume_dest):
+
+ keys_with_no_change = ('id', 'size', 'description', 'name',
+ 'user_id', 'os-vol-tenant-attr:tenant_id',
+ 'os-vol-host-attr:host')
+ keys_with_change = ('volume_type',)
+
+ # Check the volume information after the retype
+ self.assertIsNone(volume_dest['os-vol-mig-status-attr:migstat'])
+ self.assertIsNone(volume_dest['migration_status'])
+
+ for key in keys_with_no_change:
+ self.assertEqual(volume_source[key], volume_dest[key])
+
+ for key in keys_with_change:
+ self.assertNotEqual(volume_source[key], volume_dest[key])
+
+ self.assertEqual(volume_dest['volume_type'], self.dst_vol_type['name'])
+
+ @decorators.idempotent_id('b90412ee-465d-46e9-b249-ec84a47d5f25')
+ def test_available_volume_retype(self):
+ src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+
+ # Retype the volume from snapshot
+ self._retype_volume(src_vol, migration_policy='never')
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 1c671ec..9be8ee2 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -311,5 +311,6 @@
resources.extend(generate_resources(cred_provider, opts.admin))
dump_accounts(resources, opts.identity_version, opts.accounts)
+
if __name__ == "__main__":
main()
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 84c8631..d84f3a3 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -26,7 +26,7 @@
LOG = logging.getLogger(__name__)
-STESTR_CONF = """[DEFAULT]
+STESTR_CONF = r"""[DEFAULT]
test_path=%s
top_dir=%s
group_regex=([^\.]*\.)*
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index a4402fe..8dcf575 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -95,7 +95,7 @@
ip_re = re.compile(r'(^|[^0-9])[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]'
'{1,3}([^0-9]|$)')
url_re = re.compile(r'.*INFO.*Request \((?P<name>.*)\): (?P<code>[\d]{3}) '
- '(?P<verb>\w*) (?P<url>.*) .*')
+ r'(?P<verb>\w*) (?P<url>.*) .*')
port_re = re.compile(r'.*:(?P<port>\d+).*')
path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 15af271..aa333b3 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -488,5 +488,6 @@
traceback.print_exc()
raise
+
if __name__ == "__main__":
main()
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 75db155..c6e5dcb 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -210,6 +210,7 @@
except exceptions.InvalidConfiguration:
return False
+
# === Credentials
# Type of credentials available from configuration
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index ed11b21..c702d88 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -225,9 +225,9 @@
elif key in ('content-type', 'date', 'last-modified',
'x-copied-from-last-modified') and not value:
return InvalidFormat(key, value)
- elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value):
+ elif key == 'x-timestamp' and not re.match(r"^\d+\.?\d*\Z", value):
return InvalidFormat(key, value)
- elif key == 'x-copied-from' and not re.match("\S+/\S+", value):
+ elif key == 'x-copied-from' and not re.match(r"\S+/\S+", value):
return InvalidFormat(key, value)
elif key == 'x-trans-id' and \
not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value):
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 225a713..167bf5b 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -40,6 +40,7 @@
self.__dict__[attr] = attr_obj
return attr_obj
+
data_utils = DataUtils()
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index b6e7f8c..a57a360 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -287,10 +287,10 @@
if pep8.noqa(physical_line):
return
- if not re.match('class .*Test.*\(.*Admin.*\):', logical_line):
+ if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
return
- if not re.match('.\/tempest\/api\/.*\/admin\/.*', filename):
+ if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
msg = 'T115: All admin tests should exist under admin path.'
yield(0, msg)
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
index af5e67f..43e80cc 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -82,10 +82,6 @@
}
}
-unset_flavor_extra_specs = {
- 'status_code': [200]
-}
-
create_update_get_flavor_details = {
'status_code': [200],
'response_body': {
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
index a438d48..3aa1eda 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -20,7 +20,7 @@
'extra_specs': {
'type': 'object',
'patternProperties': {
- '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
}
}
},
@@ -29,12 +29,16 @@
}
}
+unset_flavor_extra_specs = {
+ 'status_code': [200]
+}
+
set_get_flavor_extra_specs_key = {
'status_code': [200],
'response_body': {
'type': 'object',
'patternProperties': {
- '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
}
}
}
diff --git a/tempest/lib/api_schema/response/compute/v2_11/services.py b/tempest/lib/api_schema/response/compute/v2_11/services.py
index 18b833b..9ece1f9 100644
--- a/tempest/lib/api_schema/response/compute/v2_11/services.py
+++ b/tempest/lib/api_schema/response/compute/v2_11/services.py
@@ -44,3 +44,10 @@
'required': ['service']
}
}
+
+# **** Schemas unchanged in microversion 2.11 since microversion 2.1 ****
+# Note(felipemonteiro): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+enable_disable_service = copy.deepcopy(services.enable_disable_service)
+disable_log_reason = copy.deepcopy(services.disable_log_reason)
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 935be70..5d6d4c3 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -26,7 +26,7 @@
'extra_specs': {
'type': 'object',
'patternProperties': {
- '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
}
}
},
diff --git a/tempest/lib/api_schema/response/compute/v2_53/__init__.py b/tempest/lib/api_schema/response/compute/v2_53/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_53/services.py b/tempest/lib/api_schema/response/compute/v2_53/services.py
new file mode 100644
index 0000000..aa132a9
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/services.py
@@ -0,0 +1,70 @@
+# Copyright 2018 AT&T 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.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_11 import services \
+ as servicesv211
+
+# ***************** Schemas changed in microversion 2.53 *****************
+
+# NOTE(felipemonteiro): This is schema for microversion 2.53 which includes:
+#
+# * changing the service 'id' to 'string' type only
+# * adding update_service which supersedes enable_service, disable_service,
+# disable_log_reason, update_forced_down.
+
+list_services = copy.deepcopy(servicesv211.list_services)
+# The ID of the service is a uuid, so v2.1 pattern does not apply.
+list_services['response_body']['properties']['services']['items'][
+ 'properties']['id'] = {'type': 'string', 'format': 'uuid'}
+
+update_service = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'binary': {'type': 'string'},
+ 'disabled_reason': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated_at': parameter_types.date_time,
+ 'zone': {'type': 'string'},
+ 'forced_down': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'binary', 'disabled_reason', 'host',
+ 'state', 'status', 'updated_at', 'zone',
+ 'forced_down']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['service']
+ }
+}
+
+# **** Schemas unchanged in microversion 2.53 since microversion 2.11 ****
+# Note(felipemonteiro): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+enable_disable_service = copy.deepcopy(servicesv211.enable_disable_service)
+update_forced_down = copy.deepcopy(servicesv211.update_forced_down)
+disable_log_reason = copy.deepcopy(servicesv211.disable_log_reason)
diff --git a/tempest/lib/api_schema/response/compute/v2_61/__init__.py b/tempest/lib/api_schema/response/compute/v2_61/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_61/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_61/flavors.py b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
new file mode 100644
index 0000000..381fb64
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
@@ -0,0 +1,102 @@
+# Copyright 2018 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 copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_55 import flavors \
+ as flavorsv255
+
+# ****** Schemas changed in microversion 2.61 *****************
+
+# Note(gmann): This is schema for microversion 2.61 which includes the
+# Flavor extra_specs in the Response body of the following APIs:
+# - ``PUT /flavors/{flavor_id}``
+# - ``GET /flavors/detail``
+# - ``GET /flavors/{flavor_id}``
+# - ``POST /flavors``
+
+flavor_description = {
+ 'type': ['string', 'null'],
+ 'minLength': 0, 'maxLength': 65535
+}
+
+flavor_extra_specs = {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9-_:. ]{1,255}$': {'type': 'string'}
+ }
+}
+
+common_flavor_info = {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'ram': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ # 'swap' attributes comes as integer value but if it is empty
+ # it comes as "". So defining type of as string and integer.
+ 'swap': {'type': ['integer', 'string']},
+ 'disk': {'type': 'integer'},
+ 'id': {'type': 'string'},
+ 'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+ 'os-flavor-access:is_public': {'type': 'boolean'},
+ 'rxtx_factor': {'type': 'number'},
+ 'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'},
+ 'description': flavor_description,
+ 'extra_specs': flavor_extra_specs
+ },
+ 'additionalProperties': False,
+ # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+ # 'OS-FLV-EXT-DATA' are API extensions. so they are not 'required'.
+ 'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id',
+ 'description']
+}
+
+list_flavors_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': common_flavor_info
+ },
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always so it is not 'required'.
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['flavors']
+ }
+}
+
+create_update_get_flavor_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor': common_flavor_info
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor']
+ }
+}
+
+# ****** Schemas unchanged in microversion 2.61 since microversion 2.55 ***
+# Note(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+list_flavors = copy.deepcopy(flavorsv255.list_flavors)
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
index a7d5e49..45d41c7 100644
--- a/tempest/lib/cli/output_parser.py
+++ b/tempest/lib/cli/output_parser.py
@@ -25,7 +25,7 @@
LOG = logging.getLogger(__name__)
-delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+delimiter_line = re.compile(r'^\+\-[\+\-]+\-\+$')
def details_multiple(output_lines, with_label=False):
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index d1f0888..82fcd0b 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -358,5 +358,6 @@
"Run 'tox -v -e uuidgen' to automatically fix tests with\n"
"missing @decorators.idempotent_id decorators.")
+
if __name__ == '__main__':
run()
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 4923d7e..2fad0a4 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -23,6 +23,8 @@
as schema_extra_specs
from tempest.lib.api_schema.response.compute.v2_55 import flavors \
as schemav255
+from tempest.lib.api_schema.response.compute.v2_61 import flavors \
+ as schemav261
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -31,7 +33,8 @@
schema_versions_info = [
{'min': None, 'max': '2.54', 'schema': schema},
- {'min': '2.55', 'max': None, 'schema': schemav255}]
+ {'min': '2.55', 'max': '2.60', 'schema': schemav255},
+ {'min': '2.61', 'max': None, 'schema': schemav261}]
def list_flavors(self, detail=False, **params):
"""Lists flavors.
@@ -202,7 +205,8 @@
"""
resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
(flavor_id, key))
- self.validate_response(schema.unset_flavor_extra_specs, resp, body)
+ self.validate_response(schema_extra_specs.unset_flavor_extra_specs,
+ resp, body)
return rest_client.ResponseBody(resp, body)
def list_flavor_access(self, flavor_id):
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index b046c35..d52de3a 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -20,6 +20,8 @@
from tempest.lib.api_schema.response.compute.v2_1 import services as schema
from tempest.lib.api_schema.response.compute.v2_11 import services \
as schemav211
+from tempest.lib.api_schema.response.compute.v2_53 import services \
+ as schemav253
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -28,7 +30,8 @@
schema_versions_info = [
{'min': None, 'max': '2.10', 'schema': schema},
- {'min': '2.11', 'max': None, 'schema': schemav211}]
+ {'min': '2.11', 'max': '2.52', 'schema': schemav211},
+ {'min': '2.53', 'max': None, 'schema': schemav253}]
def list_services(self, **params):
"""Lists all running Compute services for a tenant.
@@ -47,9 +50,30 @@
self.validate_response(_schema.list_services, resp, body)
return rest_client.ResponseBody(resp, body)
+ def update_service(self, service_id, **kwargs):
+ """Update a compute service.
+
+ Update a compute service to enable or disable scheduling, including
+ recording a reason why a compute service was disabled from scheduling.
+
+ This API is available starting with microversion 2.53.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#update-compute-service
+ """
+ put_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/%s' % service_id, put_body)
+ body = json.loads(body)
+ _schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(_schema.update_service, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
def enable_service(self, **kwargs):
"""Enable service on a host.
+ ``update_service`` supersedes this API starting with microversion 2.53.
+
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#enable-scheduling-for-a-compute-service
@@ -63,6 +87,8 @@
def disable_service(self, **kwargs):
"""Disable service on a host.
+ ``update_service`` supersedes this API starting with microversion 2.53.
+
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service
@@ -76,6 +102,8 @@
def disable_log_reason(self, **kwargs):
"""Disables scheduling for a Compute service and logs reason.
+ ``update_service`` supersedes this API starting with microversion 2.53.
+
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason
@@ -89,6 +117,8 @@
def update_forced_down(self, **kwargs):
"""Set or unset ``forced_down`` flag for the service.
+ ``update_service`` supersedes this API starting with microversion 2.53.
+
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#update-forced-down
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
index a0f832e..5068121 100644
--- a/tempest/lib/services/network/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -87,9 +87,11 @@
return self.delete_resource(uri)
def add_dhcp_agent_to_network(self, agent_id, **kwargs):
- # TODO(piyush): Current api-site doesn't contain this API description.
- # After fixing the api-site, we need to fix here also for putting the
- # link to api-site.
- # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
+ """Schedule a network to a DHCP agent.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/network/v2/#schedule-a-network-to-a-dhcp-agent
+ """
uri = '/agents/%s/dhcp-networks' % agent_id
return self.create_resource(uri, kwargs, expect_empty_body=True)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index f79bcd8..08e6c94 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -114,12 +114,12 @@
return rest_client.ResponseBody(resp, body)
def update_snapshot_status(self, snapshot_id, **kwargs):
- """Update the specified snapshot's status."""
- # TODO(gmann): api-site doesn't contain doc ref
- # for this API. After fixing the api-site, we need to
- # add the link here.
- # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645
+ """Update status of a snapshot.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#update-status-of-a-snapshot
+ """
post_body = json.dumps({'os-update_snapshot_status': kwargs})
url = 'snapshots/%s/action' % snapshot_id
resp, body = self.post(url, post_body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 9965fe5..145dcf1 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -443,7 +443,9 @@
disk_format=img_disk_format,
properties=img_properties)
except IOError:
- LOG.debug("A qcow2 image was not found. Try to get a uec image.")
+ LOG.warning(
+ "A(n) %s image was not found. Retrying with uec image.",
+ img_disk_format)
kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk}
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 739357b..1f0080f 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -77,7 +77,7 @@
def test_write_to_console_special_chars(self):
self._test_write_to_console_helper(
- '\`',
+ r'\`',
'sudo sh -c "echo \\"\\\\\\`\\" >/dev/console"')
self.conn.write_to_console('$')
self._assert_exec_called_with(
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 4a2fff4..be54130 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -59,6 +59,7 @@
self._set_attrs()
self.lock_path = cfg.CONF.oslo_concurrency.lock_path
+
fake_service1_group = cfg.OptGroup(name='fake-service1', title='Fake service1')
FakeService1Group = [
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index b8385b2..a0267d0 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -88,7 +88,7 @@
def test_rand_url(self):
actual = data_utils.rand_url()
self.assertIsInstance(actual, str)
- self.assertRegex(actual, "^https://url-[0-9]*\.com$")
+ self.assertRegex(actual, r"^https://url-[0-9]*\.com$")
actual2 = data_utils.rand_url()
self.assertNotEqual(actual, actual2)
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index 2dd981c..ba432e3 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -56,6 +56,20 @@
}
}
+ FAKE_UPDATE_SERVICE = {
+ "service": {
+ "id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
+ "binary": "nova-compute",
+ "disabled_reason": "test2",
+ "host": "host1",
+ "state": "down",
+ "status": "disabled",
+ "updated_at": "2012-10-29T13:42:05.000000",
+ "forced_down": False,
+ "zone": "nova"
+ }
+ }
+
def setUp(self):
super(TestServicesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -119,6 +133,28 @@
binary="controller",
disabled_reason='test reason')
+ def _test_update_service(self, bytes_body=False, status=None,
+ disabled_reason=None, forced_down=None):
+ resp_body = copy.deepcopy(self.FAKE_UPDATE_SERVICE)
+ kwargs = {}
+
+ if status is not None:
+ kwargs['status'] = status
+ if disabled_reason is not None:
+ kwargs['disabled_reason'] = disabled_reason
+ if forced_down is not None:
+ kwargs['forced_down'] = forced_down
+
+ resp_body['service'].update(kwargs)
+
+ self.check_service_client_function(
+ self.client.update_service,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ resp_body,
+ bytes_body,
+ service_id=resp_body['service']['id'],
+ **kwargs)
+
def test_log_reason_disabled_service_with_str_body(self):
self._test_log_reason_disabled_service()
@@ -144,3 +180,36 @@
new_callable=mock.PropertyMock(return_value='2.11'))
def test_update_forced_down_with_bytes_body(self, _):
self._test_update_forced_down(bytes_body=True)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.53'))
+ def test_update_service_disable_scheduling_with_str_body(self, _):
+ self._test_update_service(status='disabled',
+ disabled_reason='maintenance')
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.53'))
+ def test_update_service_disable_scheduling_with_bytes_body(self, _):
+ self._test_update_service(status='disabled',
+ disabled_reason='maintenance',
+ bytes_body=True)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.53'))
+ def test_update_service_enable_scheduling_with_str_body(self, _):
+ self._test_update_service(status='enabled')
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.53'))
+ def test_update_service_enable_scheduling_with_bytes_body(self, _):
+ self._test_update_service(status='enabled', bytes_body=True)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.53'))
+ def test_update_service_forced_down_with_str_body(self, _):
+ self._test_update_service(forced_down=True)
+
+ @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+ new_callable=mock.PropertyMock(return_value='2.53'))
+ def test_update_service_forced_down_with_bytes_body(self, _):
+ self._test_update_service(forced_down=True, bytes_body=True)
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
index 4af7463..1cc9c9a 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -34,7 +34,7 @@
"error on import %s" % ids)
ids = six.text_type(ids).split('\n')
for test_id in ids:
- if re.match('(\w+\.){3}\w+', test_id):
+ if re.match(r'(\w+\.){3}\w+', test_id):
if not test_id.startswith('tempest.'):
parts = test_id.partition('tempest')
fail_id = parts[1] + parts[2]
diff --git a/tools/check_logs.py b/tools/check_logs.py
index b80ccc0..de7e41d 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -96,7 +96,7 @@
def collect_url_logs(url):
page = urlreq.urlopen(url)
content = page.read()
- logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+ logs = re.findall(r'(screen-[\w-]+\.txt\.gz)</a>', content)
return logs
@@ -162,6 +162,7 @@
print("ok")
return 0
+
usage = """
Find non-white-listed log errors in log files from a devstack-gate run.
Log files will be searched for ERROR or CRITICAL messages. If any
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index bbb9019..4eb78fb 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -63,12 +63,13 @@
except HTTPError as err:
if err.code == 404:
return False
- p = re.compile('^tempest\.test_plugins', re.M)
+ p = re.compile(r'^tempest\.test_plugins', re.M)
if p.findall(r.read().decode('utf-8')):
return True
else:
False
+
r = urllib.urlopen(url)
# Gerrit prepends 4 garbage octets to the JSON, in order to counter
# cross-site scripting attacks. Therefore we must discard it so the
diff --git a/tox.ini b/tox.ini
index da0233a..de4f1b7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,7 +19,7 @@
OS_STDOUT_CAPTURE=1
OS_STDERR_CAPTURE=1
OS_TEST_TIMEOUT=160
- PYTHONWARNINGS=default::DeprecationWarning
+ PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
usedevelop = True
install_command = pip install {opts} {packages}