Merge "Making delete_on_termination configurable"
diff --git a/README.rst b/README.rst
index 725a890..53c7de5 100644
--- a/README.rst
+++ b/README.rst
@@ -1,13 +1,8 @@
Tempest - The OpenStack Integration Test Suite
==============================================
-.. image:: https://img.shields.io/pypi/v/tempest.svg
- :target: https://pypi.python.org/pypi/tempest/
- :alt: Latest Version
-
-.. image:: https://img.shields.io/pypi/dm/tempest.svg
- :target: https://pypi.python.org/pypi/tempest/
- :alt: Downloads
+The documentation for Tempest is officially hosted at:
+http://docs.openstack.org/developer/tempest/
This is a set of integration tests to be run against a live OpenStack
cluster. Tempest has batteries of tests for OpenStack API validation,
@@ -63,19 +58,21 @@
This can be done within a venv, but the assumption for this guide is that
the Tempest cli entry point will be in your shell's PATH.
-#. Installing Tempest will create a /etc/tempest dir which will contain the
- sample config file packaged with Tempest. The contents of /etc/tempest will
- be copied to all local working dirs, so if there is any common configuration
- you'd like to be shared between anyone setting up local Tempest working dirs
- it's recommended that you copy or rename tempest.conf.sample to tempest.conf
- and make those changes to that file in /etc/tempest
+#. Installing Tempest may create a /etc/tempest dir, however if one isn't
+ created you can create one or use ~/.tempest/etc or ~/.config/tempest in
+ place of /etc/tempest. If none of these dirs are created tempest will create
+ ~/.tempest/etc when it's needed. The contents of this dir will always
+ automatically be copied to all etc/ dirs in local workspaces as an initial
+ setup step. So if there is any common configuration you'd like to be shared
+ between local Tempest workspaces it's recommended that you pre-populate it
+ before running ``tempest init``.
-#. Setup a local working Tempest dir. This is done by using the tempest init
+#. Setup a local Tempest workspace. This is done by using the tempest init
command::
$ tempest init cloud-01
- works the same as::
+ which also works the same as::
$ mkdir cloud-01 && cd cloud-01 && tempest init
@@ -88,11 +85,23 @@
config files located in the etc/ subdir created by the ``tempest init``
command. Tempest is expecting a tempest.conf file in etc/ so if only a
sample exists you must rename or copy it to tempest.conf before making
- any changes to it otherwise Tempest will not know how to load it.
+ any changes to it otherwise Tempest will not know how to load it. For
+ details on configuring tempest refer to the :ref:`tempest-configuration`.
#. Once the configuration is done you're now ready to run Tempest. This can
- be done with testr directly or any `testr`_ based test runner, like
- `ostestr`_. For example, from the working dir running::
+ be done using the :ref:`tempest_run` command. This can be done by either
+ running::
+
+ $ tempest run
+
+ from the Tempest workspace directory. Or you can use the ``--workspace``
+ argument to run in the workspace you created regarless of your current
+ working directory. For example::
+
+ $ tempest run --workspace cloud-01
+
+ There is also the option to use testr directly, or any `testr`_ based test
+ runner, like `ostestr`_. For example, from the workspace dir run::
$ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
diff --git a/doc/source/run.rst b/doc/source/run.rst
index 07fa5f7..ce7f03e 100644
--- a/doc/source/run.rst
+++ b/doc/source/run.rst
@@ -1,3 +1,5 @@
+.. _tempest_run:
+
-----------
Tempest Run
-----------
diff --git a/requirements.txt b/requirements.txt
index 7d01f69..84be219 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,7 +14,7 @@
oslo.i18n>=2.1.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
-oslo.utils>=3.11.0 # Apache-2.0
+oslo.utils>=3.14.0 # Apache-2.0
six>=1.9.0 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 607bebe..bee77df 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -13,10 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
@@ -52,6 +57,37 @@
self.assertEqual(project_name, body['name'])
self.assertEqual(self.data.domain['id'], body['domain_id'])
+ @testtools.skipUnless(CONF.identity_feature_enabled.reseller,
+ 'Reseller not available.')
+ @test.idempotent_id('1854f9c0-70bc-4d11-a08a-1c789d339e3d')
+ def test_project_create_with_parent(self):
+ # Create root project without providing a parent_id
+ self.data.setup_test_domain()
+ domain_id = self.data.domain['id']
+
+ root_project_name = data_utils.rand_name('root_project')
+ root_project = self.projects_client.create_project(
+ root_project_name, domain_id=domain_id)['project']
+ self.addCleanup(
+ self.projects_client.delete_project, root_project['id'])
+
+ root_project_id = root_project['id']
+ parent_id = root_project['parent_id']
+ self.assertEqual(root_project_name, root_project['name'])
+ # If not provided, the parent_id must point to the top level
+ # project in the hierarchy, i.e. its domain
+ self.assertEqual(domain_id, parent_id)
+
+ # Create a project using root_project_id as parent_id
+ project_name = data_utils.rand_name('project')
+ project = self.projects_client.create_project(
+ project_name, domain_id=domain_id,
+ parent_id=root_project_id)['project']
+ self.addCleanup(self.projects_client.delete_project, project['id'])
+ parent_id = project['parent_id']
+ self.assertEqual(project_name, project['name'])
+ self.assertEqual(root_project_id, parent_id)
+
@test.idempotent_id('1f66dc76-50cc-4741-a200-af984509e480')
def test_project_create_enabled(self):
# Create a project that is enabled
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 7a1e3a5..df39390 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -218,21 +218,21 @@
def teardown_all(self):
for user in self.users:
test_utils.call_and_ignore_notfound_exc(
- self.users_client.delete_user, user)
+ self.users_client.delete_user, user['id'])
for tenant in self.tenants:
test_utils.call_and_ignore_notfound_exc(
- self.projects_client.delete_tenant, tenant)
+ self.projects_client.delete_tenant, tenant['id'])
for project in reversed(self.projects):
test_utils.call_and_ignore_notfound_exc(
- self.projects_client.delete_project, project)
+ self.projects_client.delete_project, project['id'])
for role in self.roles:
test_utils.call_and_ignore_notfound_exc(
- self.roles_client.delete_role, role)
+ self.roles_client.delete_role, role['id'])
for domain in self.domains:
test_utils.call_and_ignore_notfound_exc(
- self.domains_client.update_domain, domain, enabled=False)
+ self.domains_client.update_domain, domain['id'], enabled=False)
test_utils.call_and_ignore_notfound_exc(
- self.domains_client.delete_domain, domain)
+ self.domains_client.delete_domain, domain['id'])
class DataGeneratorV2(BaseDataGenerator):
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index b2892e5..92dfc56 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -14,6 +14,7 @@
# under the License.
import netaddr
+import six
from tempest.api.network import base
from tempest import config
@@ -90,7 +91,8 @@
body = self.ports_client.update_port(
port_id, allowed_address_pairs=allowed_address_pairs)
allowed_address_pair = body['port']['allowed_address_pairs']
- self.assertEqual(allowed_address_pair, allowed_address_pairs)
+ six.assertCountEqual(self, allowed_address_pair,
+ allowed_address_pairs)
@test.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
def test_update_port_with_address_pair(self):
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 66bab51..b6dc488 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -27,17 +27,17 @@
CONF = config.CONF
-class VolumesBackupsV2Test(base.BaseVolumeAdminTest):
+class VolumesBackupsAdminV2Test(base.BaseVolumeAdminTest):
@classmethod
def skip_checks(cls):
- super(VolumesBackupsV2Test, cls).skip_checks()
+ super(VolumesBackupsAdminV2Test, cls).skip_checks()
if not CONF.volume_feature_enabled.backup:
raise cls.skipException("Cinder backup feature disabled")
@classmethod
def resource_setup(cls):
- super(VolumesBackupsV2Test, cls).resource_setup()
+ super(VolumesBackupsAdminV2Test, cls).resource_setup()
cls.volume = cls.create_volume()
@@ -167,5 +167,5 @@
'available')
-class VolumesBackupsV1Test(VolumesBackupsV2Test):
+class VolumesBackupsAdminV1Test(VolumesBackupsAdminV2Test):
_api_version = 1
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index e9be529..087b9a8 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -76,6 +76,7 @@
else:
cls.snapshots_client = cls.os.snapshots_v2_client
cls.volumes_client = cls.os.volumes_v2_client
+ cls.backups_client = cls.os.backups_v2_client
cls.volumes_extension_client = cls.os.volumes_v2_extension_client
cls.availability_zone_client = (
cls.os.volume_v2_availability_zone_client)
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
new file mode 100644
index 0000000..87146db
--- /dev/null
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -0,0 +1,72 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest.common.utils import data_utils
+from tempest.common import waiters
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class VolumesBackupsV2Test(base.BaseVolumeTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesBackupsV2Test, cls).skip_checks()
+ if not CONF.volume_feature_enabled.backup:
+ raise cls.skipException("Cinder backup feature disabled")
+
+ @classmethod
+ def resource_setup(cls):
+ super(VolumesBackupsV2Test, cls).resource_setup()
+
+ cls.volume = cls.create_volume()
+
+ @test.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6')
+ @test.services('compute')
+ def test_backup_create_attached_volume(self):
+ """Test backup create using force flag.
+
+ Cinder allows to create a volume backup, whether the volume status
+ is "available" or "in-use".
+ """
+ # Create a server
+ server_name = data_utils.rand_name('instance')
+ server = self.create_server(name=server_name, wait_until='ACTIVE')
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ # Attach volume to instance
+ self.servers_client.attach_volume(server['id'],
+ volumeId=self.volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client,
+ self.volume['id'], 'in-use')
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
+ self.volume['id'], 'available')
+ self.addCleanup(self.servers_client.detach_volume, server['id'],
+ self.volume['id'])
+ # Create backup using force flag
+ backup_name = data_utils.rand_name('Backup')
+ backup = self.backups_client.create_backup(
+ volume_id=self.volume['id'],
+ name=backup_name, force=True)['backup']
+ self.addCleanup(self.backups_client.delete_backup, backup['id'])
+ self.backups_client.wait_for_backup_status(backup['id'],
+ 'available')
+ self.assertEqual(backup_name, backup['name'])
+
+
+class VolumesBackupsV1Test(VolumesBackupsV2Test):
+ _api_version = 1
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 8777c0e..2eb122e 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -64,6 +64,14 @@
directly with you current working directory being the workspace, Tempest will
take care of managing everything to be executed from there.
+Running from Anywhere
+---------------------
+Tempest run provides you with an option to execute tempest from anywhere on
+your system. You are required to provide a config file in this case with the
+``--config-file`` option. When run tempest will create a .testrepository
+directory and a .testr.conf file in your current working directory. This way
+you can use testr commands directly to inspect the state of the previous run.
+
Test Output
===========
By default tempest run's output to STDOUT will be generated using the
@@ -83,6 +91,7 @@
from oslo_log import log as logging
from testrepository.commands import run_argv
+from tempest.cmd import init
from tempest.cmd import workspace
from tempest import config
@@ -93,7 +102,9 @@
class TempestRun(command.Command):
- def _set_env(self):
+ def _set_env(self, config_file=None):
+ if config_file:
+ CONF.set_config_path(os.path.abspath(config_file))
# NOTE(mtreinish): This is needed so that testr doesn't gobble up any
# stacktraces on failure.
if 'TESTR_PDB' in os.environ:
@@ -108,9 +119,19 @@
if returncode:
sys.exit(returncode)
+ def _create_testr_conf(self):
+ top_level_path = os.path.dirname(os.path.dirname(__file__))
+ discover_path = os.path.join(top_level_path, 'test_discover')
+ file_contents = init.TESTR_CONF % (top_level_path, discover_path)
+ with open('.testr.conf', 'w+') as testr_conf_file:
+ testr_conf_file.write(file_contents)
+
def take_action(self, parsed_args):
- self._set_env()
returncode = 0
+ if parsed_args.config_file:
+ self._set_env(parsed_args.config_file)
+ else:
+ self._set_env()
# Workspace execution mode
if parsed_args.workspace:
workspace_mgr = workspace.WorkspaceManager(
@@ -126,6 +147,10 @@
# If you're running in local execution mode and there is not a
# testrepository dir create one
self._create_testrepository()
+ # local execution with config file mode
+ elif parsed_args.config_file:
+ self._create_testr_conf()
+ self._create_testrepository()
else:
print("No .testr.conf file was found for local execution")
sys.exit(2)
@@ -157,6 +182,9 @@
dest='workspace_path',
help="The path to the workspace file, the default "
"is ~/.tempest/workspace.yaml")
+ # Configuration flags
+ parser.add_argument('--config-file', default=None, dest='config_file',
+ help='Configuration file to run tempest with')
# test selection args
regex = parser.add_mutually_exclusive_group()
regex.add_argument('--smoke', action='store_true',
@@ -177,7 +205,7 @@
parser.add_argument('--list-tests', '-l', action='store_true',
help='List tests',
default=False)
- # exectution args
+ # execution args
parser.add_argument('--concurrency', '-w',
help="The number of workers to use, defaults to "
"the number of cpus")
diff --git a/tempest/common/fixed_network.py b/tempest/common/fixed_network.py
index 5f0685e..f57c18a 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/common/fixed_network.py
@@ -14,7 +14,7 @@
from oslo_log import log as logging
from tempest import exceptions
-from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib.common.utils import test_utils
LOG = logging.getLogger(__name__)
@@ -31,7 +31,7 @@
list returns a 404, there are no found networks, or the found network
is invalid
"""
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if not name:
raise exceptions.InvalidTestResource(type='network', name=name)
@@ -84,7 +84,7 @@
tenant network is available in the creds provider
:returns: a dict with 'id' and 'name' of the network
"""
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
net_creds = creds_provider.get_primary_creds()
network = getattr(net_creds, 'network', None)
if not network or not network.get('name'):
diff --git a/tempest/common/image.py b/tempest/common/image.py
index 72e3a72..95a7d1a 100644
--- a/tempest/common/image.py
+++ b/tempest/common/image.py
@@ -47,6 +47,11 @@
fields_copy = copy.deepcopy(metadata)
copy_from = fields_copy.pop('copy_from', None)
+ purge = fields_copy.pop('purge_props', None)
+
+ if purge is not None:
+ headers['x-glance-registry-purge-props'] = purge
+
if copy_from is not None:
headers['x-glance-api-copy-from'] = copy_from
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index e083167..df08e30 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -18,7 +18,7 @@
from tempest.common import image as common_image
from tempest import config
from tempest import exceptions
-from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.image.v1 import images_client as images_v1_client
@@ -91,7 +91,7 @@
'timeout': timeout})
message += ' Current status: %s.' % server_status
message += ' Current task state: %s.' % task_state
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
@@ -162,7 +162,7 @@
'status': status,
'current_status': current_status,
'timeout': client.build_timeout})
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
@@ -235,7 +235,7 @@
'status': status,
'timeout': client.build_timeout})
message += ' Current state of %s: %s.' % (attr, status_curr)
- caller = misc_utils.find_test_caller()
+ caller = test_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
diff --git a/tempest/config.py b/tempest/config.py
index eb5e23a..228d7e3 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -191,7 +191,12 @@
help="A list of enabled identity extensions with a special "
"entry all which indicates every extension is enabled. "
"Empty list indicates all extensions are disabled. "
- "To get the list of extensions run: 'keystone discover'")
+ "To get the list of extensions run: 'keystone discover'"),
+ # TODO(rodrigods): Remove the reseller flag when Kilo and Liberty is end
+ # of life.
+ cfg.BoolOpt('reseller',
+ default=False,
+ help='Does the environment support reseller?')
]
compute_group = cfg.OptGroup(name='compute',
@@ -366,7 +371,7 @@
default=True,
help='Does the test environment have the nova cert running?'),
cfg.BoolOpt('personality',
- default=True,
+ default=False,
help='Does the test environment support server personality'),
cfg.BoolOpt('attach_encrypted_volume',
default=True,
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 3289f04..44497db 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -174,7 +174,16 @@
'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
- 'os-extended-volumes:volumes_attached': {'type': 'array'},
+ 'os-extended-volumes:volumes_attached': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ },
+ },
'config_drive': {'type': 'string'}
})
server_detail['properties']['addresses']['patternProperties'][
diff --git a/tempest/lib/services/image/v2/image_members_client.py b/tempest/lib/services/image/v2/image_members_client.py
index 2ae7516..d0ab165 100644
--- a/tempest/lib/services/image/v2/image_members_client.py
+++ b/tempest/lib/services/image/v2/image_members_client.py
@@ -19,6 +19,11 @@
api_version = "v2"
def list_image_members(self, image_id):
+ """List image members.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#listImageMembers-v2
+ """
url = 'images/%s/members' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
@@ -52,12 +57,22 @@
return rest_client.ResponseBody(resp, body)
def show_image_member(self, image_id, member_id):
+ """Show an image member.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#showImageMember-v2
+ """
url = 'images/%s/members/%s' % (image_id, member_id)
resp, body = self.get(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, json.loads(body))
def delete_image_member(self, image_id, member_id):
+ """Delete an image member.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#deleteImageMember-v2
+ """
url = 'images/%s/members/%s' % (image_id, member_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 71e7c6b..4276847 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -115,18 +115,33 @@
return rest_client.ResponseBody(resp, body)
def show_image_file(self, image_id):
+ """Show an image file.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#showImageFile-v2
+ """
url = 'images/%s/file' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
return rest_client.ResponseBodyData(resp, body)
def add_image_tag(self, image_id, tag):
+ """Add an image tag.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#addImageTag-v2
+ """
url = 'images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_image_tag(self, image_id, tag):
+ """Delete an image tag.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#deleteImageTag-v2
+ """
url = 'images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/image/v2/namespaces_client.py b/tempest/lib/services/image/v2/namespaces_client.py
index 97400e1..5bd096d 100644
--- a/tempest/lib/services/image/v2/namespaces_client.py
+++ b/tempest/lib/services/image/v2/namespaces_client.py
@@ -58,6 +58,11 @@
return rest_client.ResponseBody(resp, body)
def delete_namespace(self, namespace):
+ """Delete a namespace.
+
+ Available params: http://developer.openstack.org/
+ api-ref-image-v2.html#deleteNamespace-v2
+ """
url = 'metadefs/namespaces/%s' % namespace
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
old mode 100644
new mode 100755
index 24c2ec5..19fa1db
--- a/tempest/lib/services/network/networks_client.py
+++ b/tempest/lib/services/network/networks_client.py
@@ -16,11 +16,21 @@
class NetworksClient(base.BaseNetworkClient):
def create_network(self, **kwargs):
+ """Creates a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#createNetwork
+ """
uri = '/networks'
post_data = {'network': kwargs}
return self.create_resource(uri, post_data)
def update_network(self, network_id, **kwargs):
+ """Updates a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#updateNetwork
+ """
uri = '/networks/%s' % network_id
post_data = {'network': kwargs}
return self.update_resource(uri, post_data)
diff --git a/tempest/lib/services/network/subnets_client.py b/tempest/lib/services/network/subnets_client.py
old mode 100644
new mode 100755
index 63ed13e..9de4a33
--- a/tempest/lib/services/network/subnets_client.py
+++ b/tempest/lib/services/network/subnets_client.py
@@ -16,11 +16,21 @@
class SubnetsClient(base.BaseNetworkClient):
def create_subnet(self, **kwargs):
+ """Creates a subnet on a network.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#createSubnet
+ """
uri = '/subnets'
post_data = {'subnet': kwargs}
return self.create_resource(uri, post_data)
def update_subnet(self, subnet_id, **kwargs):
+ """Updates a subnet.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#updateSubnet
+ """
uri = '/subnets/%s' % subnet_id
post_data = {'subnet': kwargs}
return self.update_resource(uri, post_data)
diff --git a/tempest/services/identity/v2/json/roles_client.py b/tempest/services/identity/v2/json/roles_client.py
deleted file mode 100644
index 15c8834..0000000
--- a/tempest/services/identity/v2/json/roles_client.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# 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 six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-
-
-class RolesClient(rest_client.RestClient):
- api_version = "v2.0"
-
- def create_role(self, **kwargs):
- """Create a role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#createRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.post('OS-KSADM/roles', post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_role(self, role_id_or_name):
- """Get a role by its id or name.
-
- Available params: see
- http://developer.openstack.org/
- api-ref-identity-v2-ext.html#showRoleByID
- OR
- http://developer.openstack.org/
- api-ref-identity-v2-ext.html#showRoleByName
- """
- resp, body = self.get('OS-KSADM/roles/%s' % role_id_or_name)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_roles(self, **params):
- """Returns roles.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#listRoles
- """
- url = 'OS-KSADM/roles'
- 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)
-
- def delete_role(self, role_id):
- """Delete a role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#deleteRole
- """
- resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_user_role_on_project(self, tenant_id, user_id, role_id):
- """Add roles to a user on a tenant.
-
- Available params: see
- http://developer.openstack.org/
- api-ref-identity-v2-ext.html#grantRoleToUserOnTenant
- """
- resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
- (tenant_id, user_id, role_id), "")
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_user_roles_on_project(self, tenant_id, user_id, **params):
- """Returns a list of roles assigned to a user for a tenant."""
- # TODO(gmann): Need to write API-ref link, Bug# 1592711
- url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id)
- 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)
-
- def delete_role_from_user_on_project(self, tenant_id, user_id, role_id):
- """Removes a role assignment for a user on a tenant.
-
- Available params: see
- http://developer.openstack.org/
- api-ref-identity-v2-ext.html#revokeRoleFromUserOnTenant
- """
- resp, body = self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
- (tenant_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/tenants_client.py b/tempest/services/identity/v2/json/tenants_client.py
deleted file mode 100644
index 77ddaa5..0000000
--- a/tempest/services/identity/v2/json/tenants_client.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# Copyright 2015 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-
-
-class TenantsClient(rest_client.RestClient):
- api_version = "v2.0"
-
- def create_tenant(self, **kwargs):
- """Create a tenant
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#createTenant
- """
- post_body = json.dumps({'tenant': kwargs})
- resp, body = self.post('tenants', post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_tenant(self, tenant_id):
- """Delete a tenant.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#deleteTenant
- """
- resp, body = self.delete('tenants/%s' % str(tenant_id))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_tenant(self, tenant_id):
- """Get tenant details.
-
- Available params: see
- http://developer.openstack.org/
- api-ref-identity-v2-ext.html#admin-showTenantById
- """
- resp, body = self.get('tenants/%s' % str(tenant_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_tenants(self, **params):
- """Returns tenants.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#admin-listTenants
- """
- url = 'tenants'
- 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)
-
- def update_tenant(self, tenant_id, **kwargs):
- """Updates a tenant.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#updateTenant
- """
- if 'id' not in kwargs:
- kwargs['id'] = tenant_id
- post_body = json.dumps({'tenant': kwargs})
- resp, body = self.post('tenants/%s' % tenant_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_tenant_users(self, tenant_id, **params):
- """List users for a Tenant.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#listUsersForTenant
- """
- url = '/tenants/%s/users' % tenant_id
- 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/services/identity/v2/json/users_client.py b/tempest/services/identity/v2/json/users_client.py
deleted file mode 100644
index 4ea17f9..0000000
--- a/tempest/services/identity/v2/json/users_client.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# 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 six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-
-
-class UsersClient(rest_client.RestClient):
- api_version = "v2.0"
-
- def create_user(self, **kwargs):
- """Create a user.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-createUser
- """
- post_body = json.dumps({'user': kwargs})
- resp, body = self.post('users', post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_user(self, user_id, **kwargs):
- """Updates a user.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-updateUser
- """
- put_body = json.dumps({'user': kwargs})
- resp, body = self.put('users/%s' % user_id, put_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_user(self, user_id):
- """GET a user.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-showUser
- """
- resp, body = self.get("users/%s" % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_user(self, user_id):
- """Delete a user.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-deleteUser
- """
- resp, body = self.delete("users/%s" % user_id)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_users(self, **params):
- """Get the list of users.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-admin-v2.html#admin-listUsers
- """
- url = "users"
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_user_enabled(self, user_id, **kwargs):
- """Enables or disables a user.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v2-ext.html#enableUser
- """
- # NOTE: The URL (users/<id>/enabled) is different from the api-site
- # one (users/<id>/OS-KSADM/enabled) , but they are the same API
- # because of the fact that in keystone/contrib/admin_crud/core.py
- # both api use same action='set_user_enabled'
- put_body = json.dumps({'user': kwargs})
- resp, body = self.put('users/%s/enabled' % user_id, put_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_user_password(self, user_id, **kwargs):
- """Update User Password."""
- # TODO(piyush): Current api-site doesn't contain this API description.
- # After fixing the api-site, we need to fix here also for putting the
- # link to api-site.
- # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524147
- put_body = json.dumps({'user': kwargs})
- resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_user_own_password(self, user_id, **kwargs):
- """User updates own password"""
- # TODO(piyush): Current api-site doesn't contain this API description.
- # After fixing the api-site, we need to fix here also for putting the
- # link to api-site.
- # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524153
- # NOTE: This API is used for updating user password by itself.
- # Ref: http://lists.openstack.org/pipermail/openstack-dev/2015-December
- # /081803.html
- patch_body = json.dumps({'user': kwargs})
- resp, body = self.patch('OS-KSCRUD/users/%s' % user_id, patch_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def create_user_ec2_credential(self, user_id, **kwargs):
- # TODO(piyush): Current api-site doesn't contain this API description.
- # After fixing the api-site, we need to fix here also for putting the
- # link to api-site.
- post_body = json.dumps(kwargs)
- resp, body = self.post('/users/%s/credentials/OS-EC2' % user_id,
- post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_user_ec2_credential(self, user_id, access):
- resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' %
- (user_id, access))
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_user_ec2_credentials(self, user_id):
- resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_user_ec2_credential(self, user_id, access):
- resp, body = self.get('/users/%s/credentials/OS-EC2/%s' %
- (user_id, access))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/common/test_image.py b/tempest/tests/common/test_image.py
index 34772a2..240df4d 100644
--- a/tempest/tests/common/test_image.py
+++ b/tempest/tests/common/test_image.py
@@ -46,7 +46,8 @@
disk_format='vhd',
copy_from='http://localhost/images/10',
properties={'foo': 'bar'},
- api={'abc': 'def'})
+ api={'abc': 'def'},
+ purge_props=True)
expected = {
'x-image-meta-name': 'test',
@@ -54,6 +55,7 @@
'x-image-meta-disk_format': 'vhd',
'x-glance-api-copy-from': 'http://localhost/images/10',
'x-image-meta-property-foo': 'bar',
- 'x-glance-api-property-abc': 'def'
+ 'x-glance-api-property-abc': 'def',
+ 'x-glance-registry-purge-props': True
}
self.assertEqual(expected, observed)