Merge "Add negative test for live migration"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index b9e22b5..23f732e 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -55,7 +55,7 @@
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
- 'oslosphinx',
+ 'openstackdocstheme',
'oslo_config.sphinxconfiggen',
]
@@ -64,6 +64,14 @@
todo_include_todos = True
+# openstackdocstheme options
+repository_name = 'openstack/tempest'
+bug_project = 'tempest'
+bug_tag = ''
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -127,7 +135,7 @@
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-html_theme_path = [openstackdocstheme.get_html_theme_path()]
+#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@@ -152,13 +160,6 @@
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
- "-n1"]
-try:
- html_last_updated_fmt = subprocess.check_output(git_cmd).decode('utf-8')
-except Exception:
- warnings.warn('Cannot get last updated time from git repository. '
- 'Not setting "html_last_updated_fmt".')
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
diff --git a/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml b/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml
new file mode 100644
index 0000000..9115f03
--- /dev/null
+++ b/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Add a new client to handle the OAUTH token feature from the identity API.
diff --git a/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml b/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml
new file mode 100644
index 0000000..46f3b49
--- /dev/null
+++ b/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ The ``list_endpoints`` method of the v3 ``EndPointsClient`` class now has
+ an additional ``**params`` argument that enables passing additional
+ information in the query string of the HTTP request.
diff --git a/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml b/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
new file mode 100644
index 0000000..8fdf4f0
--- /dev/null
+++ b/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add --save-state option to allow saving state of cloud before tempest run.
diff --git a/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml b/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml
new file mode 100644
index 0000000..140df60
--- /dev/null
+++ b/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add show snapshot metadata item API to v2 snapshots_client library.
+ This feature enables the possibility to show a snapshot's metadata for
+ a specific key.
diff --git a/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml b/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml
new file mode 100644
index 0000000..49a935c
--- /dev/null
+++ b/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add show volume metadata item API to v2 volumes_client library.
+ This feature enables the possibility to show a volume's metadata for
+ a specific key.
diff --git a/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml b/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml
new file mode 100644
index 0000000..7cd6887
--- /dev/null
+++ b/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ Define v3 backups_client for the volume service as a library interface,
+ allowing other projects to use this module as a stable library without
+ maintenance changes.
+ Add update backup API to v3 backups_client library, min_microversion
+ of this API is 3.9.
+
+ * backups_client(v3)
diff --git a/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml b/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml
new file mode 100644
index 0000000..bfebcd9
--- /dev/null
+++ b/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml
@@ -0,0 +1,4 @@
+---
+prelude: >
+ This is an intermediate release during the Pike development cycle to
+ make new functionality available to plugins and other consumers.
diff --git a/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml b/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml
new file mode 100644
index 0000000..ec81dc5
--- /dev/null
+++ b/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml
@@ -0,0 +1,15 @@
+features:
+ - |
+ Move base_client from tempest.lib.services.volume.v3 to
+ tempest.lib.services.volume, so if we want to add new
+ interfaces based on a v2 client, we can make that v2
+ client inherit from volume.base_client.BaseClient to
+ get microversion support, and then to make the new v3
+ client inherit from the v2 client, thus to avoid the
+ multiple inheritance.
+deprecations:
+ - |
+ Deprecate class BaseClient from volume.v3.base_client
+ and move it to volume.base_client.
+ ``tempest.lib.services.volume.v3.base_client.BaseClient``
+ (new ``tempest.lib.services.volume.base_client.BaseClient``)
diff --git a/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml b/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml
new file mode 100644
index 0000000..a540c7d
--- /dev/null
+++ b/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ Pause teardown
+ When pause_teardown flag in tempest.conf is set to True a pdb breakpoint
+ is added to tearDown and tearDownClass methods in test.py.
+ This allows to pause cleaning resources process, so that used resources
+ can be examined. Closer examination of used resources may lead to faster
+ debugging.
diff --git a/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
new file mode 100644
index 0000000..ec21098
--- /dev/null
+++ b/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added tempest workspace remove --name <workspace_name> --rmdir
+ feature to delete the workspace directory as well as entry.
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index d240467..3137541 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -37,10 +37,18 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'oslosphinx',
+ 'openstackdocstheme',
'reno.sphinxext',
]
+# openstackdocstheme options
+repository_name = 'openstack/tempest'
+bug_project = 'tempest'
+bug_tag = ''
+
+# Must set this variable to include year, month, day, hours, and minutes.
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -119,9 +127,6 @@
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-import openstackdocstheme
-
-html_theme_path = [openstackdocstheme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index ba8a214..83fe215 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -111,10 +111,12 @@
LOG.info('(%s) Found more than one router for tenant.',
test_utils.find_test_caller())
- # Let's just blindly remove any networks, duplicate or otherwise, that
- # the test might have created even though Neutron will cleanup
- # duplicate resources automatically (so ignore 404s).
- networks = cls.networks_client.list_networks().get('networks', [])
+ # Remove any networks, duplicate or otherwise, that these tests
+ # created. All such networks will be in the current tenant. Neutron
+ # will cleanup duplicate resources automatically, so ignore 404s.
+ search_opts = {'tenant_id': cls.networks_client.tenant_id}
+ networks = cls.networks_client.list_networks(
+ **search_opts).get('networks', [])
for router in routers:
# Disassociate the subnets from the router. Because of the race
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 56a976d..feabe35 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -424,7 +424,7 @@
LOG.exception('Waiting for deletion of volume %s failed',
volume['id'])
- def attach_volume(self, server, volume, device=None):
+ def attach_volume(self, server, volume, device=None, check_reserved=False):
"""Attaches volume to server and waits for 'in-use' volume status.
The volume will be detached when the test tears down.
@@ -433,10 +433,15 @@
:param volume: The volume to attach.
:param device: Optional mountpoint for the attached volume. Note that
this is not guaranteed for all hypervisors and is not recommended.
+ :param check_reserved: Consider a status of reserved as valid for
+ completion. This is to handle new Cinder attach where we more
+ accurately use 'reserved' for things like attaching to a shelved
+ server.
"""
attach_kwargs = dict(volumeId=volume['id'])
if device:
attach_kwargs['device'] = device
+
attachment = self.servers_client.attach_volume(
server['id'], **attach_kwargs)['volumeAttachment']
# On teardown detach the volume and wait for it to be available. This
@@ -449,8 +454,11 @@
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.servers_client.detach_volume,
server['id'], volume['id'])
+ statuses = ['in-use']
+ if check_reserved:
+ statuses.append('reserved')
waiters.wait_for_volume_resource_status(self.volumes_client,
- volume['id'], 'in-use')
+ volume['id'], statuses)
return attachment
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index bf4c958..d5bb45a 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -68,7 +68,7 @@
params = {'marker': flavor_id}
flavors = self.flavors_client.list_flavors(**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id],
'The list of flavors did not start after the marker.')
@decorators.idempotent_id('6db2f0c0-ddee-4162-9c84-0703d3dd1107')
@@ -80,7 +80,7 @@
params = {'marker': flavor_id}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id],
'The list of flavors did not start after the marker.')
@decorators.idempotent_id('3df2743e-3034-4e57-a4cb-b6527f6eac79')
@@ -92,7 +92,7 @@
params = {self._min_disk: flavor['disk'] + 1}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
@decorators.idempotent_id('09fe7509-b4ee-4b34-bf8b-39532dc47292')
def test_list_flavors_detailed_filter_by_min_ram(self):
@@ -103,7 +103,7 @@
params = {self._min_ram: flavor['ram'] + 1}
flavors = self.flavors_client.list_flavors(detail=True,
**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
@decorators.idempotent_id('10645a4d-96f5-443f-831b-730711e11dd4')
def test_list_flavors_filter_by_min_disk(self):
@@ -113,7 +113,7 @@
params = {self._min_disk: flavor['disk'] + 1}
flavors = self.flavors_client.list_flavors(**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
@decorators.idempotent_id('935cf550-e7c8-4da6-8002-00f92d5edfaa')
def test_list_flavors_filter_by_min_ram(self):
@@ -123,4 +123,4 @@
params = {self._min_ram: flavor['ram'] + 1}
flavors = self.flavors_client.list_flavors(**params)['flavors']
- self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
+ self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 6677aa2..acc8b3e 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -129,9 +129,9 @@
params = {'status': 'ACTIVE'}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('33163b73-79f5-4d07-a7ea-9213bcc468ff')
def test_list_images_filter_by_name(self):
@@ -140,9 +140,9 @@
params = {'name': self.image1['name']}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('9f238683-c763-45aa-b848-232ec3ce3105')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -152,14 +152,13 @@
params = {'server': self.server1['id']}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot1_id]),
- "Failed to find image %s in images. Got images %s" %
- (self.image1_id, images))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot3_id]))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot1_id],
+ "Failed to find image %s in images. "
+ "Got images %s" % (self.image1_id, images))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertEmpty([i for i in images if i['id'] == self.snapshot3_id])
@decorators.idempotent_id('05a377b8-28cf-4734-a1e6-2ab5c38bf606')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -173,12 +172,12 @@
params = {'server': link['href']}
images = self.client.list_images(**params)['images']
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
@decorators.idempotent_id('e3356918-4d3e-4756-81d5-abc4524ba29f')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -188,14 +187,13 @@
params = {'type': 'snapshot'}
images = self.client.list_images(**params)['images']
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.image_ref]))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image_ref])
@decorators.idempotent_id('3a484ca9-67ba-451e-b494-7fcf28d32d62')
def test_list_images_limit_results(self):
@@ -212,8 +210,8 @@
# Filter by the image's created time
params = {'changes-since': self.image3['created']}
images = self.client.list_images(**params)['images']
- found = any([i for i in images if i['id'] == self.image3_id])
- self.assertTrue(found)
+ found = [i for i in images if i['id'] == self.image3_id]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('9b0ea018-6185-4f71-948a-a123a107988e')
def test_list_images_with_detail_filter_by_status(self):
@@ -222,9 +220,9 @@
params = {'status': 'ACTIVE'}
images = self.client.list_images(detail=True, **params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
- self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertNotEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('644ea267-9bd9-4f3b-af9f-dffa02396a17')
def test_list_images_with_detail_filter_by_name(self):
@@ -233,9 +231,9 @@
params = {'name': self.image1['name']}
images = self.client.list_images(detail=True, **params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
- self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image2_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image3_id])
@decorators.idempotent_id('ba2fa9a9-b672-47cc-b354-3b4c0600e2cb')
def test_list_images_with_detail_limit_results(self):
@@ -257,12 +255,12 @@
params = {'server': link['href']}
images = self.client.list_images(detail=True, **params)['images']
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
@decorators.idempotent_id('888c0cc0-7223-43c5-9db0-b125fd0a393b')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
@@ -273,14 +271,13 @@
images = self.client.list_images(detail=True, **params)['images']
self.client.show_image(self.image_ref)
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot1_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot2_id]))
- self.assertTrue(any([i for i in images
- if i['id'] == self.snapshot3_id]))
- self.assertFalse(any([i for i in images
- if i['id'] == self.image_ref]))
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot1_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot2_id])
+ self.assertNotEmpty([i for i in images
+ if i['id'] == self.snapshot3_id])
+ self.assertEmpty([i for i in images if i['id'] == self.image_ref])
@decorators.idempotent_id('7d439e18-ac2e-4827-b049-7e18004712c4')
def test_list_images_with_detail_filter_by_changes_since(self):
@@ -290,4 +287,4 @@
# Filter by the image's created time
params = {'changes-since': self.image1['created']}
images = self.client.list_images(detail=True, **params)['images']
- self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ self.assertNotEmpty([i for i in images if i['id'] == self.image1_id])
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index 5d3cbf3..e2dbd72 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -44,12 +44,12 @@
def test_list_images(self):
# The list of all images should contain the image
images = self.client.list_images()['images']
- found = any([i for i in images if i['id'] == self.image_ref])
- self.assertTrue(found)
+ found = [i for i in images if i['id'] == self.image_ref]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('9f94cb6b-7f10-48c5-b911-a0b84d7d4cd6')
def test_list_images_with_detail(self):
# Detailed list of all images should contain the expected images
images = self.client.list_images(detail=True)['images']
- found = any([i for i in images if i['id'] == self.image_ref])
- self.assertTrue(found)
+ found = [i for i in images if i['id'] == self.image_ref]
+ self.assertNotEmpty(found)
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index b568f7d..124db0e 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -159,8 +159,8 @@
# Get rules of the created Security Group
rules = self.security_groups_client.show_security_group(
securitygroup_id)['security_group']['rules']
- self.assertTrue(any([i for i in rules if i['id'] == rule1_id]))
- self.assertTrue(any([i for i in rules if i['id'] == rule2_id]))
+ self.assertNotEmpty([i for i in rules if i['id'] == rule1_id])
+ self.assertNotEmpty([i for i in rules if i['id'] == rule2_id])
@decorators.idempotent_id('fc5c5acf-2091-43a6-a6ae-e42760e9ffaf')
@test.services('network')
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index db42c6c..ffadd96 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -97,16 +97,16 @@
# The created server should be in the list of all servers
body = self.client.list_servers()
servers = body['servers']
- found = any([i for i in servers if i['id'] == self.server['id']])
- self.assertTrue(found)
+ found = [i for i in servers if i['id'] == self.server['id']]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
def test_list_servers_with_detail(self):
# The created server should be in the detailed list of all servers
body = self.client.list_servers(detail=True)
servers = body['servers']
- found = any([i for i in servers if i['id'] == self.server['id']])
- self.assertTrue(found)
+ found = [i for i in servers if i['id'] == self.server['id']]
+ self.assertNotEmpty(found)
@decorators.idempotent_id('cbc0f52f-05aa-492b-bdc1-84b575ca294b')
@testtools.skipUnless(CONF.validation.run_validation,
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6f072b2..cd09177 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -517,23 +517,33 @@
@decorators.idempotent_id('77eba8e0-036e-4635-944b-f7a8f3b78dc9')
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
+ @test.services('image')
def test_shelve_unshelve_server(self):
+ if CONF.image_feature_enabled.api_v2:
+ glance_client = self.os_primary.image_client_v2
+ elif CONF.image_feature_enabled.api_v1:
+ glance_client = self.os_primary.image_client
+ else:
+ raise lib_exc.InvalidConfiguration(
+ 'Either api_v1 or api_v2 must be True in '
+ '[image-feature-enabled].')
compute.shelve_server(self.client, self.server_id,
force_shelve_offload=True)
server = self.client.show_server(self.server_id)['server']
image_name = server['name'] + '-shelved'
params = {'name': image_name}
- images = self.compute_images_client.list_images(**params)['images']
+ if CONF.image_feature_enabled.api_v2:
+ images = glance_client.list_images(params)['images']
+ elif CONF.image_feature_enabled.api_v1:
+ images = glance_client.list_images(
+ detail=True, **params)['images']
self.assertEqual(1, len(images))
self.assertEqual(image_name, images[0]['name'])
self.client.unshelve_server(self.server_id)
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
-
- images = self.compute_images_client.list_images(**params)['images']
- msg = ('After unshelve, shelved image is not deleted.')
- self.assertEmpty(images, msg)
+ glance_client.wait_for_resource_deletion(images[0]['id'])
@decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560')
def test_stop_start_server(self):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index ed5f9a6..502bc1b 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -212,7 +212,8 @@
num_vol = self._count_volumes(server)
self._shelve_server(server)
attachment = self.attach_volume(server, volume,
- device=('/dev/%s' % self.device))
+ device=('/dev/%s' % self.device),
+ check_reserved=True)
# Unshelve the instance and check that attached volume exists
self._unshelve_server_and_check_volumes(server, num_vol + 1)
@@ -239,7 +240,8 @@
self._shelve_server(server)
# Attach and then detach the volume
- self.attach_volume(server, volume, device=('/dev/%s' % self.device))
+ self.attach_volume(server, volume, device=('/dev/%s' % self.device),
+ check_reserved=True)
self.servers_client.detach_volume(server['id'], volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
diff --git a/tempest/api/identity/admin/v2/test_roles.py b/tempest/api/identity/admin/v2/test_roles.py
index 479663c..124bb5f 100644
--- a/tempest/api/identity/admin/v2/test_roles.py
+++ b/tempest/api/identity/admin/v2/test_roles.py
@@ -54,7 +54,7 @@
"""Return a list of all roles."""
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role in self.roles]
- self.assertTrue(any(found))
+ self.assertNotEmpty(found)
self.assertEqual(len(found), len(self.roles))
@decorators.idempotent_id('c62d909d-6c21-48c0-ae40-0a0760e6db5e')
@@ -68,13 +68,13 @@
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role['name'] == role_name]
- self.assertTrue(any(found))
+ self.assertNotEmpty(found)
body = self.roles_client.delete_role(found[0]['id'])
body = self.roles_client.list_roles()['roles']
found = [role for role in body if role['name'] == role_name]
- self.assertFalse(any(found))
+ self.assertEmpty(found)
@decorators.idempotent_id('db6870bd-a6ed-43be-a9b1-2f10a5c9994f')
def test_get_role_by_id(self):
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index ad9b983..0f955bf 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -37,7 +37,7 @@
body = self.tenants_client.list_tenants()['tenants']
found = [tenant for tenant in body if tenant['id'] in tenant_ids]
- self.assertFalse(any(found), 'Tenants failed to delete')
+ self.assertEmpty(found, 'Tenants failed to delete')
@decorators.idempotent_id('d25e9f24-1310-4d29-b61b-d91299c21d6d')
def test_tenant_create_with_description(self):
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index b1ae2aa..c9faa9a 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -29,56 +29,93 @@
def resource_setup(cls):
super(EndPointsTestJSON, cls).resource_setup()
cls.service_ids = list()
- s_name = data_utils.rand_name('service')
- s_type = data_utils.rand_name('type')
- s_description = data_utils.rand_name('description')
+
+ # Create endpoints so as to use for LIST and GET test cases
+ interfaces = ['public', 'internal']
+ cls.setup_endpoint_ids = list()
+ for i in range(2):
+ cls._create_service()
+ region = data_utils.rand_name('region')
+ url = data_utils.rand_url()
+ endpoint = cls.client.create_endpoint(
+ service_id=cls.service_ids[i], interface=interfaces[i],
+ url=url, region=region, enabled=True)['endpoint']
+ cls.setup_endpoint_ids.append(endpoint['id'])
+
+ @classmethod
+ def _create_service(cls, s_name=None, s_type=None, s_description=None):
+ if s_name is None:
+ s_name = data_utils.rand_name('service')
+ if s_type is None:
+ s_type = data_utils.rand_name('type')
+ if s_description is None:
+ s_description = data_utils.rand_name('description')
service_data = (
cls.services_client.create_service(name=s_name, type=s_type,
description=s_description))
- 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()
- for _ in range(2):
- region = data_utils.rand_name('region')
- url = data_utils.rand_url()
- interface = 'public'
- endpoint = cls.client.create_endpoint(service_id=cls.service_id,
- interface=interface,
- url=url, region=region,
- enabled=True)['endpoint']
- cls.setup_endpoints.append(endpoint)
+ service = service_data['service']
+ cls.service_ids.append(service['id'])
+ return service
@classmethod
def resource_cleanup(cls):
- for e in cls.setup_endpoints:
- cls.client.delete_endpoint(e['id'])
+ for e in cls.setup_endpoint_ids:
+ cls.client.delete_endpoint(e)
for s in cls.service_ids:
cls.services_client.delete_service(s)
super(EndPointsTestJSON, cls).resource_cleanup()
@decorators.idempotent_id('c19ecf90-240e-4e23-9966-21cee3f6a618')
def test_list_endpoints(self):
- # Get a list of endpoints
+ # Get the list of all the endpoints.
fetched_endpoints = self.client.list_endpoints()['endpoints']
- # Asserting LIST endpoints
+ fetched_endpoint_ids = [e['id'] for e in fetched_endpoints]
+ # Check that all the created endpoints are present in
+ # "fetched_endpoints".
missing_endpoints =\
- [e for e in self.setup_endpoints if e not in fetched_endpoints]
- self.assertEmpty(missing_endpoints,
+ [e for e in self.setup_endpoint_ids
+ if e not in fetched_endpoint_ids]
+ self.assertEqual(0, len(missing_endpoints),
"Failed to find endpoint %s in fetched list" %
', '.join(str(e) for e in missing_endpoints))
+ # Check that filtering endpoints by service_id works.
+ fetched_endpoints_for_service = self.client.list_endpoints(
+ service_id=self.service_ids[0])['endpoints']
+ fetched_endpoints_for_alt_service = self.client.list_endpoints(
+ service_id=self.service_ids[1])['endpoints']
+
+ # Assert that both filters returned the correct result.
+ self.assertEqual(1, len(fetched_endpoints_for_service))
+ self.assertEqual(1, len(fetched_endpoints_for_alt_service))
+ self.assertEqual(set(self.setup_endpoint_ids),
+ set([fetched_endpoints_for_service[0]['id'],
+ fetched_endpoints_for_alt_service[0]['id']]))
+
+ # Check that filtering endpoints by interface works.
+ fetched_public_endpoints = self.client.list_endpoints(
+ interface='public')['endpoints']
+ fetched_internal_endpoints = self.client.list_endpoints(
+ interface='internal')['endpoints']
+
+ # Check that the expected endpoint_id is present per filter. [0] is
+ # public and [1] is internal.
+ self.assertIn(self.setup_endpoint_ids[0],
+ [e['id'] for e in fetched_public_endpoints])
+ self.assertIn(self.setup_endpoint_ids[1],
+ [e['id'] for e in fetched_internal_endpoints])
+
@decorators.idempotent_id('0e2446d2-c1fd-461b-a729-b9e73e3e3b37')
def test_create_list_show_delete_endpoint(self):
region = data_utils.rand_name('region')
url = data_utils.rand_url()
interface = 'public'
- endpoint = self.client.create_endpoint(service_id=self.service_id,
+ endpoint = self.client.create_endpoint(service_id=self.service_ids[0],
interface=interface,
url=url, region=region,
enabled=True)['endpoint']
- self.setup_endpoints.append(endpoint)
+ self.setup_endpoint_ids.append(endpoint['id'])
# Asserting Create Endpoint response body
self.assertIn('id', endpoint)
self.assertEqual(region, endpoint['region'])
@@ -93,7 +130,7 @@
fetched_endpoint = (
self.client.show_endpoint(endpoint['id'])['endpoint'])
# Asserting if the attributes of endpoint are the same
- self.assertEqual(self.service_id, fetched_endpoint['service_id'])
+ self.assertEqual(self.service_ids[0], fetched_endpoint['service_id'])
self.assertEqual(interface, fetched_endpoint['interface'])
self.assertEqual(url, fetched_endpoint['url'])
self.assertEqual(region, fetched_endpoint['region'])
@@ -101,7 +138,7 @@
# Deleting the endpoint created in this method
self.client.delete_endpoint(endpoint['id'])
- self.setup_endpoints.remove(endpoint)
+ self.setup_endpoint_ids.remove(endpoint['id'])
# Checking whether endpoint is deleted successfully
fetched_endpoints = self.client.list_endpoints()['endpoints']
@@ -117,7 +154,7 @@
url1 = data_utils.rand_url()
interface1 = 'public'
endpoint_for_update = (
- self.client.create_endpoint(service_id=self.service_id,
+ self.client.create_endpoint(service_id=self.service_ids[0],
interface=interface1,
url=url1, region=region1,
enabled=True)['endpoint'])
@@ -126,11 +163,8 @@
s_name = data_utils.rand_name('service')
s_type = data_utils.rand_name('type')
s_description = data_utils.rand_name('description')
- service2 = (
- self.services_client.create_service(name=s_name, type=s_type,
- description=s_description))
- service2 = service2['service']
- self.service_ids.append(service2['id'])
+ service2 = self._create_service(s_name=s_name, s_type=s_type,
+ s_description=s_description)
# Updating endpoint with new values
region2 = data_utils.rand_name('region')
url2 = data_utils.rand_url()
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index ad4e549..20c8a44 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -77,17 +77,26 @@
def test_list_services(self):
# Create, List, Verify and Delete Services
service_ids = list()
+ service_types = list()
for _ in range(3):
- name = data_utils.rand_name('service')
- serv_type = data_utils.rand_name('type')
+ name = data_utils.rand_name(self.__class__.__name__ + '-Service')
+ serv_type = data_utils.rand_name(self.__class__.__name__ + '-Type')
create_service = self.services_client.create_service(
type=serv_type, name=name)['service']
self.addCleanup(self.services_client.delete_service,
create_service['id'])
service_ids.append(create_service['id'])
+ service_types.append(serv_type)
# List and Verify Services
services = self.services_client.list_services()['services']
fetched_ids = [service['id'] for service in services]
found = [s for s in fetched_ids if s in service_ids]
self.assertEqual(len(found), len(service_ids))
+
+ # Check that filtering by service type works.
+ for serv_type in service_types:
+ fetched_services = self.services_client.list_services(
+ type=serv_type)['services']
+ self.assertEqual(1, len(fetched_services))
+ self.assertEqual(serv_type, fetched_services[0]['type'])
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 3bc6ce1..4495cbf 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -223,6 +223,7 @@
cls.projects_client = cls.os_admin.projects_client
cls.role_assignments = cls.os_admin.role_assignments_client
cls.oauth_consumers_client = cls.os_admin.oauth_consumers_client
+ cls.oauth_token_client = cls.os_admin.oauth_token_client
cls.domain_config_client = cls.os_admin.domain_config_client
cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client
cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
index 1f3a7c4..567a462 100644
--- a/tempest/api/network/test_tags.py
+++ b/tempest/api/network/test_tags.py
@@ -14,11 +14,14 @@
# under the License.
from tempest.api.network import base
+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
from tempest import test
+CONF = config.CONF
+
class TagsTest(base.BaseNetworkTest):
"""Tests the following operations in the tags API:
@@ -88,3 +91,112 @@
self.assertRaises(lib_exc.NotFound,
self.tags_client.check_tag_existence, 'networks',
self.network['id'], replace_tags[i])
+
+
+class TagsExtTest(base.BaseNetworkTest):
+ """Tests the following operations in the tags API:
+
+ Update all tags.
+ Delete all tags.
+ Check tag existence.
+ Create a tag.
+ List tags.
+ Remove a tag.
+
+ v2.0 of the Neutron API is assumed. The tag-ext extension allows users to
+ set tags on the following resources: subnets, ports, routers and
+ subnetpools.
+ """
+
+ # NOTE(felipemonteiro): The supported resource names are plural. Use
+ # the singular case for the corresponding class resource object.
+ SUPPORTED_RESOURCES = ['subnets', 'ports', 'routers', 'subnetpools']
+
+ @classmethod
+ def skip_checks(cls):
+ super(TagsExtTest, cls).skip_checks()
+ if not test.is_extension_enabled('tag-ext', 'network'):
+ msg = "tag-ext extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(TagsExtTest, cls).resource_setup()
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.port = cls.create_port(cls.network)
+ cls.router = cls.create_router()
+
+ subnetpool_name = data_utils.rand_name(cls.__name__ + '-Subnetpool')
+ prefix = CONF.network.default_network
+ cls.subnetpool = cls.subnetpools_client.create_subnetpool(
+ name=subnetpool_name, prefixes=prefix)['subnetpool']
+
+ @classmethod
+ def resource_cleanup(cls):
+ cls.subnetpools_client.delete_subnetpool(cls.subnetpool['id'])
+ super(TagsExtTest, cls).resource_cleanup()
+
+ def _create_tags_for_each_resource(self):
+ # Create a tag for each resource in `SUPPORTED_RESOURCES` and return
+ # the list of tag names.
+ tag_names = []
+
+ for resource in self.SUPPORTED_RESOURCES:
+ tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag')
+ tag_names.append(tag_name)
+ resource_object = getattr(self, resource[:-1])
+
+ self.tags_client.create_tag(resource, resource_object['id'],
+ tag_name)
+ self.addCleanup(self.tags_client.delete_all_tags, resource,
+ resource_object['id'])
+
+ return tag_names
+
+ @decorators.idempotent_id('c6231efa-9a89-4adf-b050-2a3156b8a1d9')
+ def test_create_check_list_and_delete_tags(self):
+ tag_names = self._create_tags_for_each_resource()
+
+ for i, resource in enumerate(self.SUPPORTED_RESOURCES):
+ # Ensure that a tag was created for each resource.
+ resource_object = getattr(self, resource[:-1])
+ retrieved_tags = self.tags_client.list_tags(
+ resource, resource_object['id'])['tags']
+ self.assertEqual(1, len(retrieved_tags))
+ self.assertEqual(tag_names[i], retrieved_tags[0])
+
+ # Check that the expected tag exists for each resource.
+ self.tags_client.check_tag_existence(
+ resource, resource_object['id'], tag_names[i])
+
+ # Delete the tag and ensure it was deleted.
+ self.tags_client.delete_tag(
+ resource, resource_object['id'], tag_names[i])
+ retrieved_tags = self.tags_client.list_tags(
+ resource, resource_object['id'])['tags']
+ self.assertEmpty(retrieved_tags)
+
+ @decorators.idempotent_id('663a90f5-f334-4b44-afe0-c5fc1d408791')
+ def test_update_and_delete_all_tags(self):
+ self._create_tags_for_each_resource()
+
+ for resource in self.SUPPORTED_RESOURCES:
+ # Generate 3 new tag names.
+ replace_tags = [data_utils.rand_name(
+ self.__class__.__name__ + '-Tag') for _ in range(3)]
+
+ # Replace the current tag with the 3 new tags and validate that the
+ # current resource has the 3 new tags.
+ resource_object = getattr(self, resource[:-1])
+ updated_tags = self.tags_client.update_all_tags(
+ resource, resource_object['id'], replace_tags)['tags']
+ self.assertEqual(3, len(updated_tags))
+ self.assertEqual(set(replace_tags), set(updated_tags))
+
+ # Delete all the tags and check that they have been removed.
+ self.tags_client.delete_all_tags(resource, resource_object['id'])
+ for i in range(3):
+ self.assertRaises(
+ lib_exc.NotFound, self.tags_client.check_tag_existence,
+ resource, resource_object['id'], replace_tags[i])
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index ae4b579..f358d7f 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -155,7 +155,7 @@
# Accepts a volume transfer
self.alt_transfer_client.accept_volume_transfer(
- transfer_id, auth_key=auth_key)['transfer']
+ transfer_id, auth_key=auth_key)
# Verify volume transferred is available
waiters.wait_for_volume_resource_status(
diff --git a/tempest/api/volume/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
index 64bc537..7bbe674 100644
--- a/tempest/api/volume/api_microversion_fixture.py
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -13,7 +13,7 @@
import fixtures
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class APIMicroversionFixture(fixtures.Fixture):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 394c453..ef69ba3 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -69,11 +69,14 @@
if CONF.service_available.glance:
cls.images_client = cls.os_primary.image_client_v2
- cls.snapshots_client = cls.os_primary.snapshots_v2_client
- cls.volumes_client = cls.os_primary.volumes_v2_client
if cls._api_version == 3:
+ cls.backups_client = cls.os_primary.backups_v3_client
cls.volumes_client = cls.os_primary.volumes_v3_client
- cls.backups_client = cls.os_primary.backups_v2_client
+ else:
+ cls.backups_client = cls.os_primary.backups_v2_client
+ cls.volumes_client = cls.os_primary.volumes_v2_client
+
+ cls.snapshots_client = cls.os_primary.snapshots_v2_client
cls.volumes_extension_client =\
cls.os_primary.volumes_v2_extension_client
cls.availability_zone_client = (
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index 164ed37..e6fe25d 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -55,6 +55,7 @@
# Create metadata
body = self.snapshots_client.create_snapshot_metadata(
self.snapshot['id'], metadata)['metadata']
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Get the metadata of the snapshot
body = self.snapshots_client.show_snapshot_metadata(
@@ -65,6 +66,7 @@
# Update metadata
body = self.snapshots_client.update_snapshot_metadata(
self.snapshot['id'], metadata=update)['metadata']
+ self.assertEqual(update, body)
body = self.snapshots_client.show_snapshot_metadata(
self.snapshot['id'])['metadata']
self.assertEqual(update, body, 'Update snapshot metadata failed')
@@ -79,7 +81,7 @@
self.assertNotIn("key3", body)
@decorators.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed')
- def test_update_snapshot_metadata_item(self):
+ def test_update_show_snapshot_metadata_item(self):
# Update metadata item for the snapshot
metadata = {"key1": "value1",
"key2": "value2",
@@ -89,8 +91,8 @@
"key2": "value2",
"key3": "value3_update"}
# Create metadata for the snapshot
- body = self.snapshots_client.create_snapshot_metadata(
- self.snapshot['id'], metadata)['metadata']
+ self.snapshots_client.create_snapshot_metadata(
+ self.snapshot['id'], metadata)
# Get the metadata of the snapshot
body = self.snapshots_client.show_snapshot_metadata(
self.snapshot['id'])['metadata']
@@ -98,6 +100,13 @@
# Update metadata item
body = self.snapshots_client.update_snapshot_metadata_item(
self.snapshot['id'], "key3", meta=update_item)['meta']
+ self.assertEqual(update_item, body)
+
+ # Get a specific metadata item of the snapshot
+ body = self.snapshots_client.show_snapshot_metadata_item(
+ self.snapshot['id'], "key3")['meta']
+ self.assertEqual({"key3": expect['key3']}, body)
+
# Get the metadata of the snapshot
body = self.snapshots_client.show_snapshot_metadata(
self.snapshot['id'])['metadata']
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index b4d22fe..d203b2d 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -45,6 +45,7 @@
body = self.volumes_client.create_volume_metadata(self.volume['id'],
metadata)['metadata']
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Get the metadata of the volume
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
@@ -54,6 +55,7 @@
# Update metadata
body = self.volumes_client.update_volume_metadata(
self.volume['id'], update)['metadata']
+ self.assertEqual(update, body)
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
self.assertEqual(update, body, 'Update metadata failed')
@@ -68,7 +70,7 @@
'Delete one item metadata of the volume failed')
@decorators.idempotent_id('862261c5-8df4-475a-8c21-946e50e36a20')
- def test_update_volume_metadata_item(self):
+ def test_update_show_volume_metadata_item(self):
# Update metadata item for the volume
metadata = {"key1": "value1",
"key2": "value2",
@@ -85,6 +87,13 @@
# Update metadata item
body = self.volumes_client.update_volume_metadata_item(
self.volume['id'], "key3", update_item)['meta']
+ self.assertEqual(update_item, body)
+
+ # Get a specific metadata item of the volume
+ body = self.volumes_client.show_volume_metadata_item(
+ self.volume['id'], "key3")['meta']
+ self.assertEqual({"key3": expect['key3']}, body)
+
# Get the metadata of the volume
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 2c13a3c..4108da5 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -59,6 +59,8 @@
# Accept a volume transfer by alt_tenant
body = self.alt_client.accept_volume_transfer(
transfer_id, auth_key=auth_key)['transfer']
+ for key in ['id', 'name', 'links', 'volume_id']:
+ self.assertIn(key, body)
waiters.wait_for_volume_resource_status(self.alt_volumes_client,
volume['id'], 'available')
accepted_volume = self.alt_volumes_client.show_volume(
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 8541c6d..c4d10c3 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -115,12 +115,12 @@
@decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
# Mark volume as reserved.
- body = self.volumes_client.reserve_volume(self.volume['id'])
+ self.volumes_client.reserve_volume(self.volume['id'])
# To get the volume info
body = self.volumes_client.show_volume(self.volume['id'])['volume']
self.assertIn('attaching', body['status'])
# Unmark volume as reserved.
- body = self.volumes_client.unreserve_volume(self.volume['id'])
+ self.volumes_client.unreserve_volume(self.volume['id'])
# To get the volume info
body = self.volumes_client.show_volume(self.volume['id'])['volume']
self.assertIn('available', body['status'])
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 4b4aeec..1f91db6 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -140,3 +140,39 @@
restored_volume_id)['volume']
self.assertEqual('true', restored_volume_info['bootable'])
+
+
+class VolumesBackupsV39Test(base.BaseVolumeTest):
+
+ _api_version = 3
+ min_microversion = '3.9'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesBackupsV39Test, cls).skip_checks()
+ if not CONF.volume_feature_enabled.backup:
+ raise cls.skipException("Cinder backup feature disabled")
+
+ @decorators.idempotent_id('9b374cbc-be5f-4d37-8848-7efb8a873dcc')
+ def test_update_backup(self):
+ # Create volume and backup
+ volume = self.create_volume()
+ backup = self.create_backup(volume_id=volume['id'])
+
+ # Update backup and assert response body for update_backup method
+ update_kwargs = {
+ 'name': data_utils.rand_name(self.__class__.__name__ + '-Backup'),
+ 'description': data_utils.rand_name("volume-backup-description")
+ }
+ update_backup = self.backups_client.update_backup(
+ backup['id'], **update_kwargs)['backup']
+ self.assertEqual(backup['id'], update_backup['id'])
+ self.assertEqual(update_kwargs['name'], update_backup['name'])
+ self.assertIn('links', update_backup)
+
+ # Assert response body for show_backup method
+ retrieved_backup = self.backups_client.show_backup(
+ backup['id'])['backup']
+ for key in update_kwargs:
+ self.assertEqual(update_kwargs[key], retrieved_backup[key])
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 44c1def..e68ab7e 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
from testtools import matchers
from tempest.api.volume import base
@@ -149,3 +150,16 @@
# Should allow
self.assertEqual(volume['snapshot_id'], src_snap['id'])
self.assertEqual(volume['size'], src_size + 1)
+
+ @decorators.idempotent_id('bbcfa285-af7f-479e-8c1a-8c34fc16543c')
+ @testtools.skipUnless(CONF.volume_feature_enabled.backup,
+ "Cinder backup is disabled")
+ def test_snapshot_backup(self):
+ # Create a snapshot
+ snapshot = self.create_snapshot(volume_id=self.volume_origin['id'])
+
+ backup = self.create_backup(volume_id=self.volume_origin['id'],
+ snapshot_id=snapshot['id'])
+ backup_info = self.backups_client.show_backup(backup['id'])['backup']
+ self.assertEqual(self.volume_origin['id'], backup_info['volume_id'])
+ self.assertEqual(snapshot['id'], backup_info['snapshot_id'])
diff --git a/tempest/clients.py b/tempest/clients.py
index d29bef9..467ef9c 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -200,6 +200,8 @@
**params_v3)
self.oauth_consumers_client = self.identity_v3.OAUTHConsumerClient(
**params_v3)
+ self.oauth_token_client = self.identity_v3.OAUTHTokenClient(
+ **params_v3)
self.domain_config_client = self.identity_v3.DomainConfigurationClient(
**params_v3)
self.endpoint_filter_client = \
@@ -233,6 +235,7 @@
self.volume_services_v2_client = self.volume_v2.ServicesClient()
self.backups_client = self.volume_v1.BackupsClient()
self.backups_v2_client = self.volume_v2.BackupsClient()
+ self.backups_v3_client = self.volume_v3.BackupsClient()
self.encryption_types_client = self.volume_v1.EncryptionTypesClient()
self.encryption_types_v2_client = \
self.volume_v2.EncryptionTypesClient()
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index b36bf5c..350dd0b 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -97,15 +97,19 @@
from cliff import command
from os_testr import regex_builder
from os_testr import subunit_trace
+from oslo_serialization import jsonutils as json
import six
from testrepository.commands import run_argv
+from tempest.cmd import cleanup_service
from tempest.cmd import init
from tempest.cmd import workspace
+from tempest.common import credentials_factory as credentials
from tempest import config
CONF = config.CONF
+SAVED_STATE_JSON = "saved_state.json"
class TempestRun(command.Command):
@@ -174,6 +178,11 @@
else:
print("No .testr.conf file was found for local execution")
sys.exit(2)
+ if parsed_args.state:
+ self._init_state()
+ else:
+ pass
+
if parsed_args.combine:
temp_stream = tempfile.NamedTemporaryFile()
return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
@@ -203,6 +212,25 @@
def get_description(self):
return 'Run tempest'
+ def _init_state(self):
+ print("Initializing saved state.")
+ data = {}
+ self.global_services = cleanup_service.get_global_cleanup_services()
+ self.admin_mgr = credentials.AdminManager()
+ admin_mgr = self.admin_mgr
+ kwargs = {'data': data,
+ 'is_dry_run': False,
+ 'saved_state_json': data,
+ 'is_preserve': False,
+ 'is_save_state': True}
+ for service in self.global_services:
+ svc = service(admin_mgr, **kwargs)
+ svc.run()
+
+ with open(SAVED_STATE_JSON, 'w+') as f:
+ f.write(json.dumps(data,
+ sort_keys=True, indent=2, separators=(',', ': ')))
+
def get_parser(self, prog_name):
parser = super(TempestRun, self).get_parser(prog_name)
parser = self._add_args(parser)
@@ -253,6 +281,10 @@
parallel.add_argument('--serial', '-t', dest='parallel',
action='store_false',
help='Run tests serially')
+ parser.add_argument('--save-state', dest='state',
+ action='store_true',
+ help="To save the state of the cloud before "
+ "running tempest.")
# output args
parser.add_argument("--subunit", action='store_true',
help='Enable subunit v2 output')
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 6b81e0d..0972a3c 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -97,8 +97,9 @@
'keystone': os.identity_client,
'cinder': os.volumes_client,
}
- if service != 'keystone':
- # Since keystone may be listening on a path, do not remove the path.
+ if service != 'keystone' and service != 'cinder':
+ # Since keystone and cinder may be listening on a path,
+ # do not remove the path.
client_dict[service].skip_path()
endpoint = _get_unversioned_endpoint(client_dict[service].base_url)
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index 96d2300..8166b4f 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -40,6 +40,8 @@
------
Deletes the entry for a given tempest workspace --name
+--rmdir Deletes the given tempest workspace directory
+
General Options
===============
@@ -49,6 +51,7 @@
"""
import os
+import shutil
import sys
from cliff import command
@@ -102,11 +105,16 @@
sys.exit(1)
@lockutils.synchronized('workspaces', external=True)
- def remove_workspace(self, name):
+ def remove_workspace_entry(self, name):
self._populate()
self._name_exists(name)
- self.workspaces.pop(name)
+ workspace_path = self.workspaces.pop(name)
self._write_file()
+ return workspace_path
+
+ @lockutils.synchronized('workspaces', external=True)
+ def remove_workspace_directory(self, workspace_path):
+ shutil.rmtree(workspace_path)
@lockutils.synchronized('workspaces', external=True)
def list_workspaces(self):
@@ -226,12 +234,16 @@
parser = super(TempestWorkspaceRemove, self).get_parser(prog_name)
add_global_arguments(parser)
parser.add_argument('--name', required=True)
+ parser.add_argument('--rmdir', action='store_true',
+ help='Deletes the given workspace directory')
return parser
def take_action(self, parsed_args):
self.manager = WorkspaceManager(parsed_args.workspace_path)
- self.manager.remove_workspace(parsed_args.name)
+ workspace_path = self.manager.remove_workspace_entry(parsed_args.name)
+ if parsed_args.rmdir:
+ self.manager.remove_workspace_directory(workspace_path)
sys.exit(0)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 93e6fbf..cf187e6 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -179,25 +179,26 @@
raise lib_exc.TimeoutException(message)
-def wait_for_volume_resource_status(client, resource_id, status):
- """Waits for a volume resource to reach a given status.
+def wait_for_volume_resource_status(client, resource_id, statuses):
+ """Waits for a volume resource to reach any of the specified statuses.
This function is a common function for volume, snapshot and backup
resources. The function extracts the name of the desired resource from
the client class name of the resource.
"""
- resource_name = re.findall(
- r'(Volume|Snapshot|Backup|Group)',
- client.__class__.__name__)[0].lower()
+ if not isinstance(statuses, list):
+ statuses = [statuses]
+ resource_name = re.findall(r'(Volume|Snapshot|Backup|Group)',
+ client.__class__.__name__)[0].lower()
show_resource = getattr(client, 'show_' + resource_name)
resource_status = show_resource(resource_id)[resource_name]['status']
start = int(time.time())
- while resource_status != status:
+ while resource_status not in statuses:
time.sleep(client.build_interval)
resource_status = show_resource(resource_id)[
'{}'.format(resource_name)]['status']
- if resource_status == 'error' and resource_status != status:
+ if resource_status == 'error' and resource_status not in statuses:
raise exceptions.VolumeResourceBuildErrorException(
resource_name=resource_name, resource_id=resource_id)
if resource_name == 'volume' and resource_status == 'error_restoring':
@@ -206,7 +207,7 @@
if int(time.time()) - start >= client.build_timeout:
message = ('%s %s failed to reach %s status (current %s) '
'within the required time (%s s).' %
- (resource_name, resource_id, status, resource_status,
+ (resource_name, resource_id, statuses, resource_status,
client.build_timeout))
raise lib_exc.TimeoutException(message)
diff --git a/tempest/config.py b/tempest/config.py
index 7b96281..af9eefc 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1070,6 +1070,15 @@
"prefix to ideintify resources which are "
"created by Tempest and no projects set "
"this option on OpenStack dev community."),
+ cfg.BoolOpt('pause_teardown',
+ default=False,
+ help="""Whether to pause a test in global teardown.
+
+The best use case is investigating used resources of one test.
+A test can be run as follows:
+ $ ostestr --pdb TEST_ID
+or
+ $ python -m testtools.run TEST_ID"""),
]
_opts = [
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index ce607ff..e271a58 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -28,6 +28,8 @@
InheritedRolesClient
from tempest.lib.services.identity.v3.oauth_consumers_client import \
OAUTHConsumerClient
+from tempest.lib.services.identity.v3.oauth_token_client import \
+ OAUTHTokenClient
from tempest.lib.services.identity.v3.policies_client import PoliciesClient
from tempest.lib.services.identity.v3.projects_client import ProjectsClient
from tempest.lib.services.identity.v3.regions_client import RegionsClient
@@ -43,7 +45,7 @@
__all__ = ['CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient',
'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
- 'OAUTHConsumerClient', 'PoliciesClient', 'ProjectsClient',
- 'RegionsClient', 'RoleAssignmentsClient', 'RolesClient',
- 'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient',
- 'VersionsClient']
+ 'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
+ 'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient',
+ 'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
+ 'UsersClient', 'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/endpoints_client.py b/tempest/lib/services/identity/v3/endpoints_client.py
index 91592de..e24dca7 100644
--- a/tempest/lib/services/identity/v3/endpoints_client.py
+++ b/tempest/lib/services/identity/v3/endpoints_client.py
@@ -18,6 +18,7 @@
"""
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
@@ -25,9 +26,17 @@
class EndPointsClient(rest_client.RestClient):
api_version = "v3"
- def list_endpoints(self):
- """GET endpoints."""
- resp, body = self.get('endpoints')
+ def list_endpoints(self, **params):
+ """List endpoints.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/#list-endpoints
+ """
+ url = 'endpoints'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
new file mode 100644
index 0000000..b1d298b
--- /dev/null
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -0,0 +1,236 @@
+# Copyright 2017 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 binascii
+import hashlib
+import hmac
+import random
+import time
+
+import six
+from six.moves.urllib import parse as urlparse
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class OAUTHTokenClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def _escape(self, s):
+ """Escape a unicode string in an OAuth-compatible fashion."""
+ safe = b'~'
+ s = s.encode('utf-8') if isinstance(s, six.text_type) else s
+ s = urlparse.quote(s, safe)
+ if isinstance(s, six.binary_type):
+ s = s.decode('utf-8')
+ return s
+
+ def _generate_params_with_signature(self, client_key, uri,
+ client_secret=None,
+ resource_owner_key=None,
+ resource_owner_secret=None,
+ callback_uri=None,
+ verifier=None,
+ http_method='GET'):
+ """Generate OAUTH params along with signature."""
+ timestamp = six.text_type(int(time.time()))
+ nonce = six.text_type(random.getrandbits(64)) + timestamp
+ oauth_params = [
+ ('oauth_nonce', nonce),
+ ('oauth_timestamp', timestamp),
+ ('oauth_version', '1.0'),
+ ('oauth_signature_method', 'HMAC-SHA1'),
+ ('oauth_consumer_key', client_key),
+ ]
+ if resource_owner_key:
+ oauth_params.append(('oauth_token', resource_owner_key))
+ if callback_uri:
+ oauth_params.append(('oauth_callback', callback_uri))
+ if verifier:
+ oauth_params.append(('oauth_verifier', verifier))
+
+ # normalize_params
+ key_values = [(self._escape(k), self._escape(v))
+ for k, v in oauth_params]
+ key_values.sort()
+ parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values]
+ normalized_params = '&'.join(parameter_parts)
+
+ # normalize_uri
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
+ scheme = scheme.lower()
+ netloc = netloc.lower()
+ normalized_uri = urlparse.urlunparse((scheme, netloc, path,
+ params, '', ''))
+
+ # construct base string
+ base_string = self._escape(http_method.upper())
+ base_string += '&'
+ base_string += self._escape(normalized_uri)
+ base_string += '&'
+ base_string += self._escape(normalized_params)
+
+ # sign using hmac-sha1
+ key = self._escape(client_secret or '')
+ key += '&'
+ key += self._escape(resource_owner_secret or '')
+ key_utf8 = key.encode('utf-8')
+ text_utf8 = base_string.encode('utf-8')
+ signature = hmac.new(key_utf8, text_utf8, hashlib.sha1)
+ sig = binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
+
+ oauth_params.append(('oauth_signature', sig))
+ return oauth_params
+
+ def _generate_oauth_header(self, oauth_params):
+ authorization_header = {}
+ authorization_header_parameters_parts = []
+ for oauth_parameter_name, value in oauth_params:
+ escaped_name = self._escape(oauth_parameter_name)
+ escaped_value = self._escape(value)
+ part = '{0}="{1}"'.format(escaped_name, escaped_value)
+ authorization_header_parameters_parts.append(part)
+
+ authorization_header_parameters = ', '.join(
+ authorization_header_parameters_parts)
+ oauth_string = 'OAuth %s' % authorization_header_parameters
+ authorization_header['Authorization'] = oauth_string
+
+ return authorization_header
+
+ def create_request_token(self, consumer_key, consumer_secret, project_id):
+ """Create request token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#create-request-token
+ """
+ endpoint = 'OS-OAUTH1/request_token'
+ headers = {'Requested-Project-Id': project_id}
+ oauth_params = self._generate_params_with_signature(
+ consumer_key,
+ self.base_url + '/' + endpoint,
+ client_secret=consumer_secret,
+ callback_uri='oob',
+ http_method='POST')
+ oauth_header = self._generate_oauth_header(oauth_params)
+ headers.update(oauth_header)
+ resp, body = self.post(endpoint,
+ body=None,
+ headers=headers)
+ self.expected_success(201, resp.status)
+ if not isinstance(body, str):
+ body = body.decode('utf-8')
+ body = dict(item.split("=") for item in body.split("&"))
+ return rest_client.ResponseBody(resp, body)
+
+ def authorize_request_token(self, request_token_id, role_ids):
+ """Authorize request token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#authorize-request-token
+ """
+ roles = [{'id': role_id} for role_id in role_ids]
+ body = {'roles': roles}
+ post_body = json.dumps(body)
+ resp, body = self.put("OS-OAUTH1/authorize/%s" % request_token_id,
+ post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_access_token(self, consumer_key, consumer_secret, request_key,
+ request_secret, oauth_verifier):
+ """Create access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#create-access-token
+ """
+ endpoint = 'OS-OAUTH1/access_token'
+ oauth_params = self._generate_params_with_signature(
+ consumer_key,
+ self.base_url + '/' + endpoint,
+ client_secret=consumer_secret,
+ resource_owner_key=request_key,
+ resource_owner_secret=request_secret,
+ verifier=oauth_verifier,
+ http_method='POST')
+ headers = self._generate_oauth_header(oauth_params)
+ resp, body = self.post(endpoint, body=None, headers=headers)
+ self.expected_success(201, resp.status)
+ if not isinstance(body, str):
+ body = body.decode('utf-8')
+ body = dict(item.split("=") for item in body.split("&"))
+ return rest_client.ResponseBody(resp, body)
+
+ def get_access_token(self, user_id, access_token_id):
+ """Get access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#get-access-token
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s"
+ % (user_id, access_token_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_access_token(self, user_id, access_token_id):
+ """Revoke access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#revoke-access-token
+ """
+ resp, body = self.delete("users/%s/OS-OAUTH1/access_tokens/%s"
+ % (user_id, access_token_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_access_tokens(self, user_id):
+ """List access tokens.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#list-access-tokens
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens"
+ % (user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_access_token_roles(self, user_id, access_token_id):
+ """List roles for an access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#list-roles-for-an-access-token
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles"
+ % (user_id, access_token_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_access_token_role(self, user_id, access_token_id, role_id):
+ """Show role details for an access token.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3-ext/#show-role-details-for-an-access-token
+ """
+ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles/%s"
+ % (user_id, access_token_id, role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/base_client.py b/tempest/lib/services/volume/base_client.py
new file mode 100644
index 0000000..c7fb21a
--- /dev/null
+++ b/tempest/lib/services/volume/base_client.py
@@ -0,0 +1,45 @@
+# Copyright 2016 Andrew Kerr
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.common import api_version_utils
+from tempest.lib.common import rest_client
+
+VOLUME_MICROVERSION = None
+
+
+class BaseClient(rest_client.RestClient):
+ """Base volume service clients class to support microversion."""
+ api_microversion_header_name = 'Openstack-Api-Version'
+
+ def get_headers(self, accept_type=None, send_type=None):
+ headers = super(BaseClient, self).get_headers(
+ accept_type=accept_type, send_type=send_type)
+ if VOLUME_MICROVERSION:
+ headers[self.api_microversion_header_name] = ('volume %s' %
+ VOLUME_MICROVERSION)
+ return headers
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, chunked=False):
+
+ resp, resp_body = super(BaseClient, self).request(
+ method, url, extra_headers, headers, body, chunked)
+ if (VOLUME_MICROVERSION and
+ VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
+ api_version_utils.assert_version_header_matches_request(
+ self.api_microversion_header_name,
+ 'volume %s' % VOLUME_MICROVERSION,
+ resp)
+ return resp, resp_body
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index 2b5e82d..a44ed0b 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -17,9 +17,10 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
-class BackupsClient(rest_client.RestClient):
+class BackupsClient(base_client.BaseClient):
"""Volume V2 Backups client"""
api_version = "v2"
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 5f4e7de..4bc2842 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -164,6 +164,14 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ def show_snapshot_metadata_item(self, snapshot_id, id):
+ """Show metadata item for the snapshot."""
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
"""Update metadata item for the snapshot."""
# TODO(piyush): Current api-site doesn't contain this API description.
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index 86e3836..d31259f 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -21,10 +21,11 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
from tempest.lib.services.volume.v2 import transfers_client
-class VolumesClient(rest_client.RestClient):
+class VolumesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
@@ -283,6 +284,14 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ def show_volume_metadata_item(self, volume_id, id):
+ """Show metadata item for the volume."""
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
put_body = json.dumps({'meta': meta_item})
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index a351d61..ff58fc2 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations under
# the License.
+from tempest.lib.services.volume.v3.backups_client import BackupsClient
from tempest.lib.services.volume.v3.base_client import BaseClient
from tempest.lib.services.volume.v3.group_types_client import GroupTypesClient
from tempest.lib.services.volume.v3.groups_client import GroupsClient
@@ -19,5 +20,5 @@
from tempest.lib.services.volume.v3.versions_client import VersionsClient
from tempest.lib.services.volume.v3.volumes_client import VolumesClient
-__all__ = ['BaseClient', 'GroupsClient', 'GroupTypesClient',
+__all__ = ['BackupsClient', 'BaseClient', 'GroupsClient', 'GroupTypesClient',
'MessagesClient', 'VersionsClient', 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
new file mode 100644
index 0000000..e742e39
--- /dev/null
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -0,0 +1,37 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v2 import backups_client
+
+
+class BackupsClient(backups_client.BackupsClient):
+ """Volume V3 Backups client"""
+ api_version = "v3"
+
+ def update_backup(self, backup_id, **kwargs):
+ """Updates the specified volume backup.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#update-a-backup
+ """
+ put_body = json.dumps({'backup': kwargs})
+ resp, body = self.put('backups/%s' % backup_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/base_client.py b/tempest/lib/services/volume/v3/base_client.py
index 958212a..e78380b 100644
--- a/tempest/lib/services/volume/v3/base_client.py
+++ b/tempest/lib/services/volume/v3/base_client.py
@@ -13,34 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.common import api_version_utils
-from tempest.lib.common import rest_client
+from debtcollector import moves
-VOLUME_MICROVERSION = None
+from tempest.lib.services.volume import base_client
-class BaseClient(rest_client.RestClient):
- """Base class to handle Cinder v3 client microversion support."""
- api_version = 'v3'
- api_microversion_header_name = 'Openstack-Api-Version'
-
- def get_headers(self, accept_type=None, send_type=None):
- headers = super(BaseClient, self).get_headers(
- accept_type=accept_type, send_type=send_type)
- if VOLUME_MICROVERSION:
- headers[self.api_microversion_header_name] = ('volume %s' %
- VOLUME_MICROVERSION)
- return headers
-
- def request(self, method, url, extra_headers=False, headers=None,
- body=None, chunked=False):
-
- resp, resp_body = super(BaseClient, self).request(
- method, url, extra_headers, headers, body, chunked)
- if (VOLUME_MICROVERSION and
- VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
- api_version_utils.assert_version_header_matches_request(
- self.api_microversion_header_name,
- 'volume %s' % VOLUME_MICROVERSION,
- resp)
- return resp, resp_body
+BaseClient = moves.moved_class(base_client.BaseClient, 'BaseClient', __name__,
+ version="Pike", removal_version='?')
+BaseClient.api_version = 'v3'
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
index 390d44d..a6edbf5 100644
--- a/tempest/lib/services/volume/v3/group_types_client.py
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -16,11 +16,12 @@
from oslo_serialization import jsonutils as json
from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class GroupTypesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V3 Group Types API requests"""
+ api_version = 'v3'
@property
def resource_type(self):
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index c06997a..9b53bb7 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -18,11 +18,12 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class GroupsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group API requests"""
+ api_version = 'v3'
def create_group(self, **kwargs):
"""Creates a group.
diff --git a/tempest/lib/services/volume/v3/messages_client.py b/tempest/lib/services/volume/v3/messages_client.py
index 8a01864..0127271 100644
--- a/tempest/lib/services/volume/v3/messages_client.py
+++ b/tempest/lib/services/volume/v3/messages_client.py
@@ -17,11 +17,12 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class MessagesClient(base_client.BaseClient):
"""Client class to send user messages API requests."""
+ api_version = 'v3'
def show_message(self, message_id):
"""Show details for a single message."""
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index e2941c4..5702f95 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -18,10 +18,11 @@
from tempest.lib.api_schema.response.volume import versions as schema
from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v3 import base_client
+from tempest.lib.services.volume import base_client
class VersionsClient(base_client.BaseClient):
+ api_version = 'v3'
def list_versions(self):
"""List API versions
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index aec0205..5f4b278 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -18,11 +18,9 @@
from tempest.lib.common import rest_client
from tempest.lib.services.volume.v2 import volumes_client
-from tempest.lib.services.volume.v3 import base_client
-class VolumesClient(base_client.BaseClient,
- volumes_client.VolumesClient):
+class VolumesClient(volumes_client.VolumesClient):
"""Client class to send CRUD Volume V3 API requests"""
api_version = "v3"
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 38e03c7..aecb374 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -1249,6 +1249,17 @@
type_id, provider=provider, key_size=key_size, cipher=cipher,
control_location=control_location)['encryption']
+ def create_encrypted_volume(self, encryption_provider, volume_type,
+ key_size=256, cipher='aes-xts-plain64',
+ control_location='front-end'):
+ volume_type = self.create_volume_type(name=volume_type)
+ self.create_encryption_type(type_id=volume_type['id'],
+ provider=encryption_provider,
+ key_size=key_size,
+ cipher=cipher,
+ control_location=control_location)
+ return self.create_volume(volume_type=volume_type['name'])
+
class ObjectStorageScenarioTest(ScenarioTest):
"""Provide harness to do Object Storage scenario tests.
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index d7b86f6..cbdf307 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -48,15 +48,6 @@
return self.create_server(image_id=image, key_name=keypair['name'])
- def create_encrypted_volume(self, encryption_provider, volume_type):
- volume_type = self.create_volume_type(name=volume_type)
- self.create_encryption_type(type_id=volume_type['id'],
- provider=encryption_provider,
- key_size=256,
- cipher='aes-xts-plain64',
- control_location='front-end')
- return self.create_volume(volume_type=volume_type['name'])
-
def attach_detach_volume(self, server, volume):
attached_volume = self.nova_volume_attach(server, volume)
self.nova_volume_detach(server, attached_volume)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 3dfbf18..96d0474 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -24,7 +24,7 @@
LOG = logging.getLogger(__name__)
-class TestVolumeBootPattern(manager.ScenarioTest):
+class TestVolumeBootPattern(manager.EncryptionScenarioTest):
# Boot from volume scenario is quite slow, and needs extra
# breathing room to get through deletes in the time allotted.
@@ -227,3 +227,26 @@
# delete instance
self._delete_server(instance)
+
+ @decorators.idempotent_id('cb78919a-e553-4bab-b73b-10cf4d2eb125')
+ @testtools.skipIf(CONF.volume.storage_protocol.lower() in ['ceph', 'nfs'],
+ 'Currently, {} does not support volume encryption'
+ .format(CONF.volume.storage_protocol))
+ @test.services('compute', 'volume')
+ def test_boot_server_from_encrypted_volume_luks(self):
+ # Create an encrypted volume
+ volume = self.create_encrypted_volume('nova.volume.encryptors.'
+ 'luks.LuksEncryptor',
+ volume_type='luks')
+
+ self.volumes_client.set_bootable_volume(volume['id'], bootable=True)
+
+ # Boot a server from the encrypted volume
+ server = self._boot_instance_from_resource(
+ source_id=volume['id'],
+ source_type='volume',
+ delete_on_termination=False)
+
+ server_info = self.servers_client.show_server(server['id'])['server']
+ created_volume = server_info['os-extended-volumes:volumes_attached']
+ self.assertEqual(volume['id'], created_volume[0]['id'])
diff --git a/tempest/test.py b/tempest/test.py
index f07c071..fc846ff 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -63,7 +63,14 @@
'compute': CONF.service_available.nova,
'image': CONF.service_available.glance,
'volume': CONF.service_available.cinder,
+ # NOTE(masayukig): We have two network services which are neutron and
+ # nova-network. And we have no way to know whether nova-network is
+ # available or not. After the pending removal of nova-network from
+ # nova, we can treat the network/neutron case in the same manner as
+ # the other services.
'network': True,
+ # NOTE(masayukig): Tempest tests always require the identity service.
+ # So we should set this True here.
'identity': True,
'object_storage': CONF.service_available.swift,
}
@@ -247,6 +254,9 @@
@classmethod
def tearDownClass(cls):
+ # insert pdb breakpoint when pause_teardown is enabled
+ if CONF.pause_teardown:
+ cls.insert_pdb_breakpoint()
at_exit_set.discard(cls)
# It should never be overridden by descendants
if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
@@ -283,6 +293,22 @@
finally:
del trace # to avoid circular refs
+ def tearDown(self):
+ super(BaseTestCase, self).tearDown()
+ # insert pdb breakpoint when pause_teardown is enabled
+ if CONF.pause_teardown:
+ BaseTestCase.insert_pdb_breakpoint()
+
+ @classmethod
+ def insert_pdb_breakpoint(cls):
+ """Add pdb breakpoint.
+
+ This can help in debugging process, cleaning of resources is
+ paused, so they can be examined.
+ """
+ import pdb
+ pdb.set_trace()
+
@classmethod
def skip_checks(cls):
"""Class level skip checks.
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index dc6c0c8..a1c8c53 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -80,13 +80,20 @@
self.assertEqual(
self.workspace_manager.get_workspace(self.name), new_path)
- def test_run_workspace_remove(self):
+ def test_run_workspace_remove_entry(self):
cmd = ['tempest', 'workspace', 'remove',
'--workspace-path', self.store_file,
'--name', self.name]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+ def test_run_workspace_remove_directory(self):
+ cmd = ['tempest', 'workspace', 'remove',
+ '--workspace-path', self.store_file,
+ '--name', self.name, '--rmdir']
+ self._run_cmd_gets_return_code(cmd, 0)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
class TestTempestWorkspaceManager(TestTempestWorkspaceBase):
def setUp(self):
@@ -117,8 +124,13 @@
self.assertEqual(
self.workspace_manager.get_workspace(self.name), new_path)
- def test_workspace_manager_remove(self):
- self.workspace_manager.remove_workspace(self.name)
+ def test_workspace_manager_remove_entry(self):
+ self.workspace_manager.remove_workspace_entry(self.name)
+ self.assertIsNone(self.workspace_manager.get_workspace(self.name))
+
+ def test_workspace_manager_remove_directory(self):
+ path = self.workspace_manager.remove_workspace_entry(self.name)
+ self.workspace_manager.remove_workspace_directory(path)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
def test_path_expansion(self):
diff --git a/tempest/tests/lib/services/base.py b/tempest/tests/lib/services/base.py
index 90c9f63..778c966 100644
--- a/tempest/tests/lib/services/base.py
+++ b/tempest/tests/lib/services/base.py
@@ -40,7 +40,7 @@
function.
:param body: Expected response body returned by the service client
function.
- :param to_utf: Whether to use UTF-8 encoding for request.
+ :param to_utf: Whether to use UTF-8 encoding for response.
:param status: Expected response status returned by the service client
function.
:param headers: Expected headers returned by the service client
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
index f8c553f..ca15dd1 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
@@ -53,6 +53,8 @@
]
}
+ FAKE_SERVICE_ID = "a4dc5060-f757-4662-b658-edd2aefbb41d"
+
def setUp(self):
super(TestEndpointsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -72,12 +74,15 @@
adminurl="https://compute.north.internal.com/v1",
internalurl="https://compute.north.internal.com/v1")
- def _test_list_endpoints(self, bytes_body=False):
+ def _test_list_endpoints(self, bytes_body=False, mock_args='endpoints',
+ **params):
self.check_service_client_function(
self.client.list_endpoints,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_LIST_ENDPOINTS,
- bytes_body)
+ bytes_body,
+ mock_args=[mock_args],
+ **params)
def test_create_endpoint_with_str_body(self):
self._test_create_endpoint()
@@ -91,6 +96,16 @@
def test_list_endpoints_with_bytes_body(self):
self._test_list_endpoints(bytes_body=True)
+ def test_list_endpoints_with_params(self):
+ # Run the test separately for each param, to avoid assertion error
+ # resulting from randomized params order.
+ mock_args = 'endpoints?service_id=%s' % self.FAKE_SERVICE_ID
+ self._test_list_endpoints(mock_args=mock_args,
+ service_id=self.FAKE_SERVICE_ID)
+
+ mock_args = 'endpoints?interface=public'
+ self._test_list_endpoints(mock_args=mock_args, interface='public')
+
def test_delete_endpoint(self):
self.check_service_client_function(
self.client.delete_endpoint,
diff --git a/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py b/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py
new file mode 100644
index 0000000..b9b9b15
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py
@@ -0,0 +1,215 @@
+# Copyright 2017 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.
+
+from oslotest import mockpatch
+
+from tempest.lib.services.identity.v3 import oauth_token_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services import base
+
+
+class TestOAUTHTokenClient(base.BaseServiceTest):
+ FAKE_CREATE_REQUEST_TOKEN = {
+ 'oauth_token': '29971f',
+ 'oauth_token_secret': '238eb8',
+ 'oauth_expires_at': '2013-09-11T06:07:51.501805Z'
+ }
+
+ FAKE_AUTHORIZE_REQUEST_TOKEN = {
+ 'token': {
+ 'oauth_verifier': '8171'
+ }
+ }
+
+ FAKE_CREATE_ACCESS_TOKEN = {
+ 'oauth_token': 'accd36',
+ 'oauth_token_secret': 'aa47da',
+ 'oauth_expires_at': '2013-09-11T06:07:51.501805Z'
+ }
+
+ FAKE_ACCESS_TOKEN_INFO = {
+ 'access_token': {
+ 'consumer_id': '7fea2d',
+ 'id': '6be26a',
+ 'expires_at': '2013-09-11T06:07:51.501805Z',
+ 'links': {
+ 'roles': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles',
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a'
+ },
+ 'project_id': 'b9fca3',
+ 'authorizing_user_id': 'ce9e07'
+ }
+ }
+
+ FAKE_LIST_ACCESS_TOKENS = {
+ 'access_tokens': [
+ {
+ 'consumer_id': '7fea2d',
+ 'id': '6be26a',
+ 'expires_at': '2013-09-11T06:07:51.501805Z',
+ 'links': {
+ 'roles': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/' +
+ '6be26a/roles',
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a'
+ },
+ 'project_id': 'b9fca3',
+ 'authorizing_user_id': 'ce9e07'
+ }
+ ],
+ 'links': {
+ 'next': None,
+ 'previous': None,
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens'
+ }
+ }
+
+ FAKE_LIST_ACCESS_TOKEN_ROLES = {
+ 'roles': [
+ {
+ 'id': '26b860',
+ 'domain_id': 'fake_domain',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/' +
+ 'roles/26b860'
+ },
+ 'name': 'fake_role'
+ }
+ ],
+ 'links': {
+ 'next': None,
+ 'previous': None,
+ 'self': 'http://example.com/identity/v3/' +
+ 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles'
+ }
+ }
+
+ FAKE_ACCESS_TOKEN_ROLE_INFO = {
+ 'role': {
+ 'id': '26b860',
+ 'domain_id': 'fake_domain',
+ 'links': {
+ 'self': 'http://example.com/identity/v3/' +
+ 'roles/26b860'
+ },
+ 'name': 'fake_role'
+ }
+ }
+
+ def setUp(self):
+ super(TestOAUTHTokenClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = oauth_token_client.OAUTHTokenClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _mock_token_response(self, body):
+ temp_response = [key + '=' + value for key, value in body.items()]
+ return '&'.join(temp_response)
+
+ def _test_authorize_request_token(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.authorize_request_token,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_AUTHORIZE_REQUEST_TOKEN,
+ bytes_body,
+ request_token_id=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'],
+ role_ids=['26b860'],
+ status=200)
+
+ def test_create_request_token(self):
+ mock_resp = self._mock_token_response(self.FAKE_CREATE_REQUEST_TOKEN)
+ resp = fake_http.fake_http_response(None, status=201), mock_resp
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.rest_client.RestClient.post',
+ return_value=resp))
+
+ resp = self.client.create_request_token(
+ consumer_key='12345',
+ consumer_secret='23456',
+ project_id='c8f58432c6f00162f04d3250f')
+ self.assertEqual(self.FAKE_CREATE_REQUEST_TOKEN, resp)
+
+ def test_authorize_token_request_with_str_body(self):
+ self._test_authorize_request_token()
+
+ def test_authorize_token_request_with_bytes_body(self):
+ self._test_authorize_request_token(bytes_body=True)
+
+ def test_create_access_token(self):
+ mock_resp = self._mock_token_response(self.FAKE_CREATE_ACCESS_TOKEN)
+ req_secret = self.FAKE_CREATE_REQUEST_TOKEN['oauth_token_secret']
+ resp = fake_http.fake_http_response(None, status=201), mock_resp
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.rest_client.RestClient.post',
+ return_value=resp))
+
+ resp = self.client.create_access_token(
+ consumer_key='12345',
+ consumer_secret='23456',
+ request_key=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'],
+ request_secret=req_secret,
+ oauth_verifier='8171')
+ self.assertEqual(self.FAKE_CREATE_ACCESS_TOKEN, resp)
+
+ def test_get_access_token(self):
+ self.check_service_client_function(
+ self.client.get_access_token,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ACCESS_TOKEN_INFO,
+ user_id='ce9e07',
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ status=200)
+
+ def test_list_access_tokens(self):
+ self.check_service_client_function(
+ self.client.list_access_tokens,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ACCESS_TOKENS,
+ user_id='ce9e07',
+ status=200)
+
+ def test_revoke_access_token(self):
+ self.check_service_client_function(
+ self.client.revoke_access_token,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['consumer_id'],
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ status=204)
+
+ def test_list_access_token_roles(self):
+ self.check_service_client_function(
+ self.client.list_access_token_roles,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ACCESS_TOKEN_ROLES,
+ user_id='ce9e07',
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ status=200)
+
+ def test_get_access_token_role(self):
+ self.check_service_client_function(
+ self.client.get_access_token_role,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ACCESS_TOKEN_ROLE_INFO,
+ user_id='ce9e07',
+ access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'],
+ role_id=self.FAKE_ACCESS_TOKEN_ROLE_INFO['role']['id'],
+ status=200)
diff --git a/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py b/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
new file mode 100644
index 0000000..8a5f25f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py
@@ -0,0 +1,83 @@
+# Copyright 2017 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.
+
+from tempest.lib.services.volume.v2 import scheduler_stats_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSchedulerStatsClient(base.BaseServiceTest):
+ FAKE_POOLS_LIST = {
+ "pools": [
+ {
+ "name": "pool1",
+ "capabilities": {
+ "updated": "2014-10-28T00:00:00-00:00",
+ "total_capacity": 1024,
+ "free_capacity": 100,
+ "volume_backend_name": "pool1",
+ "reserved_percentage": 0,
+ "driver_version": "1.0.0",
+ "storage_protocol": "iSCSI",
+ "QoS_support": False
+ }
+ },
+ {
+ "name": "pool2",
+ "capabilities": {
+ "updated": "2014-10-28T00:00:00-00:00",
+ "total_capacity": 512,
+ "free_capacity": 200,
+ "volume_backend_name": "pool2",
+ "reserved_percentage": 0,
+ "driver_version": "1.0.2",
+ "storage_protocol": "iSER",
+ "QoS_support": True
+ }
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestSchedulerStatsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = scheduler_stats_client.SchedulerStatsClient(
+ fake_auth, 'volume', 'regionOne')
+
+ def _test_list_pools(self, bytes_body=False, detail=False):
+ resp_body = []
+ if detail:
+ resp_body = self.FAKE_POOLS_LIST
+ else:
+ resp_body = {'pools': [{'name': pool['name']}
+ for pool in self.FAKE_POOLS_LIST['pools']]}
+ self.check_service_client_function(
+ self.client.list_pools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp_body,
+ bytes_body,
+ detail=detail)
+
+ def test_list_pools_with_str_body(self):
+ self._test_list_pools()
+
+ def test_list_pools_with_str_body_and_detail(self):
+ self._test_list_pools(detail=True)
+
+ def test_list_pools_with_bytes_body(self):
+ self._test_list_pools(bytes_body=True)
+
+ def test_list_pools_with_bytes_body_and_detail(self):
+ self._test_list_pools(bytes_body=True, detail=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py b/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
index 7d656f1..c9f57a0 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_snapshots_client.py
@@ -72,6 +72,12 @@
]
}
+ FAKE_SNAPSHOT_METADATA_ITEM = {
+ "meta": {
+ "key1": "value1"
+ }
+ }
+
def setUp(self):
super(TestSnapshotsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -142,6 +148,15 @@
self.FAKE_INFO_SNAPSHOT,
bytes_body, volume_type_id="cbc36478b0bd8e67e89")
+ def _test_show_snapshot_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_snapshot_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SNAPSHOT_METADATA_ITEM,
+ bytes_body,
+ snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
+ id="key1")
+
def test_create_snapshot_with_str_body(self):
self._test_create_snapshot()
@@ -184,6 +199,12 @@
def test_update_snapshot_metadata_with_bytes_body(self):
self._test_update_snapshot_metadata(bytes_body=True)
+ def test_show_snapshot_metadata_item_with_str_body(self):
+ self._test_show_snapshot_metadata_item()
+
+ def test_show_snapshot_metadata_item_with_bytes_body(self):
+ self._test_show_snapshot_metadata_item(bytes_body=True)
+
def test_force_delete_snapshot(self):
self.check_service_client_function(
self.client.force_delete_snapshot,
diff --git a/tempest/tests/lib/services/volume/v2/test_transfers_client.py b/tempest/tests/lib/services/volume/v2/test_transfers_client.py
index 0c59bf2..84f4992 100644
--- a/tempest/tests/lib/services/volume/v2/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_transfers_client.py
@@ -13,6 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
+import mock
+from oslo_serialization import jsonutils as json
+
from tempest.lib.services.volume.v2 import transfers_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -20,11 +25,14 @@
class TestTransfersClient(base.BaseServiceTest):
- FAKE_LIST_VOLUME_TRANSFERS_WITH_DETAIL = {
- "transfers": [{
- "created_at": "2017-04-18T09:10:03.000000",
+ FAKE_VOLUME_TRANSFER_ID = "0e89cdd1-6249-421b-96d8-25fac0623d42"
+
+ FAKE_VOLUME_TRANSFER_INFO = {
+ "transfer": {
+ "id": FAKE_VOLUME_TRANSFER_ID,
+ "name": "fake-volume-transfer",
"volume_id": "47bf04ef-1ea5-4c5f-a375-430a086d6747",
- "id": "0e89cdd1-6249-421b-96d8-25fac0623d42",
+ "created_at": "2017-04-18T09:10:03.000000",
"links": [
{
"href": "fake-url-1",
@@ -34,9 +42,8 @@
"href": "fake-url-2",
"rel": "bookmark"
}
- ],
- "name": "fake-volume-transfer"
- }]
+ ]
+ }
}
def setUp(self):
@@ -46,16 +53,106 @@
'volume',
'regionOne')
- def _test_list_volume_transfers_with_detail(self, bytes_body=False):
+ def _test_create_volume_transfer(self, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
+ resp_body['transfer'].update({"auth_key": "fake-auth-key"})
+ kwargs = {"name": "fake-volume-transfer",
+ "volume_id": "47bf04ef-1ea5-4c5f-a375-430a086d6747"}
+ payload = json.dumps({"transfer": kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.create_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ resp_body,
+ to_utf=bytes_body,
+ status=202,
+ mock_args=['os-volume-transfer', payload],
+ **kwargs)
+
+ def _test_accept_volume_transfer(self, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
+ resp_body['transfer'].pop('created_at')
+ kwargs = {"auth_key": "fake-auth-key"}
+ payload = json.dumps({"accept": kwargs}, sort_keys=True)
+ json_dumps = json.dumps
+
+ # NOTE: Use sort_keys for json.dumps so that the expected and actual
+ # payloads are guaranteed to be identical for mock_args assert check.
+ with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
+ mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+ self.check_service_client_function(
+ self.client.accept_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ resp_body,
+ to_utf=bytes_body,
+ status=202,
+ mock_args=['os-volume-transfer/%s/accept' %
+ self.FAKE_VOLUME_TRANSFER_ID, payload],
+ transfer_id=self.FAKE_VOLUME_TRANSFER_ID,
+ **kwargs)
+
+ def _test_show_volume_transfer(self, bytes_body=False):
+ resp_body = self.FAKE_VOLUME_TRANSFER_INFO
+ self.check_service_client_function(
+ self.client.show_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ resp_body,
+ to_utf=bytes_body,
+ transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42")
+
+ def _test_list_volume_transfers(self, detail=False, bytes_body=False):
+ resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO)
+ if not detail:
+ resp_body['transfer'].pop('created_at')
+ resp_body = {"transfers": [resp_body['transfer']]}
self.check_service_client_function(
self.client.list_volume_transfers,
'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_LIST_VOLUME_TRANSFERS_WITH_DETAIL,
- bytes_body,
- detail=True)
+ resp_body,
+ to_utf=bytes_body,
+ detail=detail)
+
+ def test_create_volume_transfer_with_str_body(self):
+ self._test_create_volume_transfer()
+
+ def test_create_volume_transfer_with_bytes_body(self):
+ self._test_create_volume_transfer(bytes_body=True)
+
+ def test_accept_volume_transfer_with_str_body(self):
+ self._test_accept_volume_transfer()
+
+ def test_accept_volume_transfer_with_bytes_body(self):
+ self._test_accept_volume_transfer(bytes_body=True)
+
+ def test_show_volume_transfer_with_str_body(self):
+ self._test_show_volume_transfer()
+
+ def test_show_volume_transfer_with_bytes_body(self):
+ self._test_show_volume_transfer(bytes_body=True)
+
+ def test_list_volume_transfers_with_str_body(self):
+ self._test_list_volume_transfers()
+
+ def test_list_volume_transfers_with_bytes_body(self):
+ self._test_list_volume_transfers(bytes_body=True)
def test_list_volume_transfers_with_detail_with_str_body(self):
- self._test_list_volume_transfers_with_detail()
+ self._test_list_volume_transfers(detail=True)
def test_list_volume_transfers_with_detail_with_bytes_body(self):
- self._test_list_volume_transfers_with_detail(bytes_body=True)
+ self._test_list_volume_transfers(detail=True, bytes_body=True)
+
+ def test_delete_volume_transfer(self):
+ self.check_service_client_function(
+ self.client.delete_volume_transfer,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=202,
+ transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42")
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
index 498b963..befb1f6 100644
--- a/tempest/tests/lib/services/volume/v2/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
@@ -20,6 +20,12 @@
class TestVolumesClient(base.BaseServiceTest):
+ FAKE_VOLUME_METADATA_ITEM = {
+ "meta": {
+ "key1": "value1"
+ }
+ }
+
def setUp(self):
super(TestVolumesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -45,8 +51,23 @@
**kwargs
)
+ def _test_show_volume_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_METADATA_ITEM,
+ to_utf=bytes_body,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ id="key1")
+
def test_force_detach_volume_with_str_body(self):
self._test_force_detach_volume()
def test_force_detach_volume_with_bytes_body(self):
self._test_force_detach_volume(bytes_body=True)
+
+ def test_show_volume_metadata_item_with_str_body(self):
+ self._test_show_volume_metadata_item()
+
+ def test_show_volume_metadata_item_with_bytes_body(self):
+ self._test_show_volume_metadata_item(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
new file mode 100644
index 0000000..f1ce987
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -0,0 +1,50 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.volume.v3 import backups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestBackupsClient(base.BaseServiceTest):
+
+ FAKE_BACKUP_UPDATE = {
+ "backup": {
+ "id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7",
+ "name": "fake-backup-name",
+ "links": "fake-links"
+ }
+ }
+
+ def setUp(self):
+ super(TestBackupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = backups_client.BackupsClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_update_backup(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_backup,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_BACKUP_UPDATE,
+ bytes_body,
+ backup_id='4c65c15f-a5c5-464b-b92a-90e4c04636a7')
+
+ def test_update_backup_with_str_body(self):
+ self._test_update_backup()
+
+ def test_update_backup_with_bytes_body(self):
+ self._test_update_backup(bytes_body=True)
diff --git a/test-requirements.txt b/test-requirements.txt
index f8793be..6a5ea03 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,10 +4,9 @@
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
# needed for doc build
sphinx>=1.6.2 # BSD
-oslosphinx>=4.7.0 # Apache-2.0
+openstackdocstheme>=1.11.0 # Apache-2.0
reno!=2.3.1,>=1.8.0 # Apache-2.0
mock>=2.0 # BSD
coverage!=4.4,>=4.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
flake8-import-order==0.11 # LGPLv3
-openstackdocstheme>=1.11.0 # Apache-2.0
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index a33962b..99df0d1 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -28,10 +28,13 @@
try:
# For Python 3.0 and later
- import urllib
+ from urllib.error import HTTPError as HTTPError
+ import urllib.request as urllib
except ImportError:
# Fall back to Python 2's urllib2
import urllib2 as urllib
+ from urllib2 import HTTPError as HTTPError
+
url = 'https://review.openstack.org/projects/'
@@ -55,13 +58,13 @@
def has_tempest_plugin(proj):
try:
- r = urllib.urlopen("https://git.openstack.org/cgit/%s/plain/setup.cfg"
- % proj)
- except urllib.HTTPError as err:
+ r = urllib.urlopen(
+ "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+ except HTTPError as err:
if err.code == 404:
return False
p = re.compile('^tempest\.test_plugins', re.M)
- if p.findall(r.read()):
+ if p.findall(r.read().decode('utf-8')):
return True
else:
False