Merge "Allow user to be disabled"
diff --git a/.gitignore b/.gitignore
index d58b162..5b87cec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,6 @@
!.coveragerc
cover/
doc/source/_static/tempest.conf.sample
+
+# Files created by releasenotes build
+releasenotes/build
diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/notes/.placeholder
diff --git a/releasenotes/notes/start-using-reno-ed9518126fd0e1a3.yaml b/releasenotes/notes/start-using-reno-ed9518126fd0e1a3.yaml
new file mode 100644
index 0000000..0bc9af5
--- /dev/null
+++ b/releasenotes/notes/start-using-reno-ed9518126fd0e1a3.yaml
@@ -0,0 +1,3 @@
+---
+other:
+ - Start using reno for managing release notes.
diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/source/_static/.placeholder
diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/source/_templates/.placeholder
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 0000000..4522a17
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,277 @@
+# -*- coding: utf-8 -*-
+# 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.
+
+# tempest Release Notes documentation build configuration file, created by
+# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'oslosphinx',
+ 'reno.sphinxext',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'tempest Release Notes'
+copyright = u'2016, tempest Developers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+from tempest.version import version_info as tempest_version
+# The full version, including alpha/beta/rc tags.
+release = tempest_version.version_string_with_vcs()
+# The short X.Y version.
+version = tempest_version.canonical_version_string()
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'tempestReleaseNotesdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ ('index', 'olso.configReleaseNotes.tex',
+ u'olso.config Release Notes Documentation',
+ u'tempest Developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'olso.configreleasenotes',
+ u'tempest Release Notes Documentation',
+ [u'tempest Developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'tempestReleaseNotes',
+ u'tempest Release Notes Documentation',
+ u'tempest Developers', 'olso.configReleaseNotes',
+ 'An OpenStack library for parsing configuration options from the command'
+ ' line and configuration files.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# texinfo_no_detailmenu = False
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 0000000..584540b
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,14 @@
+===========================
+ tempest Release Notes
+===========================
+
+ .. toctree::
+ :maxdepth: 1
+
+ unreleased
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 0000000..5860a46
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+==========================
+ Unreleased Release Notes
+==========================
+
+.. release-notes::
diff --git a/requirements.txt b/requirements.txt
index 66e5696..46ebbc5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,7 +21,7 @@
iso8601>=0.1.9 # MIT
fixtures>=1.3.1 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
-tempest-lib>=0.14.0 # Apache-2.0
PyYAML>=3.1.0 # MIT
stevedore>=1.5.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
+os-testr>=0.4.1 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index cc3a365..0ddb898 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -32,6 +32,8 @@
tempest-cleanup = tempest.cmd.cleanup:main
tempest-account-generator = tempest.cmd.account_generator:main
tempest = tempest.cmd.main:main
+ skip-tracker = tempest.lib.cmd.skip_tracker:main
+ check-uuid = tempest.lib.cmd.check_uuid:run
tempest.cm =
account-generator = tempest.cmd.account_generator:TempestAccountGenerator
init = tempest.cmd.init:TempestInit
diff --git a/tempest/api/baremetal/admin/base.py b/tempest/api/baremetal/admin/base.py
index 80b69b9..f7891dd 100644
--- a/tempest/api/baremetal/admin/base.py
+++ b/tempest/api/baremetal/admin/base.py
@@ -12,10 +12,9 @@
import functools
-from tempest_lib import exceptions as lib_exc
-
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/baremetal/admin/test_chassis.py b/tempest/api/baremetal/admin/test_chassis.py
index 29fc64c..339aaea 100644
--- a/tempest/api/baremetal/admin/test_chassis.py
+++ b/tempest/api/baremetal/admin/test_chassis.py
@@ -12,10 +12,10 @@
# under the License.
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/baremetal/admin/test_nodes.py b/tempest/api/baremetal/admin/test_nodes.py
index b6dee18..8cc4d05 100644
--- a/tempest/api/baremetal/admin/test_nodes.py
+++ b/tempest/api/baremetal/admin/test_nodes.py
@@ -11,11 +11,11 @@
# under the License.
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/baremetal/admin/test_ports.py b/tempest/api/baremetal/admin/test_ports.py
index 180b848..ce519c1 100644
--- a/tempest/api/baremetal/admin/test_ports.py
+++ b/tempest/api/baremetal/admin/test_ports.py
@@ -11,10 +11,10 @@
# under the License.
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/baremetal/admin/test_ports_negative.py b/tempest/api/baremetal/admin/test_ports_negative.py
index 610758a..8f04db9 100644
--- a/tempest/api/baremetal/admin/test_ports_negative.py
+++ b/tempest/api/baremetal/admin/test_ports_negative.py
@@ -10,10 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 9f7bbae..671b139 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -13,10 +13,10 @@
# under the License.
from oslo_log import log
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
LOG = log.getLogger(__name__)
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 1d83fec..a2b1e2f 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 181533b..6b75aee 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_availability_zone_negative.py b/tempest/api/compute/admin/test_availability_zone_negative.py
index fe979d4..c60de1e 100644
--- a/tempest/api/compute/admin/test_availability_zone_negative.py
+++ b/tempest/api/compute/admin/test_availability_zone_negative.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index 8d745c9..05bba21 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 1ef8f67..96dedcf 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 5070fd7..3854973 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index 14646e8..b0ab9be 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index 65ada4d..8366945 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 0e8012a..f313f76 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
index eea3103..b908502 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -16,9 +16,9 @@
import datetime
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 3ad9305..b1c42a6 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -19,6 +19,7 @@
from tempest.api.compute import base
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
@@ -129,6 +130,8 @@
def test_live_block_migration_paused(self):
self._test_live_migration(state='PAUSED')
+ @decorators.skip_because(bug="1549511",
+ condition=CONF.service_available.neutron)
@test.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
@test.services('volume')
def test_volume_backed_live_migration(self):
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index bdbfde4..d6aba5b 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
index 74f3caa..ce350b6 100644
--- a/tempest/api/compute/admin/test_security_group_default_rules.py
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -12,11 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 49c7318..3eb6d94 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -12,13 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.compute import base
from tempest.common import compute
from tempest.common import fixed_network
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import decorators
from tempest import test
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 23b8a6c..07a7a30 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -14,7 +14,6 @@
import uuid
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
@@ -22,6 +21,7 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -68,7 +68,11 @@
flavor_id = self._get_unused_flavor_id()
quota_set = (self.quotas_client.show_default_quota_set(self.tenant_id)
['quota_set'])
- ram = int(quota_set['ram']) + 1
+ ram = int(quota_set['ram'])
+ if ram == -1:
+ raise self.skipException("default ram quota set is -1,"
+ " cannot test overlimit")
+ ram += 1
vcpus = 8
disk = 10
flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
@@ -93,7 +97,11 @@
ram = 512
quota_set = (self.quotas_client.show_default_quota_set(self.tenant_id)
['quota_set'])
- vcpus = int(quota_set['cores']) + 1
+ vcpus = int(quota_set['cores'])
+ if vcpus == -1:
+ raise self.skipException("default cores quota set is -1,"
+ " cannot test overlimit")
+ vcpus += 1
disk = 10
flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
ram=ram, vcpus=vcpus,
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index e57401a..710cac4 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index e5c17ca..8986db8 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -16,8 +16,8 @@
import datetime
from tempest.api.compute import base
+from tempest.lib import exceptions as e
from tempest import test
-from tempest_lib import exceptions as e
# Time that waits for until returning valid response
# TODO(takmatsu): Ideally this value would come from configuration.
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
index e9b4ad4..315116e 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
@@ -14,9 +14,9 @@
# under the License.
import datetime
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/api_microversion_fixture.py b/tempest/api/compute/api_microversion_fixture.py
new file mode 100644
index 0000000..bf4de3e
--- /dev/null
+++ b/tempest/api/compute/api_microversion_fixture.py
@@ -0,0 +1,31 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+
+from tempest.services.compute.json import base_compute_client
+
+
+class APIMicroversionFixture(fixtures.Fixture):
+
+ def __init__(self, compute_microversion):
+ self.compute_microversion = compute_microversion
+
+ def _setUp(self):
+ super(APIMicroversionFixture, self)._setUp()
+ base_compute_client.COMPUTE_MICROVERSION = self.compute_microversion
+ self.addCleanup(self._reset_compute_microversion)
+
+ def _reset_compute_microversion(self):
+ base_compute_client.COMPUTE_MICROVERSION = None
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 0856983..ee21284 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -16,14 +16,15 @@
import time
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
+from tempest.api.compute import api_microversion_fixture
from tempest.common import api_version_utils
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -56,13 +57,6 @@
@classmethod
def setup_credentials(cls):
cls.set_network_resources()
- cls.request_microversion = (
- api_version_utils.select_request_microversion(
- cls.min_microversion,
- CONF.compute_feature_enabled.min_microversion))
- if cls.request_microversion:
- cls.services_microversion = {
- CONF.compute.catalog_type: cls.request_microversion}
super(BaseV2ComputeTest, cls).setup_credentials()
@classmethod
@@ -108,6 +102,10 @@
@classmethod
def resource_setup(cls):
super(BaseV2ComputeTest, cls).resource_setup()
+ cls.request_microversion = (
+ api_version_utils.select_request_microversion(
+ cls.min_microversion,
+ CONF.compute_feature_enabled.min_microversion))
cls.build_interval = CONF.compute.build_interval
cls.build_timeout = CONF.compute.build_timeout
cls.image_ref = CONF.compute.image_ref
@@ -371,6 +369,11 @@
else:
raise exceptions.InvalidConfiguration()
+ def setUp(self):
+ super(BaseV2ComputeTest, self).setUp()
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+ self.request_microversion))
+
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
"""Base test case class for Compute Admin API tests."""
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 5b90641..85fd4ab 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.floating_ips import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index 0223c0d..105c4e3 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -15,11 +15,10 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.floating_ips import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index 75b6b55..c6c7347 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -15,11 +15,10 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 85d137b..9cb9e03 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index 8706566..63ba650 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 2fc9ef8..d9b80e1 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -15,12 +15,12 @@
# under the License.
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/images/test_list_image_filters_negative.py b/tempest/api/compute/images/test_list_image_filters_negative.py
index 34d26e2..2689f88 100644
--- a/tempest/api/compute/images/test_list_image_filters_negative.py
+++ b/tempest/api/compute/images/test_list_image_filters_negative.py
@@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index 0ab78fb..2a6139b 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.keypairs import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index 773bf23..66bc241 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 816038a..853ef31 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.security_groups import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 81a02be..f6353c8 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute.security_groups import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 120d327..5125e2b 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -13,13 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute.security_groups import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index be79163..4fb4e9a 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -15,11 +15,10 @@
import time
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_instance_actions_negative.py b/tempest/api/compute/servers/test_instance_actions_negative.py
index ac66d05..54ec6aa 100644
--- a/tempest/api/compute/servers/test_instance_actions_negative.py
+++ b/tempest/api/compute/servers/test_instance_actions_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 37f322f..0b33d66 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -13,15 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.api import utils
from tempest.common import fixed_network
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index f205ddf..b18789e 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -14,10 +14,10 @@
# under the License.
from six import moves
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_multiple_create_negative.py b/tempest/api/compute/servers/test_multiple_create_negative.py
index 3d8a732..e5b4f46 100644
--- a/tempest/api/compute/servers/test_multiple_create_negative.py
+++ b/tempest/api/compute/servers/test_multiple_create_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 66e85a6..5b4417a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -16,8 +16,6 @@
import logging
from six.moves.urllib import parse as urlparse
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
@@ -25,6 +23,8 @@
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_server_addresses_negative.py b/tempest/api/compute/servers/test_server_addresses_negative.py
index 3503dc2..b4753e1 100644
--- a/tempest/api/compute/servers/test_server_addresses_negative.py
+++ b/tempest/api/compute/servers/test_server_addresses_negative.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index 18d80be..cbe70e2 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index dad8e90..74d34a2 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -14,13 +14,13 @@
# under the License.
import base64
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 5afb4d1..8d63b6b 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -13,13 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 681b5db..0df6ead 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -15,13 +15,13 @@
import sys
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 7aa6d34..b3e138f 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -14,10 +14,10 @@
# under the License.
import netaddr
-from tempest_lib import decorators
from tempest.api.compute import base
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
index 577a673..e038b82 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces_negative.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -15,9 +15,8 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index bf4396d..133502c 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -16,11 +16,11 @@
import six
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/test_live_block_migration_negative.py b/tempest/api/compute/test_live_block_migration_negative.py
index 2cd85f2..dc57396 100644
--- a/tempest/api/compute/test_live_block_migration_negative.py
+++ b/tempest/api/compute/test_live_block_migration_negative.py
@@ -13,12 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 01a0baf..d1c48c4 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -15,11 +15,10 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index 8424db0..164caaf 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -16,10 +16,10 @@
import copy
import six
-from tempest_lib import exceptions as lib_exc
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
diff --git a/tempest/api/database/flavors/test_flavors_negative.py b/tempest/api/database/flavors/test_flavors_negative.py
index 3dee96f..cd2981b 100644
--- a/tempest/api/database/flavors/test_flavors_negative.py
+++ b/tempest/api/database/flavors/test_flavors_negative.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.database import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index 23a1958..14f4306 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v2/test_services.py b/tempest/api/identity/admin/v2/test_services.py
index 5685922..fe83759 100644
--- a/tempest/api/identity/admin/v2/test_services.py
+++ b/tempest/api/identity/admin/v2/test_services.py
@@ -14,10 +14,10 @@
# under the License.
from six import moves
-from tempest_lib import exceptions as lib_exc
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v2/test_tenant_negative.py b/tempest/api/identity/admin/v2/test_tenant_negative.py
index a02dbc1..a4c1afc 100644
--- a/tempest/api/identity/admin/v2/test_tenant_negative.py
+++ b/tempest/api/identity/admin/v2/test_tenant_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 0a5d0c9..dee42b7 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index 3dc3cb6..ff53187 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -10,12 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import auth
-
from tempest.api.identity import base
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import auth
from tempest import manager
from tempest import test
@@ -32,14 +31,14 @@
def _delete_domain(self, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
- self.client.update_domain(domain_id, enabled=False)
- self.client.delete_domain(domain_id)
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
@test.idempotent_id('d6110661-6a71-49a7-a453-b5e26640ff6d')
def test_default_project_id(self):
# create a domain
dom_name = data_utils.rand_name('dom')
- domain_body = self.client.create_domain(dom_name)['domain']
+ domain_body = self.domains_client.create_domain(dom_name)['domain']
dom_id = domain_body['id']
self.addCleanup(self._delete_domain, dom_id)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 1729dc9..27ff15d 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -26,11 +26,11 @@
def _delete_domain(self, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
- self.client.update_domain(domain_id, enabled=False)
- self.client.delete_domain(domain_id)
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
# Asserting that the domain is not found in the list
# after deletion
- body = self.client.list_domains()['domains']
+ body = self.domains_client.list_domains()['domains']
domains_list = [d['id'] for d in body]
self.assertNotIn(domain_id, domains_list)
@@ -40,14 +40,14 @@
domain_ids = list()
fetched_ids = list()
for _ in range(3):
- domain = self.client.create_domain(
+ domain = self.domains_client.create_domain(
data_utils.rand_name('domain'),
description=data_utils.rand_name('domain-desc'))['domain']
# Delete the domain at the end of this method
self.addCleanup(self._delete_domain, domain['id'])
domain_ids.append(domain['id'])
# List and Verify Domains
- body = self.client.list_domains()['domains']
+ body = self.domains_client.list_domains()['domains']
for d in body:
fetched_ids.append(d['id'])
missing_doms = [d for d in domain_ids if d not in fetched_ids]
@@ -58,7 +58,7 @@
def test_create_update_delete_domain(self):
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
- domain = self.client.create_domain(
+ domain = self.domains_client.create_domain(
d_name, description=d_desc)['domain']
self.addCleanup(self._delete_domain, domain['id'])
self.assertIn('id', domain)
@@ -73,7 +73,7 @@
new_desc = data_utils.rand_name('new-desc')
new_name = data_utils.rand_name('new-name')
- updated_domain = self.client.update_domain(
+ updated_domain = self.domains_client.update_domain(
domain['id'], name=new_name, description=new_desc)['domain']
self.assertIn('id', updated_domain)
self.assertIn('description', updated_domain)
@@ -85,7 +85,8 @@
self.assertEqual(new_desc, updated_domain['description'])
self.assertEqual(True, updated_domain['enabled'])
- fetched_domain = self.client.show_domain(domain['id'])['domain']
+ fetched_domain = self.domains_client.show_domain(
+ domain['id'])['domain']
self.assertEqual(new_name, fetched_domain['name'])
self.assertEqual(new_desc, fetched_domain['description'])
self.assertEqual(True, fetched_domain['enabled'])
@@ -95,9 +96,9 @@
# Create domain with enabled status as false
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
- domain = self.client.create_domain(
+ domain = self.domains_client.create_domain(
d_name, description=d_desc, enabled=False)['domain']
- self.addCleanup(self.client.delete_domain, domain['id'])
+ self.addCleanup(self.domains_client.delete_domain, domain['id'])
self.assertEqual(d_name, domain['name'])
self.assertFalse(domain['enabled'])
self.assertEqual(d_desc, domain['description'])
@@ -106,7 +107,7 @@
def test_create_domain_without_description(self):
# Create domain only with name
d_name = data_utils.rand_name('domain')
- domain = self.client.create_domain(d_name)['domain']
+ domain = self.domains_client.create_domain(d_name)['domain']
self.addCleanup(self._delete_domain, domain['id'])
self.assertIn('id', domain)
expected_data = {'name': d_name, 'enabled': True}
@@ -124,6 +125,6 @@
@test.attr(type='smoke')
@test.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
def test_default_domain_exists(self):
- domain = self.client.show_domain(self.domain_id)['domain']
+ domain = self.domains_client.show_domain(self.domain_id)['domain']
self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 9eb3149..4330cab 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -14,11 +14,10 @@
# under the License.
from tempest.api.identity import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions as lib_exc
-
class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
@@ -28,45 +27,46 @@
def test_delete_active_domain(self):
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
- domain = self.client.create_domain(d_name,
- description=d_desc)['domain']
+ domain = self.domains_client.create_domain(
+ d_name,
+ description=d_desc)['domain']
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
# domain need to be disabled before deleting
- self.assertRaises(lib_exc.Forbidden, self.client.delete_domain,
+ self.assertRaises(lib_exc.Forbidden, self.domains_client.delete_domain,
domain_id)
@test.attr(type=['negative'])
@test.idempotent_id('9018461d-7d24-408d-b3fe-ae37e8cd5c9e')
def test_create_domain_with_empty_name(self):
# Domain name should not be empty
- self.assertRaises(lib_exc.BadRequest, self.client.create_domain,
- name='')
+ self.assertRaises(lib_exc.BadRequest,
+ self.domains_client.create_domain, name='')
@test.attr(type=['negative'])
@test.idempotent_id('37b1bbf2-d664-4785-9a11-333438586eae')
def test_create_domain_with_name_length_over_64(self):
# Domain name length should not ne greater than 64 characters
d_name = 'a' * 65
- self.assertRaises(lib_exc.BadRequest, self.client.create_domain,
- d_name)
+ self.assertRaises(lib_exc.BadRequest,
+ self.domains_client.create_domain, d_name)
@test.attr(type=['negative'])
@test.idempotent_id('43781c07-764f-4cf2-a405-953c1916f605')
def test_delete_non_existent_domain(self):
# Attempt to delete a non existent domain should fail
- self.assertRaises(lib_exc.NotFound, self.client.delete_domain,
+ self.assertRaises(lib_exc.NotFound, self.domains_client.delete_domain,
data_utils.rand_uuid_hex())
@test.attr(type=['negative'])
@test.idempotent_id('e6f9e4a2-4f36-4be8-bdbc-4e199ae29427')
def test_domain_create_duplicate(self):
domain_name = data_utils.rand_name('domain-dup')
- domain = self.client.create_domain(domain_name)['domain']
+ domain = self.domains_client.create_domain(domain_name)['domain']
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
# Domain name should be unique
self.assertRaises(
- lib_exc.Conflict, self.client.create_domain, domain_name)
+ lib_exc.Conflict, self.domains_client.create_domain, domain_name)
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 372254f..b16605e 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_projects_negative.py b/tempest/api/identity/admin/v3/test_projects_negative.py
index 79cfc91..fb4a8cf 100644
--- a/tempest/api/identity/admin/v3/test_projects_negative.py
+++ b/tempest/api/identity/admin/v3/test_projects_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index 8bba3cb..ece36b9 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 468f169..a0a842c 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -32,7 +32,7 @@
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
cls.u_password = data_utils.rand_password()
- cls.domain = cls.client.create_domain(
+ cls.domain = cls.domains_client.create_domain(
data_utils.rand_name('domain'),
description=data_utils.rand_name('domain-desc'))['domain']
cls.project = cls.projects_client.create_project(
@@ -57,8 +57,8 @@
cls.projects_client.delete_project(cls.project['id'])
# NOTE(harika-vakadi): It is necessary to disable the domain
# before deleting,or else it would result in unauthorized error
- cls.client.update_domain(cls.domain['id'], enabled=False)
- cls.client.delete_domain(cls.domain['id'])
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
super(RolesV3TestJSON, cls).resource_cleanup()
def _list_assertions(self, body, fetched_role_ids, role_id):
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index c6e3df4..2c3cae5 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 531ff56..63ba158 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 180e695..d31514c 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -14,13 +14,13 @@
import re
from oslo_utils import timeutils
-from tempest_lib import exceptions as lib_exc
from tempest.api.identity import base
from tempest import clients
from tempest.common import credentials_factory as common_creds
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -97,7 +97,7 @@
tenant_name=self.trustor_project_name,
project_domain_id='default')
os = clients.Manager(credentials=creds)
- self.trustor_client = os.identity_v3_client
+ self.trustor_client = os.trusts_client
def cleanup_user_and_roles(self):
if self.trustor_user_id:
@@ -264,7 +264,7 @@
@test.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
def test_get_trusts_all(self):
self.create_trust()
- trusts_get = self.client.list_trusts()['trusts']
+ trusts_get = self.trusts_client.list_trusts()['trusts']
trusts = [t for t in trusts_get
if t['id'] == self.trust_id]
self.assertEqual(1, len(trusts))
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index 9bc4b9c..1375db1 100644
--- a/tempest/api/identity/admin/v3/test_users_negative.py
+++ b/tempest/api/identity/admin/v3/test_users_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 46dcb83..849b761 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -14,10 +14,10 @@
# under the License.
from oslo_log import log as logging
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -150,7 +150,9 @@
def setup_clients(cls):
super(BaseIdentityV3AdminTest, cls).setup_clients()
cls.client = cls.os_adm.identity_v3_client
+ cls.domains_client = cls.os_adm.domains_client
cls.users_client = cls.os_adm.users_v3_client
+ cls.trusts_client = cls.os_adm.trusts_client
cls.token = cls.os_adm.token_v3_client
cls.endpoints_client = cls.os_adm.endpoints_client
cls.regions_client = cls.os_adm.regions_client
@@ -164,7 +166,7 @@
def resource_setup(cls):
super(BaseIdentityV3AdminTest, cls).resource_setup()
cls.data = DataGeneratorV3(cls.client, cls.projects_client,
- cls.users_client)
+ cls.users_client, None, cls.domains_client)
@classmethod
def resource_cleanup(cls):
@@ -186,18 +188,19 @@
def delete_domain(self, domain_id):
# NOTE(mpavlase) It is necessary to disable the domain before deleting
# otherwise it raises Forbidden exception
- self.client.update_domain(domain_id, enabled=False)
- self.client.delete_domain(domain_id)
+ self.domains_client.update_domain(domain_id, enabled=False)
+ self.domains_client.delete_domain(domain_id)
class BaseDataGenerator(object):
def __init__(self, client, projects_client,
- users_client, roles_client=None):
+ users_client, roles_client=None, domains_client=None):
self.client = client
self.projects_client = projects_client
self.users_client = users_client
self.roles_client = roles_client or client
+ self.domains_client = domains_client
self.user_password = None
self.user = None
@@ -246,8 +249,9 @@
for role in self.roles:
self._try_wrapper(self.roles_client.delete_role, role)
for domain in self.domains:
- self._try_wrapper(self.client.update_domain, domain, enabled=False)
- self._try_wrapper(self.client.delete_domain, domain)
+ self._try_wrapper(self.domains_client.update_domain, domain,
+ enabled=False)
+ self._try_wrapper(self.domains_client.delete_domain, domain)
class DataGeneratorV2(BaseDataGenerator):
@@ -281,7 +285,7 @@
def setup_test_domain(self):
"""Set up a test domain."""
- self.domain = self.client.create_domain(
+ self.domain = self.domains_client.create_domain(
name=data_utils.rand_name('test_domain'),
description=data_utils.rand_name('desc'))['domain']
self.domains.append(self.domain)
diff --git a/tempest/api/identity/v2/test_ec2_credentials.py b/tempest/api/identity/v2/test_ec2_credentials.py
index bd49326..8600980 100644
--- a/tempest/api/identity/v2/test_ec2_credentials.py
+++ b/tempest/api/identity/v2/test_ec2_credentials.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/v2/test_tenants.py b/tempest/api/identity/v2/test_tenants.py
index 4e31557..b742e69 100644
--- a/tempest/api/identity/v2/test_tenants.py
+++ b/tempest/api/identity/v2/test_tenants.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index a59a1a0..ce4ee69 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -16,10 +16,9 @@
import copy
import time
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
-
from tempest.api.identity import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import manager
from tempest import test
diff --git a/tempest/api/identity/v3/test_projects.py b/tempest/api/identity/v3/test_projects.py
index b42cf43..7049bcf 100644
--- a/tempest/api/identity/v3/test_projects.py
+++ b/tempest/api/identity/v3/test_projects.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.identity import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 29396a8..ab48c07 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -16,10 +16,9 @@
import copy
import time
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
-
from tempest.api.identity import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import manager
from tempest import test
diff --git a/tempest/api/image/admin/v2/test_images.py b/tempest/api/image/admin/v2/test_images.py
index b171da3..80da7a1 100644
--- a/tempest/api/image/admin/v2/test_images.py
+++ b/tempest/api/image/admin/v2/test_images.py
@@ -14,13 +14,13 @@
# under the License.
from six import moves
-from tempest_lib.common.utils import data_utils
import testtools
from tempest.api.image import base
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
CONF = config.CONF
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index ade7b67..0683936 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -13,10 +13,10 @@
# under the License.
from six import moves
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index 50f5048..16a4ba6 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
index f16b80e..babee74 100644
--- a/tempest/api/image/v1/test_images_negative.py
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index eb90719..388eb08 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -10,9 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/test_images_metadefs_namespaces.py
index efb7b8b..de8299e 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespaces.py
@@ -15,8 +15,8 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
class MetadataNamespacesTest(base.BaseV2ImageTest):
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index 485942e..fc74326 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -16,9 +16,8 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index a3f4ca8..1aa2d11 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -14,10 +14,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/network/admin/test_external_networks_negative.py b/tempest/api/network/admin/test_external_networks_negative.py
index d031108..57b144a 100644
--- a/tempest/api/network/admin/test_external_networks_negative.py
+++ b/tempest/api/network/admin/test_external_networks_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 47da08c..a1a881d 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -14,8 +14,8 @@
# under the License.
from tempest.api.network import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
class QuotasNegativeTest(base.BaseAdminNetworkTest):
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index 8b32a94..d72e960 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -17,8 +17,8 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
class QuotasTest(base.BaseAdminNetworkTest):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index f209f89..1e636f1 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -14,11 +14,11 @@
# under the License.
import netaddr
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index dbb0d14..f59ecff 100644
--- a/tempest/api/network/test_dhcp_ipv6.py
+++ b/tempest/api/network/test_dhcp_ipv6.py
@@ -17,11 +17,11 @@
import random
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
@@ -66,8 +66,8 @@
body = self.ports_client.list_ports()
ports = body['ports']
for port in ports:
- if (port['device_owner'].startswith('network:router_interface')
- and port['device_id'] in [r['id'] for r in self.routers]):
+ if (port['device_owner'].startswith('network:router_interface') and
+ port['device_id'] in [r['id'] for r in self.routers]):
self.client.remove_router_interface(port['device_id'],
port_id=port['id'])
else:
diff --git a/tempest/api/network/test_floating_ips_negative.py b/tempest/api/network/test_floating_ips_negative.py
index f915615..963d99d 100644
--- a/tempest/api/network/test_floating_ips_negative.py
+++ b/tempest/api/network/test_floating_ips_negative.py
@@ -14,11 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 1c446ef..ffe0336 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -16,12 +16,12 @@
import netaddr
import six
-from tempest_lib import exceptions as lib_exc
from tempest.api.network import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/network/test_networks_negative.py b/tempest/api/network/test_networks_negative.py
index 0ef96a6..d87c2b6 100644
--- a/tempest/api/network/test_networks_negative.py
+++ b/tempest/api/network/test_networks_negative.py
@@ -14,10 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 7b07d42..84dbd8d 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -14,11 +14,11 @@
# under the License.
import netaddr
-from tempest_lib import exceptions as lib_exc
from tempest.api.network import base_routers as base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index ff38e9e..5213c18 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.network import base_security_groups as base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/network/test_service_type_management.py b/tempest/api/network/test_service_type_management.py
index ad1ecc4..f49f082 100644
--- a/tempest/api/network/test_service_type_management.py
+++ b/tempest/api/network/test_service_type_management.py
@@ -10,9 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.network import base
+from tempest.lib import decorators
from tempest import test
diff --git a/tempest/api/network/test_subnetpools_extensions.py b/tempest/api/network/test_subnetpools_extensions.py
index e5d0462..d027132 100644
--- a/tempest/api/network/test_subnetpools_extensions.py
+++ b/tempest/api/network/test_subnetpools_extensions.py
@@ -15,8 +15,8 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
CONF = config.CONF
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 2621581..044e8c1 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.common import custom_matchers
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index aee17d3..546bb06 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index 998c2bd..254a9b3 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -12,10 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 3bb47f0..0055bf9 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 896352b..01e5389 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 1cc9437..9d043e5 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -13,9 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib.common.utils import data_utils
-
from tempest.api.object_storage import base
+from tempest.lib.common.utils import data_utils
from tempest import test
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 18593f3..5b3ce79 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 0e39b7e..2a5cec6 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -16,13 +16,13 @@
import time
from six.moves.urllib import parse as urlparse
-from tempest_lib import decorators
import testtools
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 1c9d582..9db8bde 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -13,11 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
import time
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/object_storage/test_object_formpost_negative.py b/tempest/api/object_storage/test_object_formpost_negative.py
index 7d9e115..cb13271 100644
--- a/tempest/api/object_storage/test_object_formpost_negative.py
+++ b/tempest/api/object_storage/test_object_formpost_negative.py
@@ -17,10 +17,10 @@
import time
from six.moves.urllib import parse as urlparse
-from tempest_lib import exceptions as lib_exc
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 5811cb8..752f0b4 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -15,11 +15,11 @@
import hashlib
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
# Each segment, except for the final one, must be at least 1 megabyte
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index 6d06143..38fe697 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -17,10 +17,10 @@
import time
from six.moves.urllib import parse as urlparse
-from tempest_lib import exceptions as lib_exc
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index c93b5ed..26ace81 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -12,11 +12,11 @@
import os.path
-from tempest_lib import exceptions as lib_exc
import yaml
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/orchestration/stacks/test_limits.py b/tempest/api/orchestration/stacks/test_limits.py
index 315b3e0..d85aa96 100644
--- a/tempest/api/orchestration/stacks/test_limits.py
+++ b/tempest/api/orchestration/stacks/test_limits.py
@@ -10,11 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 09e863e..001bc08 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -30,6 +30,8 @@
@classmethod
def skip_checks(cls):
+ msg = "Skipped until Bug: 1547261 is resolved."
+ raise cls.skipException(msg)
super(NeutronResourcesTestJSON, cls).skip_checks()
if not CONF.service_available.neutron:
raise cls.skipException("Neutron support is required")
diff --git a/tempest/api/orchestration/stacks/test_soft_conf.py b/tempest/api/orchestration/stacks/test_soft_conf.py
index ab45929..6a4e2b9 100644
--- a/tempest/api/orchestration/stacks/test_soft_conf.py
+++ b/tempest/api/orchestration/stacks/test_soft_conf.py
@@ -10,11 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/orchestration/stacks/test_templates_negative.py b/tempest/api/orchestration/stacks/test_templates_negative.py
index 4bd0f33..24e10dd 100644
--- a/tempest/api/orchestration/stacks/test_templates_negative.py
+++ b/tempest/api/orchestration/stacks/test_templates_negative.py
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index e51551b..37e68ef 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -10,11 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/telemetry/base.py b/tempest/api/telemetry/base.py
index ff06810..7238098 100644
--- a/tempest/api/telemetry/base.py
+++ b/tempest/api/telemetry/base.py
@@ -13,13 +13,13 @@
import time
from oslo_utils import timeutils
-from tempest_lib import exceptions as lib_exc
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/telemetry/test_alarming_api.py b/tempest/api/telemetry/test_alarming_api.py
index daa0939..586bb42 100644
--- a/tempest/api/telemetry/test_alarming_api.py
+++ b/tempest/api/telemetry/test_alarming_api.py
@@ -10,10 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.telemetry import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/telemetry/test_alarming_api_negative.py b/tempest/api/telemetry/test_alarming_api_negative.py
index e945556..0701b54 100644
--- a/tempest/api/telemetry/test_alarming_api_negative.py
+++ b/tempest/api/telemetry/test_alarming_api_negative.py
@@ -14,8 +14,8 @@
from tempest.api.telemetry import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
import uuid
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
index a575125..53d457f 100644
--- a/tempest/api/telemetry/test_telemetry_notification_api.py
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -10,11 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
import testtools
from tempest.api.telemetry import base
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index 9185553..a43ee8e 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index fb42633..47130de 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
+from tempest.lib import decorators
from tempest.api.volume import base
from tempest import config
diff --git a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
index c66207f..b7f70ba 100644
--- a/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index 6483af3..29ce2e7 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -15,10 +15,9 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index bc32fc9..bccf20e 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -15,9 +15,8 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 4b2d3f3..30c6a15 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import decorators
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import decorators
from tempest import test
CONF = config.CONF
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cc906e5..82dc2c9 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,12 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index ad6f556..1b5e72a 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -15,11 +15,10 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
from tempest import test
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index d46c9b5..54459ac 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -12,11 +12,10 @@
import uuid
-from tempest_lib import exceptions as lib_exc
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
diff --git a/tempest/clients.py b/tempest/clients.py
index 2a8a4ae..e85a683 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -16,81 +16,81 @@
import copy
from oslo_log import log as logging
-from tempest_lib.services.compute.agents_client import AgentsClient
-from tempest_lib.services.compute.aggregates_client import AggregatesClient
-from tempest_lib.services.compute.availability_zone_client import \
- AvailabilityZoneClient
-from tempest_lib.services.compute.baremetal_nodes_client import \
- BaremetalNodesClient
-from tempest_lib.services.compute.certificates_client import \
- CertificatesClient
-from tempest_lib.services.compute.extensions_client import \
- ExtensionsClient
-from tempest_lib.services.compute.fixed_ips_client import FixedIPsClient
-from tempest_lib.services.compute.flavors_client import FlavorsClient
-from tempest_lib.services.compute.floating_ip_pools_client import \
- FloatingIPPoolsClient
-from tempest_lib.services.compute.floating_ips_bulk_client import \
- FloatingIPsBulkClient
-from tempest_lib.services.compute.floating_ips_client import \
- FloatingIPsClient as ComputeFloatingIPsClient
-from tempest_lib.services.compute.hosts_client import HostsClient
-from tempest_lib.services.compute.hypervisor_client import \
- HypervisorClient
-from tempest_lib.services.compute.images_client import ImagesClient \
- as ComputeImagesClient
-from tempest_lib.services.compute.instance_usage_audit_log_client import \
- InstanceUsagesAuditLogClient
-from tempest_lib.services.compute.interfaces_client import InterfacesClient
-from tempest_lib.services.compute.limits_client import LimitsClient
-from tempest_lib.services.compute.migrations_client import MigrationsClient
-from tempest_lib.services.compute.networks_client import NetworksClient \
- as ComputeNetworksClient
-from tempest_lib.services.compute.quota_classes_client import \
- QuotaClassesClient
-from tempest_lib.services.compute.quotas_client import QuotasClient
-from tempest_lib.services.compute.security_group_default_rules_client import \
- SecurityGroupDefaultRulesClient
-from tempest_lib.services.compute.security_group_rules_client import \
- SecurityGroupRulesClient as ComputeSecurityGroupRulesClient
-from tempest_lib.services.compute.security_groups_client import \
- SecurityGroupsClient as ComputeSecurityGroupsClient
-from tempest_lib.services.compute.server_groups_client import \
- ServerGroupsClient
-from tempest_lib.services.compute.servers_client import ServersClient
-from tempest_lib.services.compute.services_client import ServicesClient
-from tempest_lib.services.compute.snapshots_client import \
- SnapshotsClient as ComputeSnapshotsClient
-from tempest_lib.services.compute.tenant_networks_client import \
- TenantNetworksClient
-from tempest_lib.services.compute.tenant_usages_client import \
- TenantUsagesClient
-from tempest_lib.services.compute.versions_client import VersionsClient
-from tempest_lib.services.compute.volumes_client import \
- VolumesClient as ComputeVolumesClient
-from tempest_lib.services.identity.v2.token_client import TokenClient
-from tempest_lib.services.identity.v3.token_client import V3TokenClient
-from tempest_lib.services.network.agents_client import AgentsClient \
- as NetworkAgentsClient
-from tempest_lib.services.network.extensions_client import \
- ExtensionsClient as NetworkExtensionsClient
-from tempest_lib.services.network.floating_ips_client import FloatingIPsClient
-from tempest_lib.services.network.metering_label_rules_client import \
- MeteringLabelRulesClient
-from tempest_lib.services.network.metering_labels_client import \
- MeteringLabelsClient
-from tempest_lib.services.network.networks_client import NetworksClient
-from tempest_lib.services.network.ports_client import PortsClient
-from tempest_lib.services.network.quotas_client import QuotasClient \
- as NetworkQuotasClient
-from tempest_lib.services.network.security_groups_client import \
- SecurityGroupsClient
-from tempest_lib.services.network.subnetpools_client import SubnetpoolsClient
-from tempest_lib.services.network.subnets_client import SubnetsClient
from tempest.common import negative_rest_client
from tempest import config
from tempest import exceptions
+from tempest.lib.services.compute.agents_client import AgentsClient
+from tempest.lib.services.compute.aggregates_client import AggregatesClient
+from tempest.lib.services.compute.availability_zone_client import \
+ AvailabilityZoneClient
+from tempest.lib.services.compute.baremetal_nodes_client import \
+ BaremetalNodesClient
+from tempest.lib.services.compute.certificates_client import \
+ CertificatesClient
+from tempest.lib.services.compute.extensions_client import \
+ ExtensionsClient
+from tempest.lib.services.compute.fixed_ips_client import FixedIPsClient
+from tempest.lib.services.compute.flavors_client import FlavorsClient
+from tempest.lib.services.compute.floating_ip_pools_client import \
+ FloatingIPPoolsClient
+from tempest.lib.services.compute.floating_ips_bulk_client import \
+ FloatingIPsBulkClient
+from tempest.lib.services.compute.floating_ips_client import \
+ FloatingIPsClient as ComputeFloatingIPsClient
+from tempest.lib.services.compute.hosts_client import HostsClient
+from tempest.lib.services.compute.hypervisor_client import \
+ HypervisorClient
+from tempest.lib.services.compute.images_client import ImagesClient \
+ as ComputeImagesClient
+from tempest.lib.services.compute.instance_usage_audit_log_client import \
+ InstanceUsagesAuditLogClient
+from tempest.lib.services.compute.interfaces_client import InterfacesClient
+from tempest.lib.services.compute.limits_client import LimitsClient
+from tempest.lib.services.compute.migrations_client import MigrationsClient
+from tempest.lib.services.compute.networks_client import NetworksClient \
+ as ComputeNetworksClient
+from tempest.lib.services.compute.quota_classes_client import \
+ QuotaClassesClient
+from tempest.lib.services.compute.quotas_client import QuotasClient
+from tempest.lib.services.compute.security_group_default_rules_client import \
+ SecurityGroupDefaultRulesClient
+from tempest.lib.services.compute.security_group_rules_client import \
+ SecurityGroupRulesClient as ComputeSecurityGroupRulesClient
+from tempest.lib.services.compute.security_groups_client import \
+ SecurityGroupsClient as ComputeSecurityGroupsClient
+from tempest.lib.services.compute.server_groups_client import \
+ ServerGroupsClient
+from tempest.lib.services.compute.servers_client import ServersClient
+from tempest.lib.services.compute.services_client import ServicesClient
+from tempest.lib.services.compute.snapshots_client import \
+ SnapshotsClient as ComputeSnapshotsClient
+from tempest.lib.services.compute.tenant_networks_client import \
+ TenantNetworksClient
+from tempest.lib.services.compute.tenant_usages_client import \
+ TenantUsagesClient
+from tempest.lib.services.compute.versions_client import VersionsClient
+from tempest.lib.services.compute.volumes_client import \
+ VolumesClient as ComputeVolumesClient
+from tempest.lib.services.identity.v2.token_client import TokenClient
+from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.network.agents_client import AgentsClient \
+ as NetworkAgentsClient
+from tempest.lib.services.network.extensions_client import \
+ ExtensionsClient as NetworkExtensionsClient
+from tempest.lib.services.network.floating_ips_client import FloatingIPsClient
+from tempest.lib.services.network.metering_label_rules_client import \
+ MeteringLabelRulesClient
+from tempest.lib.services.network.metering_labels_client import \
+ MeteringLabelsClient
+from tempest.lib.services.network.networks_client import NetworksClient
+from tempest.lib.services.network.ports_client import PortsClient
+from tempest.lib.services.network.quotas_client import QuotasClient \
+ as NetworkQuotasClient
+from tempest.lib.services.network.security_groups_client import \
+ SecurityGroupsClient
+from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
+from tempest.lib.services.network.subnets_client import SubnetsClient
from tempest import manager
from tempest.services.baremetal.v1.json.baremetal_client import \
BaremetalClient
@@ -117,6 +117,7 @@
UsersClient
from tempest.services.identity.v3.json.credentials_client import \
CredentialsClient as CredentialsV3Client
+from tempest.services.identity.v3.json.domains_client import DomainsClient
from tempest.services.identity.v3.json.endpoints_client import \
EndPointClient as EndPointV3Client
from tempest.services.identity.v3.json.groups_client import \
@@ -129,6 +130,7 @@
RegionsClient as RegionsV3Client
from tempest.services.identity.v3.json.services_client import \
ServicesClient as IdentityServicesV3Client
+from tempest.services.identity.v3.json.trusts_client import TrustsClient
from tempest.services.identity.v3.json.users_clients import UsersV3Client
from tempest.services.image.v1.json.images_client import ImagesClient
from tempest.services.image.v2.json.images_client import ImagesClientV2
@@ -202,30 +204,14 @@
}
default_params_with_timeout_values.update(default_params)
- def __init__(self, credentials, service=None, api_microversions=None):
+ def __init__(self, credentials, service=None):
"""Initialization of Manager class.
Setup all services clients and make them available for tests cases.
:param credentials: type Credentials or TestResources
:param service: Service name
- :param api_microversions: This is dict of services catalog type
- and their microversion which will be set on respective
- services clients.
- {<service catalog type>: request_microversion}
- Example :
- {'compute': request_microversion}
- - request_microversion will be set on all compute
- service clients.
- OR
- {'compute': request_microversion,
- 'volume': request_microversion}
- - request_microversion of compute will be set on all
- compute service clients.
- - request_microversion of volume will be set on all
- volume service clients.
"""
super(Manager, self).__init__(credentials=credentials)
- self.api_microversions = api_microversions or {}
self._set_compute_clients()
self._set_database_clients()
self._set_identity_clients()
@@ -390,8 +376,6 @@
self.negative_client = negative_rest_client.NegativeRestClient(
self.auth_provider, service, **self.default_params)
- self._set_api_microversions()
-
def _set_compute_clients(self):
params = {
'service': CONF.compute.catalog_type,
@@ -527,8 +511,11 @@
# Clients below use the endpoint type of Keystone API v3
params_v3 = params.copy()
params_v3['endpoint_type'] = CONF.identity.v3_endpoint_type
+ self.domains_client = DomainsClient(self.auth_provider,
+ **params_v3)
self.identity_v3_client = IdentityV3Client(self.auth_provider,
**params_v3)
+ self.trusts_client = TrustsClient(self.auth_provider, **params_v3)
self.users_v3_client = UsersV3Client(self.auth_provider, **params_v3)
self.endpoints_client = EndPointV3Client(self.auth_provider,
**params_v3)
@@ -623,15 +610,3 @@
self.account_client = AccountClient(self.auth_provider, **params)
self.container_client = ContainerClient(self.auth_provider, **params)
self.object_client = ObjectClient(self.auth_provider, **params)
-
- def _set_api_microversions(self):
- service_clients = [x for x in self.__dict__ if x.endswith('_client')]
- for client in service_clients:
- client_obj = getattr(self, client)
- microversion = self.api_microversions.get(client_obj.service)
- if microversion:
- if hasattr(client_obj, 'set_api_microversion'):
- client_obj.set_api_microversion(microversion)
- else:
- LOG.debug("Need to implement set_api_microversion on %s"
- % client)
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 9e98d90..df2d4b5 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -89,16 +89,16 @@
from cliff import command
from oslo_log import log as logging
-import tempest_lib.auth
-from tempest_lib.common.utils import data_utils
-import tempest_lib.exceptions
-from tempest_lib.services.network import networks_client
-from tempest_lib.services.network import subnets_client
import yaml
from tempest.common import identity
from tempest import config
from tempest import exceptions as exc
+import tempest.lib.auth
+from tempest.lib.common.utils import data_utils
+import tempest.lib.exceptions
+from tempest.lib.services.network import networks_client
+from tempest.lib.services.network import subnets_client
from tempest.services.identity.v2.json import identity_client
from tempest.services.identity.v2.json import roles_client
from tempest.services.identity.v2.json import tenants_client
@@ -121,7 +121,7 @@
def get_admin_clients(opts):
- _creds = tempest_lib.auth.KeystoneV2Credentials(
+ _creds = tempest.lib.auth.KeystoneV2Credentials(
username=opts.os_username,
password=opts.os_password,
tenant_name=opts.os_tenant_name)
@@ -131,7 +131,7 @@
'ca_certs': CONF.identity.ca_certificates_file,
'trace_requests': CONF.debug.trace_requests
}
- _auth = tempest_lib.auth.KeystoneV2AuthProvider(
+ _auth = tempest.lib.auth.KeystoneV2AuthProvider(
_creds, CONF.identity.uri, **auth_params)
params = {
'disable_ssl_certificate_validation':
@@ -223,14 +223,14 @@
for u in resources['users']:
try:
tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
LOG.error("Tenant: %s - not found" % u['tenant'])
continue
while True:
try:
identity.get_user_by_username(tenants_admin,
tenant['id'], u['name'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
users_admin.create_user(
u['name'], u['pass'], tenant['id'],
"%s@%s" % (u['name'], tenant['id']),
@@ -254,19 +254,19 @@
for u in resources['users']:
try:
tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
LOG.error("Tenant: %s - not found" % u['tenant'])
continue
try:
user = identity.get_user_by_username(tenants_admin,
tenant['id'], u['name'])
- except tempest_lib.exceptions.NotFound:
+ except tempest.lib.exceptions.NotFound:
LOG.error("User: %s - not found" % u['user'])
continue
for r in u['role_ids']:
try:
roles_admin.assign_user_role(tenant['id'], user['id'], r)
- except tempest_lib.exceptions.Conflict:
+ except tempest.lib.exceptions.Conflict:
# don't care if it's already assigned
pass
LOG.info('Roles assigned')
@@ -294,7 +294,7 @@
enable_dhcp=True,
ip_version=4)
break
- except tempest_lib.exceptions.BadRequest as e:
+ except tempest.lib.exceptions.BadRequest as e:
if 'overlaps with another subnet' not in str(e):
raise
else:
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 5ec8008..0640a4e 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -940,7 +940,7 @@
def __init__(self, manager, **kwargs):
super(DomainService, self).__init__(kwargs)
- self.client = manager.identity_v3_client
+ self.client = manager.domains_client
def list(self):
client = self.client
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index e26a014..e474d86 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -115,19 +115,19 @@
from oslo_log import log as logging
from oslo_utils import timeutils
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
-from tempest_lib.services.compute import flavors_client
-from tempest_lib.services.compute import floating_ips_client
-from tempest_lib.services.compute import security_group_rules_client
-from tempest_lib.services.compute import security_groups_client
-from tempest_lib.services.compute import servers_client
-from tempest_lib.services.network import subnets_client
import yaml
from tempest.common import identity
from tempest.common import waiters
from tempest import config
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import flavors_client
+from tempest.lib.services.compute import floating_ips_client
+from tempest.lib.services.compute import security_group_rules_client
+from tempest.lib.services.compute import security_groups_client
+from tempest.lib.services.compute import servers_client
+from tempest.lib.services.network import subnets_client
from tempest.services.identity.v2.json import identity_client
from tempest.services.identity.v2.json import roles_client
from tempest.services.identity.v2.json import tenants_client
diff --git a/tempest/cmd/run_stress.py b/tempest/cmd/run_stress.py
index 943fe5b..6fe3928 100644
--- a/tempest/cmd/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -146,8 +146,6 @@
def main():
- LOG.warning("Deprecated: Use 'tempest run-stress' instead. "
- "The old entrypoint will be removed in a future release.")
parser = argparse.ArgumentParser(description='Run stress tests')
pa = add_arguments(parser)
ns = pa.parse_args()
diff --git a/tempest/common/api_version_utils.py b/tempest/common/api_version_utils.py
index c3d977f..7c7e96a 100644
--- a/tempest/common/api_version_utils.py
+++ b/tempest/common/api_version_utils.py
@@ -18,6 +18,9 @@
from tempest import exceptions
+LATEST_MICROVERSION = 'latest'
+
+
class BaseMicroversionTest(object):
"""Mixin class for API microversion test class."""
@@ -27,7 +30,7 @@
# for all microversions. We need to define microversion range
# (min_microversion, max_microversion) on each test class if necessary.
min_microversion = None
- max_microversion = 'latest'
+ max_microversion = LATEST_MICROVERSION
def check_skip_with_microversion(test_min_version, test_max_version,
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 73505e6..b14012e 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -15,12 +15,12 @@
from oslo_log import log as logging
from oslo_utils import excutils
-from tempest_lib.common.utils import data_utils
from tempest.common import fixed_network
from tempest.common import service_client
from tempest.common import waiters
from tempest import config
+from tempest.lib.common.utils import data_utils
CONF = config.CONF
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index 5aa794c..edc51ca 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -14,9 +14,9 @@
from oslo_log import log as logging
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
from tempest.services.identity.v2.json import identity_client as v2_identity
LOG = logging.getLogger(__name__)
@@ -130,13 +130,15 @@
class V3CredsClient(CredsClient):
def __init__(self, identity_client, projects_client, users_client,
- domain_name):
- super(V3CredsClient, self).__init__(identity_client,
- projects_client, users_client)
+ domains_client, domain_name):
+ super(V3CredsClient, self).__init__(identity_client, projects_client,
+ users_client)
+ self.domains_client = domains_client
+
try:
# Domain names must be unique, in any case a list is returned,
# selecting the first (and only) element
- self.creds_domain = self.identity_client.list_domains(
+ self.creds_domain = self.domains_client.list_domains(
params={'name': domain_name})['domains'][0]
except lib_exc.NotFound:
# TODO(andrea) we could probably create the domain on the fly
@@ -179,10 +181,11 @@
projects_client,
users_client,
roles_client=None,
+ domains_client=None,
project_domain_name=None):
if isinstance(identity_client, v2_identity.IdentityClient):
return V2CredsClient(identity_client, projects_client, users_client,
roles_client)
else:
return V3CredsClient(identity_client, projects_client, users_client,
- project_domain_name)
+ domains_client, project_domain_name)
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index 9dd89ea..a4b2ae8 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -15,9 +15,9 @@
import abc
import six
-from tempest_lib import auth
from tempest import exceptions
+from tempest.lib import auth
@six.add_metaclass(abc.ABCMeta)
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 24c1198..0ca14a9 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -12,7 +12,6 @@
# limitations under the License.
from oslo_concurrency import lockutils
-from tempest_lib import auth
from tempest import clients
from tempest.common import cred_provider
@@ -20,6 +19,7 @@
from tempest.common import preprov_creds
from tempest import config
from tempest import exceptions
+from tempest.lib import auth
CONF = config.CONF
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 1810c57..a254211 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -15,7 +15,6 @@
import netaddr
from oslo_log import log as logging
import six
-from tempest_lib import exceptions as lib_exc
from tempest import clients
from tempest.common import cred_client
@@ -23,6 +22,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -61,6 +61,7 @@
self.tenants_admin_client,
self.users_admin_client,
self.roles_admin_client,
+ self.domains_admin_client,
self.network_admin_client,
self.networks_admin_client,
self.subnets_admin_client,
@@ -78,6 +79,7 @@
self.tenants_admin_client,
self.users_admin_client,
self.roles_admin_client,
+ self.domains_admin_client,
self.creds_domain_name)
def _get_admin_clients(self):
@@ -90,14 +92,14 @@
os = clients.Manager(self.default_admin_creds)
if self.identity_version == 'v2':
return (os.identity_client, os.tenants_client, os.users_client,
- os.roles_client, os.network_client, os.networks_client,
- os.subnets_client, os.ports_client,
+ os.roles_client, None, os.network_client,
+ os.networks_client, os.subnets_client, os.ports_client,
os.security_groups_client)
else:
return (os.identity_v3_client, os.projects_client,
- os.users_v3_client, None, os.network_client,
- os.networks_client, os.subnets_client, os.ports_client,
- os.security_groups_client)
+ os.users_v3_client, None, os.domains_client,
+ os.network_client, os.networks_client, os.subnets_client,
+ os.ports_client, os.security_groups_client)
def _create_creds(self, suffix="", admin=False, roles=None):
"""Create random credentials under the following schema.
diff --git a/tempest/common/fixed_network.py b/tempest/common/fixed_network.py
index 3fc1365..5f0685e 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/common/fixed_network.py
@@ -13,9 +13,8 @@
import copy
from oslo_log import log as logging
-from tempest_lib.common.utils import misc as misc_utils
-
from tempest import exceptions
+from tempest.lib.common.utils import misc as misc_utils
LOG = logging.getLogger(__name__)
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index 2179363..469defe 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
+from tempest.lib import exceptions as lib_exc
def get_tenant_by_name(client, tenant_name):
diff --git a/tempest/common/preprov_creds.py b/tempest/common/preprov_creds.py
index 34af31e..97cc2ec 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/common/preprov_creds.py
@@ -18,14 +18,14 @@
from oslo_concurrency import lockutils
from oslo_log import log as logging
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
import yaml
from tempest import clients
from tempest.common import cred_provider
from tempest.common import fixed_network
from tempest import exceptions
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
diff --git a/tempest/common/service_client.py b/tempest/common/service_client.py
index b3a5a09..14a3bd6 100644
--- a/tempest/common/service_client.py
+++ b/tempest/common/service_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib.common import rest_client
+from tempest.lib.common import rest_client
class ServiceClient(rest_client.RestClient):
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index aad6373..b6565d1 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -15,8 +15,7 @@
from functools import partial
from tempest import config
-
-from tempest_lib.common.utils import data_utils as lib_data_utils
+from tempest.lib.common.utils import data_utils as lib_data_utils
CONF = config.CONF
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 8f5faef..cfd9df6 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -15,11 +15,11 @@
import time
from oslo_log import log as logging
-from tempest_lib.common import ssh
-import tempest_lib.exceptions
from tempest import config
from tempest import exceptions
+from tempest.lib.common import ssh
+import tempest.lib.exceptions
CONF = config.CONF
@@ -181,7 +181,7 @@
cmd_mkfs = 'sudo /usr/sbin/mke2fs -t %s /dev/%s' % (fs, dev_name)
try:
self.exec_command(cmd_mkfs)
- except tempest_lib.exceptions.SSHExecCommandFailed:
+ except tempest.lib.exceptions.SSHExecCommandFailed:
LOG.error("Couldn't mke2fs")
cmd_why = 'sudo ls -lR /dev'
LOG.info("Contents of /dev: %s" % self.exec_command(cmd_why))
diff --git a/tempest/common/validation_resources.py b/tempest/common/validation_resources.py
index 9457a60..84d0143 100644
--- a/tempest/common/validation_resources.py
+++ b/tempest/common/validation_resources.py
@@ -14,9 +14,9 @@
from oslo_log import log as logging
from tempest import config
-from tempest_lib import exceptions as lib_exc
from tempest.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
LOG = logging.getLogger(__name__)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 867d3f6..95305f3 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -14,11 +14,11 @@
import time
from oslo_log import log as logging
-from tempest_lib.common.utils import misc as misc_utils
-from tempest_lib import exceptions as lib_exc
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
LOG = logging.getLogger(__name__)
diff --git a/tempest/config.py b/tempest/config.py
index 4fe7163..b062201 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1248,7 +1248,10 @@
generator to discover the options exposed to users.
"""
ext_plugins = plugins.TempestTestPluginManager()
- opt_list = [(getattr(g, 'name', None), o) for g, o in _opts]
+ # Make a shallow copy of the options list that can be
+ # extended by plugins. Send back the group object
+ # to allow group help text to be generated.
+ opt_list = [(g, o) for g, o in _opts]
opt_list.extend(ext_plugins.get_plugin_options_list())
return opt_list
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index cd4f50d..c666c96 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -69,10 +69,12 @@
if pep8.noqa(physical_line):
return
- if 'tempest/test.py' not in filename:
- if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
- return (physical_line.find('def'),
- "T105: (setUp|tearDown)Class can not be used in tests")
+ if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
+ return
+
+ if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
+ return (physical_line.find('def'),
+ "T105: (setUp|tearDown)Class can not be used in tests")
def no_vi_headers(physical_line, line_number, lines):
diff --git a/tempest/lib/__init__.py b/tempest/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/__init__.py
diff --git a/tempest/lib/api_schema/__init__.py b/tempest/lib/api_schema/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/__init__.py
diff --git a/tempest/lib/api_schema/response/__init__.py b/tempest/lib/api_schema/response/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/__init__.py b/tempest/lib/api_schema/response/compute/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/__init__.py b/tempest/lib/api_schema/response/compute/v2_1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/agents.py b/tempest/lib/api_schema/response/compute/v2_1/agents.py
new file mode 100644
index 0000000..6f712b4
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/agents.py
@@ -0,0 +1,82 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+common_agent_info = {
+ 'type': 'object',
+ 'properties': {
+ 'agent_id': {'type': ['integer', 'string']},
+ 'hypervisor': {'type': 'string'},
+ 'os': {'type': 'string'},
+ 'architecture': {'type': 'string'},
+ 'version': {'type': 'string'},
+ 'url': {'type': 'string', 'format': 'uri'},
+ 'md5hash': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['agent_id', 'hypervisor', 'os', 'architecture',
+ 'version', 'url', 'md5hash']
+}
+
+list_agents = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agents': {
+ 'type': 'array',
+ 'items': common_agent_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['agents']
+ }
+}
+
+create_agent = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agent': common_agent_info
+ },
+ 'additionalProperties': False,
+ 'required': ['agent']
+ }
+}
+
+update_agent = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agent': {
+ 'type': 'object',
+ 'properties': {
+ 'agent_id': {'type': ['integer', 'string']},
+ 'version': {'type': 'string'},
+ 'url': {'type': 'string', 'format': 'uri'},
+ 'md5hash': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['agent_id', 'version', 'url', 'md5hash']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['agent']
+ }
+}
+
+delete_agent = {
+ 'status_code': [200]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/aggregates.py b/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
new file mode 100644
index 0000000..1a9fe41
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/aggregates.py
@@ -0,0 +1,92 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+# create-aggregate api doesn't have 'hosts' and 'metadata' attributes.
+aggregate_for_create = {
+ 'type': 'object',
+ 'properties': {
+ 'availability_zone': {'type': ['string', 'null']},
+ 'created_at': {'type': 'string'},
+ 'deleted': {'type': 'boolean'},
+ 'deleted_at': {'type': ['string', 'null']},
+ 'id': {'type': 'integer'},
+ 'name': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['availability_zone', 'created_at', 'deleted',
+ 'deleted_at', 'id', 'name', 'updated_at'],
+}
+
+common_aggregate_info = copy.deepcopy(aggregate_for_create)
+common_aggregate_info['properties'].update({
+ 'hosts': {'type': 'array'},
+ 'metadata': {'type': 'object'}
+})
+common_aggregate_info['required'].extend(['hosts', 'metadata'])
+
+list_aggregates = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregates': {
+ 'type': 'array',
+ 'items': common_aggregate_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['aggregates'],
+ }
+}
+
+get_aggregate = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregate': common_aggregate_info
+ },
+ 'additionalProperties': False,
+ 'required': ['aggregate'],
+ }
+}
+
+aggregate_set_metadata = get_aggregate
+# The 'updated_at' attribute of 'update_aggregate' can't be null.
+update_aggregate = copy.deepcopy(get_aggregate)
+update_aggregate['response_body']['properties']['aggregate']['properties'][
+ 'updated_at'] = {
+ 'type': 'string'
+ }
+
+delete_aggregate = {
+ 'status_code': [200]
+}
+
+create_aggregate = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'aggregate': aggregate_for_create
+ },
+ 'additionalProperties': False,
+ 'required': ['aggregate'],
+ }
+}
+
+aggregate_add_remove_host = get_aggregate
diff --git a/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
new file mode 100644
index 0000000..d9aebce
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py
@@ -0,0 +1,78 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+
+base = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'availabilityZoneInfo': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'zoneName': {'type': 'string'},
+ 'zoneState': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'}
+ },
+ 'additionalProperties': False,
+ 'required': ['available']
+ },
+ # NOTE: Here is the difference between detail and
+ # non-detail.
+ 'hosts': {'type': 'null'}
+ },
+ 'additionalProperties': False,
+ 'required': ['zoneName', 'zoneState', 'hosts']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['availabilityZoneInfo']
+ }
+}
+
+detail = {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for a hostname
+ '^[a-zA-Z0-9-_.]+$': {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for a service name
+ '^.*$': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'},
+ 'active': {'type': 'boolean'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['available', 'active', 'updated_at']
+ }
+ }
+ }
+ }
+}
+
+list_availability_zone_list = copy.deepcopy(base)
+
+list_availability_zone_list_detail = copy.deepcopy(base)
+list_availability_zone_list_detail['response_body']['properties'][
+ 'availabilityZoneInfo']['items']['properties']['hosts'] = detail
diff --git a/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py b/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py
new file mode 100644
index 0000000..d1ee877
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py
@@ -0,0 +1,63 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+node = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'interfaces': {'type': 'array'},
+ 'host': {'type': 'string'},
+ 'task_state': {'type': ['string', 'null']},
+ 'cpus': {'type': ['integer', 'string']},
+ 'memory_mb': {'type': ['integer', 'string']},
+ 'disk_gb': {'type': ['integer', 'string']},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'interfaces', 'host', 'task_state', 'cpus', 'memory_mb',
+ 'disk_gb']
+}
+
+list_baremetal_nodes = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'nodes': {
+ 'type': 'array',
+ 'items': node
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['nodes']
+ }
+}
+
+baremetal_node = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'node': node
+ },
+ 'additionalProperties': False,
+ 'required': ['node']
+ }
+}
+get_baremetal_node = copy.deepcopy(baremetal_node)
+get_baremetal_node['response_body']['properties']['node'][
+ 'properties'].update({'instance_uuid': {'type': ['string', 'null']}})
+get_baremetal_node['response_body']['properties']['node'][
+ 'required'].append('instance_uuid')
diff --git a/tempest/lib/api_schema/response/compute/v2_1/certificates.py b/tempest/lib/api_schema/response/compute/v2_1/certificates.py
new file mode 100644
index 0000000..4e7cbe4
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/certificates.py
@@ -0,0 +1,41 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+_common_schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'certificate': {
+ 'type': 'object',
+ 'properties': {
+ 'data': {'type': 'string'},
+ 'private_key': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['data', 'private_key']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['certificate']
+ }
+}
+
+get_certificate = copy.deepcopy(_common_schema)
+get_certificate['response_body']['properties']['certificate'][
+ 'properties']['private_key'].update({'type': 'null'})
+
+create_certificate = copy.deepcopy(_common_schema)
diff --git a/tempest/lib/api_schema/response/compute/v2_1/extensions.py b/tempest/lib/api_schema/response/compute/v2_1/extensions.py
new file mode 100644
index 0000000..a6a455c
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/extensions.py
@@ -0,0 +1,47 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+list_extensions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'extensions': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'updated': {
+ 'type': 'string',
+ 'format': 'data-time'
+ },
+ 'name': {'type': 'string'},
+ 'links': {'type': 'array'},
+ 'namespace': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'alias': {'type': 'string'},
+ 'description': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['updated', 'name', 'links', 'namespace',
+ 'alias', 'description']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['extensions']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py b/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py
new file mode 100644
index 0000000..a653213
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py
@@ -0,0 +1,41 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+get_fixed_ip = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'fixed_ip': {
+ 'type': 'object',
+ 'properties': {
+ 'address': parameter_types.ip_address,
+ 'cidr': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'hostname': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['address', 'cidr', 'host', 'hostname']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['fixed_ip']
+ }
+}
+
+reserve_unreserve_fixed_ip = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
new file mode 100644
index 0000000..547d94d
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -0,0 +1,103 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+list_flavors = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['name', 'links', 'id']
+ }
+ },
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always So it is not 'required'.
+ 'required': ['flavors']
+ }
+}
+
+common_flavor_info = {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'ram': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ # 'swap' attributes comes as integer value but if it is empty
+ # it comes as "". So defining type of as string and integer.
+ 'swap': {'type': ['integer', 'string']},
+ 'disk': {'type': 'integer'},
+ 'id': {'type': 'string'},
+ 'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+ 'os-flavor-access:is_public': {'type': 'boolean'},
+ 'rxtx_factor': {'type': 'number'},
+ 'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+ # 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
+ 'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id']
+}
+
+list_flavors_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': common_flavor_info
+ },
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always So it is not 'required'.
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['flavors']
+ }
+}
+
+unset_flavor_extra_specs = {
+ 'status_code': [200]
+}
+
+create_get_flavor_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor': common_flavor_info
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor']
+ }
+}
+
+delete_flavor = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py
new file mode 100644
index 0000000..a4d6af0
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py
@@ -0,0 +1,36 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+add_remove_list_flavor_access = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor_access': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor_id', 'tenant_id'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor_access']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
new file mode 100644
index 0000000..a438d48
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -0,0 +1,40 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+set_get_flavor_extra_specs = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'extra_specs': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['extra_specs']
+ }
+}
+
+set_get_flavor_extra_specs_key = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py b/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
new file mode 100644
index 0000000..0c66590
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py
@@ -0,0 +1,148 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+common_floating_ip_info = {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but
+ # here allows 'string' also because we will be
+ # able to change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'pool': {'type': ['string', 'null']},
+ 'instance_id': {'type': ['string', 'null']},
+ 'ip': parameter_types.ip_address,
+ 'fixed_ip': parameter_types.ip_address
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'pool', 'instance_id',
+ 'ip', 'fixed_ip'],
+
+}
+list_floating_ips = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips': {
+ 'type': 'array',
+ 'items': common_floating_ip_info
+ },
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ips'],
+ }
+}
+
+create_get_floating_ip = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip': common_floating_ip_info
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ip'],
+ }
+}
+
+list_floating_ip_pools = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip_pools': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ip_pools'],
+ }
+}
+
+add_remove_floating_ip = {
+ 'status_code': [202]
+}
+
+create_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_create': {
+ 'type': 'object',
+ 'properties': {
+ 'interface': {'type': ['string', 'null']},
+ 'ip_range': {'type': 'string'},
+ 'pool': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ 'required': ['interface', 'ip_range', 'pool'],
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ips_bulk_create'],
+ }
+}
+
+delete_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_delete': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ips_bulk_delete'],
+ }
+}
+
+list_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ip_info': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'address': parameter_types.ip_address,
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'interface': {'type': ['string', 'null']},
+ 'pool': {'type': ['string', 'null']},
+ 'project_id': {'type': ['string', 'null']},
+ 'fixed_ip': parameter_types.ip_address
+ },
+ 'additionalProperties': False,
+ # NOTE: fixed_ip is introduced after JUNO release,
+ # So it is not defined as 'required'.
+ 'required': ['address', 'instance_uuid', 'interface',
+ 'pool', 'project_id'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['floating_ip_info'],
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/hosts.py b/tempest/lib/api_schema/response/compute/v2_1/hosts.py
new file mode 100644
index 0000000..ae70ff1
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/hosts.py
@@ -0,0 +1,116 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+
+list_hosts = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hosts': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'host_name': {'type': 'string'},
+ 'service': {'type': 'string'},
+ 'zone': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['host_name', 'service', 'zone']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hosts']
+ }
+}
+
+get_host_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {
+ 'type': 'array',
+ 'item': {
+ 'type': 'object',
+ 'properties': {
+ 'resource': {
+ 'type': 'object',
+ 'properties': {
+ 'cpu': {'type': 'integer'},
+ 'disk_gb': {'type': 'integer'},
+ 'host': {'type': 'string'},
+ 'memory_mb': {'type': 'integer'},
+ 'project': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['cpu', 'disk_gb', 'host',
+ 'memory_mb', 'project']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['resource']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['host']
+ }
+}
+
+startup_host = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {'type': 'string'},
+ 'power_action': {'enum': ['startup']}
+ },
+ 'additionalProperties': False,
+ 'required': ['host', 'power_action']
+ }
+}
+
+# The 'power_action' attribute of 'shutdown_host' API is 'shutdown'
+shutdown_host = copy.deepcopy(startup_host)
+
+shutdown_host['response_body']['properties']['power_action'] = {
+ 'enum': ['shutdown']
+}
+
+# The 'power_action' attribute of 'reboot_host' API is 'reboot'
+reboot_host = copy.deepcopy(startup_host)
+
+reboot_host['response_body']['properties']['power_action'] = {
+ 'enum': ['reboot']
+}
+
+update_host = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {'type': 'string'},
+ 'maintenance_mode': {'enum': ['on_maintenance',
+ 'off_maintenance']},
+ 'status': {'enum': ['enabled', 'disabled']}
+ },
+ 'additionalProperties': False,
+ 'required': ['host', 'maintenance_mode', 'status']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py b/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py
new file mode 100644
index 0000000..d15b4f6
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py
@@ -0,0 +1,195 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+get_hypervisor_statistics = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisor_statistics': {
+ 'type': 'object',
+ 'properties': {
+ 'count': {'type': 'integer'},
+ 'current_workload': {'type': 'integer'},
+ 'disk_available_least': {'type': ['integer', 'null']},
+ 'free_disk_gb': {'type': 'integer'},
+ 'free_ram_mb': {'type': 'integer'},
+ 'local_gb': {'type': 'integer'},
+ 'local_gb_used': {'type': 'integer'},
+ 'memory_mb': {'type': 'integer'},
+ 'memory_mb_used': {'type': 'integer'},
+ 'running_vms': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ 'vcpus_used': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['count', 'current_workload',
+ 'disk_available_least', 'free_disk_gb',
+ 'free_ram_mb', 'local_gb', 'local_gb_used',
+ 'memory_mb', 'memory_mb_used', 'running_vms',
+ 'vcpus', 'vcpus_used']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisor_statistics']
+ }
+}
+
+
+hypervisor_detail = {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'cpu_info': {'type': 'string'},
+ 'current_workload': {'type': 'integer'},
+ 'disk_available_least': {'type': ['integer', 'null']},
+ 'host_ip': parameter_types.ip_address,
+ 'free_disk_gb': {'type': 'integer'},
+ 'free_ram_mb': {'type': 'integer'},
+ 'hypervisor_hostname': {'type': 'string'},
+ 'hypervisor_type': {'type': 'string'},
+ 'hypervisor_version': {'type': 'integer'},
+ 'id': {'type': ['integer', 'string']},
+ 'local_gb': {'type': 'integer'},
+ 'local_gb_used': {'type': 'integer'},
+ 'memory_mb': {'type': 'integer'},
+ 'memory_mb_used': {'type': 'integer'},
+ 'running_vms': {'type': 'integer'},
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'host': {'type': 'string'},
+ 'id': {'type': ['integer', 'string']},
+ 'disabled_reason': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['host', 'id']
+ },
+ 'vcpus': {'type': 'integer'},
+ 'vcpus_used': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # NOTE: When loading os-hypervisor-status extension,
+ # a response contains status and state. So these params
+ # should not be required.
+ 'required': ['cpu_info', 'current_workload',
+ 'disk_available_least', 'host_ip',
+ 'free_disk_gb', 'free_ram_mb',
+ 'hypervisor_hostname', 'hypervisor_type',
+ 'hypervisor_version', 'id', 'local_gb',
+ 'local_gb_used', 'memory_mb', 'memory_mb_used',
+ 'running_vms', 'service', 'vcpus', 'vcpus_used']
+}
+
+list_hypervisors_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisors': {
+ 'type': 'array',
+ 'items': hypervisor_detail
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisors']
+ }
+}
+
+get_hypervisor = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisor': hypervisor_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisor']
+ }
+}
+
+list_search_hypervisors = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisors': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'id': {'type': ['integer', 'string']},
+ 'hypervisor_hostname': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE: When loading os-hypervisor-status extension,
+ # a response contains status and state. So these params
+ # should not be required.
+ 'required': ['id', 'hypervisor_hostname']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisors']
+ }
+}
+
+get_hypervisor_uptime = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hypervisor': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'id': {'type': ['integer', 'string']},
+ 'hypervisor_hostname': {'type': 'string'},
+ 'uptime': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE: When loading os-hypervisor-status extension,
+ # a response contains status and state. So these params
+ # should not be required.
+ 'required': ['id', 'hypervisor_hostname', 'uptime']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['hypervisor']
+ }
+}
+
+get_hypervisors_servers = copy.deepcopy(list_search_hypervisors)
+get_hypervisors_servers['response_body']['properties']['hypervisors']['items'][
+ 'properties']['servers'] = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'uuid': {'type': 'string'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ }
+ }
+# In V2 API, if there is no servers (VM) on the Hypervisor host then 'servers'
+# attribute will not be present in response body So it is not 'required'.
diff --git a/tempest/lib/api_schema/response/compute/v2_1/images.py b/tempest/lib/api_schema/response/compute/v2_1/images.py
new file mode 100644
index 0000000..daab898
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/images.py
@@ -0,0 +1,154 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+image_links = copy.deepcopy(parameter_types.links)
+image_links['items']['properties'].update({'type': {'type': 'string'}})
+
+common_image_schema = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'links': image_links,
+ 'name': {'type': ['string', 'null']},
+ 'created': {'type': 'string'},
+ 'minDisk': {'type': 'integer'},
+ 'minRam': {'type': 'integer'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'OS-EXT-IMG-SIZE:size': {'type': ['integer', 'null']},
+ 'OS-DCF:diskConfig': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # 'server' attributes only comes in response body if image is
+ # associated with any server. 'OS-EXT-IMG-SIZE:size' & 'OS-DCF:diskConfig'
+ # are API extension, So those are not defined as 'required'.
+ 'required': ['id', 'status', 'updated', 'links', 'name',
+ 'created', 'minDisk', 'minRam', 'progress',
+ 'metadata']
+}
+
+get_image = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'image': common_image_schema
+ },
+ 'additionalProperties': False,
+ 'required': ['image']
+ }
+}
+
+list_images = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'images': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': image_links,
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'images_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): images_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['images']
+ }
+}
+
+create_image = {
+ 'status_code': [202],
+ 'response_header': {
+ 'type': 'object',
+ 'properties': parameter_types.response_header
+ }
+}
+create_image['response_header']['properties'].update(
+ {'location': {
+ 'type': 'string',
+ 'format': 'uri'}
+ }
+)
+create_image['response_header']['required'] = ['location']
+
+delete = {
+ 'status_code': [204]
+}
+
+image_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+image_meta_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['meta']
+ }
+}
+
+list_images_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'images': {
+ 'type': 'array',
+ 'items': common_image_schema
+ },
+ 'images_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): images_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['images']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py b/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py
new file mode 100644
index 0000000..c6c4deb
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py
@@ -0,0 +1,62 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+common_instance_usage_audit_log = {
+ 'type': 'object',
+ 'properties': {
+ 'hosts_not_run': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'log': {'type': 'object'},
+ 'num_hosts': {'type': 'integer'},
+ 'num_hosts_done': {'type': 'integer'},
+ 'num_hosts_not_run': {'type': 'integer'},
+ 'num_hosts_running': {'type': 'integer'},
+ 'overall_status': {'type': 'string'},
+ 'period_beginning': {'type': 'string'},
+ 'period_ending': {'type': 'string'},
+ 'total_errors': {'type': 'integer'},
+ 'total_instances': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['hosts_not_run', 'log', 'num_hosts', 'num_hosts_done',
+ 'num_hosts_not_run', 'num_hosts_running', 'overall_status',
+ 'period_beginning', 'period_ending', 'total_errors',
+ 'total_instances']
+}
+
+get_instance_usage_audit_log = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instance_usage_audit_log': common_instance_usage_audit_log
+ },
+ 'additionalProperties': False,
+ 'required': ['instance_usage_audit_log']
+ }
+}
+
+list_instance_usage_audit_log = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instance_usage_audit_logs': common_instance_usage_audit_log
+ },
+ 'additionalProperties': False,
+ 'required': ['instance_usage_audit_logs']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/interfaces.py b/tempest/lib/api_schema/response/compute/v2_1/interfaces.py
new file mode 100644
index 0000000..9984750
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/interfaces.py
@@ -0,0 +1,73 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+interface_common_info = {
+ 'type': 'object',
+ 'properties': {
+ 'port_state': {'type': 'string'},
+ 'fixed_ips': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'subnet_id': {
+ 'type': 'string',
+ 'format': 'uuid'
+ },
+ 'ip_address': parameter_types.ip_address
+ },
+ 'additionalProperties': False,
+ 'required': ['subnet_id', 'ip_address']
+ }
+ },
+ 'port_id': {'type': 'string', 'format': 'uuid'},
+ 'net_id': {'type': 'string', 'format': 'uuid'},
+ 'mac_addr': parameter_types.mac_address
+ },
+ 'additionalProperties': False,
+ 'required': ['port_state', 'fixed_ips', 'port_id', 'net_id', 'mac_addr']
+}
+
+get_create_interfaces = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'interfaceAttachment': interface_common_info
+ },
+ 'additionalProperties': False,
+ 'required': ['interfaceAttachment']
+ }
+}
+
+list_interfaces = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'interfaceAttachments': {
+ 'type': 'array',
+ 'items': interface_common_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['interfaceAttachments']
+ }
+}
+
+delete_interface = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/keypairs.py b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
new file mode 100644
index 0000000..9c04c79
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
@@ -0,0 +1,107 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+get_keypair = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'keypair': {
+ 'type': 'object',
+ 'properties': {
+ 'public_key': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'fingerprint': {'type': 'string'},
+ 'user_id': {'type': 'string'},
+ 'deleted': {'type': 'boolean'},
+ 'created_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
+ 'deleted_at': {'type': ['string', 'null']},
+ 'id': {'type': 'integer'}
+
+ },
+ 'additionalProperties': False,
+ # When we run the get keypair API, response body includes
+ # all the above mentioned attributes.
+ # But in Nova API sample file, response body includes only
+ # 'public_key', 'name' & 'fingerprint'. So only 'public_key',
+ # 'name' & 'fingerprint' are defined as 'required'.
+ 'required': ['public_key', 'name', 'fingerprint']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['keypair']
+ }
+}
+
+create_keypair = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'keypair': {
+ 'type': 'object',
+ 'properties': {
+ 'fingerprint': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'public_key': {'type': 'string'},
+ 'user_id': {'type': 'string'},
+ 'private_key': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # When create keypair API is being called with 'Public key'
+ # (Importing keypair) then, response body does not contain
+ # 'private_key' So it is not defined as 'required'
+ 'required': ['fingerprint', 'name', 'public_key', 'user_id']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['keypair']
+ }
+}
+
+delete_keypair = {
+ 'status_code': [202],
+}
+
+list_keypairs = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'keypairs': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'keypair': {
+ 'type': 'object',
+ 'properties': {
+ 'public_key': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'fingerprint': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['public_key', 'name', 'fingerprint']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['keypair']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['keypairs']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/limits.py b/tempest/lib/api_schema/response/compute/v2_1/limits.py
new file mode 100644
index 0000000..81f175f
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/limits.py
@@ -0,0 +1,106 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+get_limit = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'limits': {
+ 'type': 'object',
+ 'properties': {
+ 'absolute': {
+ 'type': 'object',
+ 'properties': {
+ 'maxTotalRAMSize': {'type': 'integer'},
+ 'totalCoresUsed': {'type': 'integer'},
+ 'maxTotalInstances': {'type': 'integer'},
+ 'maxTotalFloatingIps': {'type': 'integer'},
+ 'totalSecurityGroupsUsed': {'type': 'integer'},
+ 'maxTotalCores': {'type': 'integer'},
+ 'totalFloatingIpsUsed': {'type': 'integer'},
+ 'maxSecurityGroups': {'type': 'integer'},
+ 'maxServerMeta': {'type': 'integer'},
+ 'maxPersonality': {'type': 'integer'},
+ 'maxImageMeta': {'type': 'integer'},
+ 'maxPersonalitySize': {'type': 'integer'},
+ 'maxSecurityGroupRules': {'type': 'integer'},
+ 'maxTotalKeypairs': {'type': 'integer'},
+ 'totalRAMUsed': {'type': 'integer'},
+ 'totalInstancesUsed': {'type': 'integer'},
+ 'maxServerGroupMembers': {'type': 'integer'},
+ 'maxServerGroups': {'type': 'integer'},
+ 'totalServerGroupsUsed': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): maxServerGroupMembers, maxServerGroups
+ # and totalServerGroupsUsed are API extension,
+ # and some environments return a response without these
+ # attributes.So they are not 'required'.
+ 'required': ['maxImageMeta',
+ 'maxPersonality',
+ 'maxPersonalitySize',
+ 'maxSecurityGroupRules',
+ 'maxSecurityGroups',
+ 'maxServerMeta',
+ 'maxTotalCores',
+ 'maxTotalFloatingIps',
+ 'maxTotalInstances',
+ 'maxTotalKeypairs',
+ 'maxTotalRAMSize',
+ 'totalCoresUsed',
+ 'totalFloatingIpsUsed',
+ 'totalInstancesUsed',
+ 'totalRAMUsed',
+ 'totalSecurityGroupsUsed']
+ },
+ 'rate': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'limit': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'next-available':
+ {'type': 'string'},
+ 'remaining':
+ {'type': 'integer'},
+ 'unit':
+ {'type': 'string'},
+ 'value':
+ {'type': 'integer'},
+ 'verb':
+ {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ }
+ },
+ 'regex': {'type': 'string'},
+ 'uri': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['absolute', 'rate']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['limits']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/migrations.py b/tempest/lib/api_schema/response/compute/v2_1/migrations.py
new file mode 100644
index 0000000..b7d66ea
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/migrations.py
@@ -0,0 +1,51 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+list_migrations = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'migrations': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'integer'},
+ 'status': {'type': ['string', 'null']},
+ 'instance_uuid': {'type': ['string', 'null']},
+ 'source_node': {'type': ['string', 'null']},
+ 'source_compute': {'type': ['string', 'null']},
+ 'dest_node': {'type': ['string', 'null']},
+ 'dest_compute': {'type': ['string', 'null']},
+ 'dest_host': {'type': ['string', 'null']},
+ 'old_instance_type_id': {'type': ['integer', 'null']},
+ 'new_instance_type_id': {'type': ['integer', 'null']},
+ 'created_at': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': [
+ 'id', 'status', 'instance_uuid', 'source_node',
+ 'source_compute', 'dest_node', 'dest_compute',
+ 'dest_host', 'old_instance_type_id',
+ 'new_instance_type_id', 'created_at', 'updated_at'
+ ]
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['migrations']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
new file mode 100644
index 0000000..07cc890
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py
@@ -0,0 +1,96 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+links = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['href', 'rel']
+ }
+}
+
+mac_address = {
+ 'type': 'string',
+ 'pattern': '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}'
+}
+
+ip_address = {
+ 'oneOf': [
+ {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ },
+ {'type': 'null'}
+ ]
+}
+
+access_ip_v4 = {
+ 'type': 'string',
+ 'oneOf': [{'format': 'ipv4'}, {'enum': ['']}]
+}
+
+access_ip_v6 = {
+ 'type': 'string',
+ 'oneOf': [{'format': 'ipv6'}, {'enum': ['']}]
+}
+
+addresses = {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for 'private' or something.
+ '^[a-zA-Z0-9-_.]+$': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'version': {'type': 'integer'},
+ 'addr': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['version', 'addr']
+ }
+ }
+ }
+}
+
+response_header = {
+ 'connection': {'type': 'string'},
+ 'content-length': {'type': 'string'},
+ 'content-type': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'x-compute-request-id': {'type': 'string'},
+ 'vary': {'type': 'string'},
+ 'x-openstack-nova-api-version': {'type': 'string'},
+ 'date': {
+ 'type': 'string',
+ 'format': 'data-time'
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py b/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py
new file mode 100644
index 0000000..03d7f12
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py
@@ -0,0 +1,31 @@
+# Copyright 2014 IBM Corporation.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import quotas
+
+# NOTE(mriedem): os-quota-class-sets responses are the same as os-quota-sets
+# except for the key in the response body is quota_class_set instead of
+# quota_set, so update this copy of the schema from os-quota-sets.
+get_quota_class_set = copy.deepcopy(quotas.get_quota_set)
+get_quota_class_set['response_body']['properties']['quota_class_set'] = (
+ get_quota_class_set['response_body']['properties'].pop('quota_set'))
+get_quota_class_set['response_body']['required'] = ['quota_class_set']
+
+update_quota_class_set = copy.deepcopy(quotas.update_quota_set)
+update_quota_class_set['response_body']['properties']['quota_class_set'] = (
+ update_quota_class_set['response_body']['properties'].pop('quota_set'))
+update_quota_class_set['response_body']['required'] = ['quota_class_set']
diff --git a/tempest/lib/api_schema/response/compute/v2_1/quotas.py b/tempest/lib/api_schema/response/compute/v2_1/quotas.py
new file mode 100644
index 0000000..7953983
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/quotas.py
@@ -0,0 +1,65 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+update_quota_set = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'quota_set': {
+ 'type': 'object',
+ 'properties': {
+ 'instances': {'type': 'integer'},
+ 'cores': {'type': 'integer'},
+ 'ram': {'type': 'integer'},
+ 'floating_ips': {'type': 'integer'},
+ 'fixed_ips': {'type': 'integer'},
+ 'metadata_items': {'type': 'integer'},
+ 'key_pairs': {'type': 'integer'},
+ 'security_groups': {'type': 'integer'},
+ 'security_group_rules': {'type': 'integer'},
+ 'server_group_members': {'type': 'integer'},
+ 'server_groups': {'type': 'integer'},
+ 'injected_files': {'type': 'integer'},
+ 'injected_file_content_bytes': {'type': 'integer'},
+ 'injected_file_path_bytes': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ # NOTE: server_group_members and server_groups are represented
+ # when enabling quota_server_group extension. So they should
+ # not be required.
+ 'required': ['instances', 'cores', 'ram',
+ 'floating_ips', 'fixed_ips',
+ 'metadata_items', 'key_pairs',
+ 'security_groups', 'security_group_rules',
+ 'injected_files', 'injected_file_content_bytes',
+ 'injected_file_path_bytes']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['quota_set']
+ }
+}
+
+get_quota_set = copy.deepcopy(update_quota_set)
+get_quota_set['response_body']['properties']['quota_set']['properties'][
+ 'id'] = {'type': 'string'}
+get_quota_set['response_body']['properties']['quota_set']['required'].extend([
+ 'id'])
+
+delete_quota = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py b/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py
new file mode 100644
index 0000000..2ec2826
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py
@@ -0,0 +1,65 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+common_security_group_default_rule_info = {
+ 'type': 'object',
+ 'properties': {
+ 'from_port': {'type': 'integer'},
+ 'id': {'type': 'integer'},
+ 'ip_protocol': {'type': 'string'},
+ 'ip_range': {
+ 'type': 'object',
+ 'properties': {
+ 'cidr': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['cidr'],
+ },
+ 'to_port': {'type': 'integer'},
+ },
+ 'additionalProperties': False,
+ 'required': ['from_port', 'id', 'ip_protocol', 'ip_range', 'to_port'],
+}
+
+create_get_security_group_default_rule = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group_default_rule':
+ common_security_group_default_rule_info
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group_default_rule']
+ }
+}
+
+delete_security_group_default_rule = {
+ 'status_code': [204]
+}
+
+list_security_group_default_rules = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group_default_rules': {
+ 'type': 'array',
+ 'items': common_security_group_default_rule_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group_default_rules']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/security_groups.py b/tempest/lib/api_schema/response/compute/v2_1/security_groups.py
new file mode 100644
index 0000000..5ed5a5c
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/security_groups.py
@@ -0,0 +1,113 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+common_security_group_rule = {
+ 'from_port': {'type': ['integer', 'null']},
+ 'to_port': {'type': ['integer', 'null']},
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'tenant_id': {'type': 'string'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ },
+ 'ip_protocol': {'type': ['string', 'null']},
+ # 'parent_group_id' can be UUID so defining it as 'string' also.
+ 'parent_group_id': {'type': ['string', 'integer', 'null']},
+ 'ip_range': {
+ 'type': 'object',
+ 'properties': {
+ 'cidr': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # When optional argument is provided in request body
+ # like 'group_id' then, attribute 'cidr' does not
+ # comes in response body. So it is not 'required'.
+ },
+ 'id': {'type': ['string', 'integer']}
+}
+
+common_security_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'string']},
+ 'name': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'rules': {
+ 'type': 'array',
+ 'items': {
+ 'type': ['object', 'null'],
+ 'properties': common_security_group_rule,
+ 'additionalProperties': False,
+ }
+ },
+ 'description': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'tenant_id', 'rules', 'description'],
+}
+
+list_security_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_groups': {
+ 'type': 'array',
+ 'items': common_security_group
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['security_groups']
+ }
+}
+
+get_security_group = create_security_group = update_security_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group': common_security_group
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group']
+ }
+}
+
+delete_security_group = {
+ 'status_code': [202]
+}
+
+create_security_group_rule = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'security_group_rule': {
+ 'type': 'object',
+ 'properties': common_security_group_rule,
+ 'additionalProperties': False,
+ 'required': ['from_port', 'to_port', 'group', 'ip_protocol',
+ 'parent_group_id', 'id', 'ip_range']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['security_group_rule']
+ }
+}
+
+delete_security_group_rule = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
new file mode 100644
index 0000000..485c51a
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -0,0 +1,553 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+create_server = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'security_groups': {'type': 'array'},
+ 'links': parameter_types.links,
+ 'OS-DCF:diskConfig': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE: OS-DCF:diskConfig & security_groups are API extension,
+ # and some environments return a response without these
+ # attributes.So they are not 'required'.
+ 'required': ['id', 'links']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+create_server_with_admin_pass = copy.deepcopy(create_server)
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+list_servers = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+delete_server = {
+ 'status_code': [204],
+}
+
+common_show_server = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'image': {'oneOf': [
+ {'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']},
+ {'type': ['string', 'null']}
+ ]},
+ 'flavor': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links']
+ },
+ 'fault': {
+ 'type': 'object',
+ 'properties': {
+ 'code': {'type': 'integer'},
+ 'created': {'type': 'string'},
+ 'message': {'type': 'string'},
+ 'details': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): 'details' is not necessary to be present
+ # in the 'fault'. So it is not defined as 'required'.
+ 'required': ['code', 'created', 'message']
+ },
+ 'user_id': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'links': parameter_types.links,
+ 'addresses': parameter_types.addresses,
+ 'hostId': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'},
+ 'accessIPv4': parameter_types.access_ip_v4,
+ 'accessIPv6': parameter_types.access_ip_v6
+ },
+ 'additionalProperties': False,
+ # NOTE(GMann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # 'fault' attribute is present in the response
+ # only when server's status is one of the "ERROR", "DELETED".
+ # OS-DCF:diskConfig and accessIPv4/v6 are API
+ # extensions, and some environments return a response
+ # without these attributes.So these are not defined as 'required'.
+ 'required': ['id', 'name', 'status', 'image', 'flavor',
+ 'user_id', 'tenant_id', 'created', 'updated',
+ 'metadata', 'links', 'addresses', 'hostId']
+}
+
+update_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': common_show_server
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+server_detail = copy.deepcopy(common_show_server)
+server_detail['properties'].update({
+ 'key_name': {'type': ['string', 'null']},
+ 'security_groups': {'type': 'array'},
+
+ # NOTE: Non-admin users also can see "OS-SRV-USG" and "OS-EXT-AZ"
+ # attributes.
+ 'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
+ 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
+ 'OS-EXT-AZ:availability_zone': {'type': 'string'},
+
+ # NOTE: Admin users only can see "OS-EXT-STS" and "OS-EXT-SRV-ATTR"
+ # attributes.
+ 'OS-EXT-STS:task_state': {'type': ['string', 'null']},
+ 'OS-EXT-STS:vm_state': {'type': 'string'},
+ 'OS-EXT-STS:power_state': {'type': 'integer'},
+ 'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
+ 'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
+ 'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
+ 'os-extended-volumes:volumes_attached': {'type': 'array'},
+ 'config_drive': {'type': 'string'}
+})
+server_detail['properties']['addresses']['patternProperties'][
+ '^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
+ 'OS-EXT-IPS:type': {'type': 'string'},
+ 'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
+# NOTE(gmann): Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
+# attributes in server address. Those are API extension,
+# and some environments return a response without
+# these attributes. So they are not 'required'.
+
+get_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': server_detail
+ },
+ 'additionalProperties': False,
+ 'required': ['server']
+ }
+}
+
+list_servers_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'servers': {
+ 'type': 'array',
+ 'items': server_detail
+ },
+ 'servers_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): servers_links attribute is not necessary to be
+ # present always So it is not 'required'.
+ 'required': ['servers']
+ }
+}
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+rescue_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'adminPass': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['adminPass']
+ }
+}
+
+list_virtual_interfaces = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'virtual_interfaces': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'mac_address': parameter_types.mac_address,
+ 'OS-EXT-VIF-NET:net_id': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # 'OS-EXT-VIF-NET:net_id' is API extension So it is
+ # not defined as 'required'
+ 'required': ['id', 'mac_address']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['virtual_interfaces']
+ }
+}
+
+common_attach_volume_info = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': ['integer', 'string']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'device', 'volumeId', 'serverId']
+}
+
+attach_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumeAttachment': common_attach_volume_info
+ },
+ 'additionalProperties': False,
+ 'required': ['volumeAttachment']
+ }
+}
+
+detach_volume = {
+ 'status_code': [202]
+}
+
+show_volume_attachment = copy.deepcopy(attach_volume)
+show_volume_attachment['response_body']['properties'][
+ 'volumeAttachment']['properties'].update({'serverId': {'type': 'string'}})
+
+list_volume_attachments = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumeAttachments': {
+ 'type': 'array',
+ 'items': common_attach_volume_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volumeAttachments']
+ }
+}
+list_volume_attachments['response_body']['properties'][
+ 'volumeAttachments']['items']['properties'].update(
+ {'serverId': {'type': 'string'}})
+
+list_addresses_by_network = {
+ 'status_code': [200],
+ 'response_body': parameter_types.addresses
+}
+
+list_addresses = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'addresses': parameter_types.addresses
+ },
+ 'additionalProperties': False,
+ 'required': ['addresses']
+ }
+}
+
+common_server_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'policies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ # 'members' attribute contains the array of instance's UUID of
+ # instances present in server group
+ 'members': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'metadata': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'policies', 'members', 'metadata']
+}
+
+create_show_server_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_group': common_server_group
+ },
+ 'additionalProperties': False,
+ 'required': ['server_group']
+ }
+}
+
+delete_server_group = {
+ 'status_code': [204]
+}
+
+list_server_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_groups': {
+ 'type': 'array',
+ 'items': common_server_group
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['server_groups']
+ }
+}
+
+instance_actions = {
+ 'type': 'object',
+ 'properties': {
+ 'action': {'type': 'string'},
+ 'request_id': {'type': 'string'},
+ 'user_id': {'type': 'string'},
+ 'project_id': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'message': {'type': ['string', 'null']},
+ 'instance_uuid': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['action', 'request_id', 'user_id', 'project_id',
+ 'start_time', 'message', 'instance_uuid']
+}
+
+instance_action_events = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'event': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'finish_time': {'type': 'string'},
+ 'result': {'type': 'string'},
+ 'traceback': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['event', 'start_time', 'finish_time', 'result',
+ 'traceback']
+ }
+}
+
+list_instance_actions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceActions': {
+ 'type': 'array',
+ 'items': instance_actions
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['instanceActions']
+ }
+}
+
+instance_actions_with_events = copy.deepcopy(instance_actions)
+instance_actions_with_events['properties'].update({
+ 'events': instance_action_events})
+# 'events' does not come in response body always so it is not
+# defined as 'required'
+
+show_instance_action = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceAction': instance_actions_with_events
+ },
+ 'additionalProperties': False,
+ 'required': ['instanceAction']
+ }
+}
+
+show_password = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'password': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['password']
+ }
+}
+
+get_vnc_console = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'console': {
+ 'type': 'object',
+ 'properties': {
+ 'type': {'type': 'string'},
+ 'url': {
+ 'type': 'string',
+ 'format': 'uri'
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['type', 'url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['console']
+ }
+}
+
+get_console_output = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'output': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['output']
+ }
+}
+
+set_server_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['metadata']
+ }
+}
+
+list_server_metadata = copy.deepcopy(set_server_metadata)
+
+update_server_metadata = copy.deepcopy(set_server_metadata)
+
+delete_server_metadata_item = {
+ 'status_code': [204]
+}
+
+set_show_server_metadata_item = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'meta': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^.+$': {'type': 'string'}
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['meta']
+ }
+}
+
+server_actions_common_schema = {
+ 'status_code': [202]
+}
+
+server_actions_delete_password = {
+ 'status_code': [204]
+}
+
+server_actions_confirm_resize = copy.deepcopy(
+ server_actions_delete_password)
+
+update_attached_volume = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/services.py b/tempest/lib/api_schema/response/compute/v2_1/services.py
new file mode 100644
index 0000000..ddef7b2
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/services.py
@@ -0,0 +1,65 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+list_services = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'services': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'string'],
+ 'pattern': '^[a-zA-Z!]*@[0-9]+$'},
+ 'zone': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'binary': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated_at': {'type': ['string', 'null']},
+ 'disabled_reason': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'zone', 'host', 'state', 'binary',
+ 'status', 'updated_at', 'disabled_reason']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['services']
+ }
+}
+
+enable_disable_service = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'service': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'binary': {'type': 'string'},
+ 'host': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'binary', 'host']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['service']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/snapshots.py b/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
new file mode 100644
index 0000000..01a524b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/snapshots.py
@@ -0,0 +1,61 @@
+# Copyright 2015 Fujitsu(fnst) 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.
+
+common_snapshot_info = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'size': {'type': 'integer'},
+ 'createdAt': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'displayDescription': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'volumeId', 'status', 'size',
+ 'createdAt', 'displayName', 'displayDescription']
+}
+
+create_get_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshot': common_snapshot_info
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshot']
+ }
+}
+
+list_snapshots = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshots': {
+ 'type': 'array',
+ 'items': common_snapshot_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshots']
+ }
+}
+
+delete_snapshot = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py b/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py
new file mode 100644
index 0000000..ddfab96
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py
@@ -0,0 +1,53 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+param_network = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'cidr': {'type': ['string', 'null']},
+ 'label': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'cidr', 'label']
+}
+
+
+list_tenant_networks = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'networks': {
+ 'type': 'array',
+ 'items': param_network
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['networks']
+ }
+}
+
+
+get_tenant_network = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'network': param_network
+ },
+ 'additionalProperties': False,
+ 'required': ['network']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py b/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
new file mode 100644
index 0000000..d51ef12
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py
@@ -0,0 +1,92 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+_server_usages = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'ended_at': {
+ 'oneOf': [
+ {'type': 'string'},
+ {'type': 'null'}
+ ]
+ },
+ 'flavor': {'type': 'string'},
+ 'hours': {'type': 'number'},
+ 'instance_id': {'type': 'string'},
+ 'local_gb': {'type': 'integer'},
+ 'memory_mb': {'type': 'integer'},
+ 'name': {'type': 'string'},
+ 'started_at': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'uptime': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ },
+ 'required': ['ended_at', 'flavor', 'hours', 'instance_id', 'local_gb',
+ 'memory_mb', 'name', 'started_at', 'state', 'tenant_id',
+ 'uptime', 'vcpus']
+ }
+}
+
+_tenant_usage_list = {
+ 'type': 'object',
+ 'properties': {
+ 'server_usages': _server_usages,
+ 'start': {'type': 'string'},
+ 'stop': {'type': 'string'},
+ 'tenant_id': {'type': 'string'},
+ 'total_hours': {'type': 'number'},
+ 'total_local_gb_usage': {'type': 'number'},
+ 'total_memory_mb_usage': {'type': 'number'},
+ 'total_vcpus_usage': {'type': 'number'},
+ },
+ 'required': ['start', 'stop', 'tenant_id',
+ 'total_hours', 'total_local_gb_usage',
+ 'total_memory_mb_usage', 'total_vcpus_usage']
+}
+
+# 'required' of get_tenant is different from list_tenant's.
+_tenant_usage_get = copy.deepcopy(_tenant_usage_list)
+_tenant_usage_get['required'] = ['server_usages', 'start', 'stop', 'tenant_id',
+ 'total_hours', 'total_local_gb_usage',
+ 'total_memory_mb_usage', 'total_vcpus_usage']
+
+list_tenant_usage = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'tenant_usages': {
+ 'type': 'array',
+ 'items': _tenant_usage_list
+ }
+ },
+ 'required': ['tenant_usages']
+ }
+}
+
+get_tenant_usage = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'tenant_usage': _tenant_usage_get
+ },
+ 'required': ['tenant_usage']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/versions.py b/tempest/lib/api_schema/response/compute/v2_1/versions.py
new file mode 100644
index 0000000..08a9fab
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/versions.py
@@ -0,0 +1,110 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+
+_version = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {'type': 'string', 'format': 'uri'},
+ 'rel': {'type': 'string'},
+ 'type': {'type': 'string'},
+ },
+ 'required': ['href', 'rel'],
+ 'additionalProperties': False
+ }
+ },
+ 'status': {'type': 'string'},
+ 'updated': {'type': 'string', 'format': 'date-time'},
+ 'version': {'type': 'string'},
+ 'min_version': {'type': 'string'},
+ 'media-types': {
+ 'type': 'array',
+ 'properties': {
+ 'base': {'type': 'string'},
+ 'type': {'type': 'string'},
+ }
+ },
+ },
+ # NOTE: version and min_version have been added since Kilo,
+ # so they should not be required.
+ # NOTE(sdague): media-types only shows up in single version requests.
+ 'required': ['id', 'links', 'status', 'updated'],
+ 'additionalProperties': False
+}
+
+list_versions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'versions': {
+ 'type': 'array',
+ 'items': _version
+ }
+ },
+ 'required': ['versions'],
+ 'additionalProperties': False
+ }
+}
+
+
+_detail_get_version = copy.deepcopy(_version)
+_detail_get_version['properties'].pop('min_version')
+_detail_get_version['properties'].pop('version')
+_detail_get_version['properties'].pop('updated')
+_detail_get_version['properties']['media-types'] = {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'base': {'type': 'string'},
+ 'type': {'type': 'string'}
+ }
+ }
+}
+_detail_get_version['required'] = ['id', 'links', 'status', 'media-types']
+
+get_version = {
+ 'status_code': [300],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'choices': {
+ 'type': 'array',
+ 'items': _detail_get_version
+ }
+ },
+ 'required': ['choices'],
+ 'additionalProperties': False
+ }
+}
+
+get_one_version = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'version': _version
+ },
+ 'additionalProperties': False
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/volumes.py b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
new file mode 100644
index 0000000..bb34acb
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/volumes.py
@@ -0,0 +1,120 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+create_get_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'availabilityZone': {'type': 'string'},
+ 'createdAt': {'type': 'string'},
+ 'displayDescription': {'type': ['string', 'null']},
+ 'volumeType': {'type': ['string', 'null']},
+ 'snapshotId': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'size': {'type': 'integer'},
+ 'attachments': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE- If volume is not attached to any server
+ # then, 'attachments' attributes comes as array
+ # with empty objects "[{}]" due to that elements
+ # of 'attachments' cannot defined as 'required'.
+ # If it would come as empty array "[]" then,
+ # those elements can be defined as 'required'.
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'status', 'displayName', 'availabilityZone',
+ 'createdAt', 'displayDescription', 'volumeType',
+ 'snapshotId', 'metadata', 'size', 'attachments']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volume']
+ }
+}
+
+list_volumes = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumes': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'availabilityZone': {'type': 'string'},
+ 'createdAt': {'type': 'string'},
+ 'displayDescription': {'type': ['string', 'null']},
+ 'volumeType': {'type': ['string', 'null']},
+ 'snapshotId': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'size': {'type': 'integer'},
+ 'attachments': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ # NOTE- If volume is not attached to any server
+ # then, 'attachments' attributes comes as array
+ # with empty object "[{}]" due to that elements
+ # of 'attachments' cannot defined as 'required'
+ # If it would come as empty array "[]" then,
+ # those elements can be defined as 'required'.
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'status', 'displayName',
+ 'availabilityZone', 'createdAt',
+ 'displayDescription', 'volumeType',
+ 'snapshotId', 'metadata', 'size',
+ 'attachments']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['volumes']
+ }
+}
+
+delete_volume = {
+ 'status_code': [202]
+}
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
new file mode 100644
index 0000000..806acb5
--- /dev/null
+++ b/tempest/lib/auth.py
@@ -0,0 +1,676 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+import copy
+import datetime
+import re
+
+from oslo_log import log as logging
+import six
+from six.moves.urllib import parse as urlparse
+
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client as json_v2id
+from tempest.lib.services.identity.v3 import token_client as json_v3id
+
+ISO8601_FLOAT_SECONDS = '%Y-%m-%dT%H:%M:%S.%fZ'
+ISO8601_INT_SECONDS = '%Y-%m-%dT%H:%M:%SZ'
+LOG = logging.getLogger(__name__)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class AuthProvider(object):
+ """Provide authentication"""
+
+ def __init__(self, credentials):
+ """Auth provider __init__
+
+ :param credentials: credentials for authentication
+ """
+ if self.check_credentials(credentials):
+ self.credentials = credentials
+ else:
+ if isinstance(credentials, Credentials):
+ password = credentials.get('password')
+ message = "Credentials are: " + str(credentials)
+ if password is None:
+ message += " Password is not defined."
+ else:
+ message += " Password is defined."
+ raise exceptions.InvalidCredentials(message)
+ else:
+ raise TypeError("credentials object is of type %s, which is"
+ " not a valid Credentials object type." %
+ credentials.__class__.__name__)
+ self.cache = None
+ self.alt_auth_data = None
+ self.alt_part = None
+
+ def __str__(self):
+ return "Creds :{creds}, cached auth data: {cache}".format(
+ creds=self.credentials, cache=self.cache)
+
+ @abc.abstractmethod
+ def _decorate_request(self, filters, method, url, headers=None, body=None,
+ auth_data=None):
+ """Decorate request with authentication data"""
+ return
+
+ @abc.abstractmethod
+ def _get_auth(self):
+ return
+
+ @abc.abstractmethod
+ def _fill_credentials(self, auth_data_body):
+ return
+
+ def fill_credentials(self):
+ """Fill credentials object with data from auth"""
+ auth_data = self.get_auth()
+ self._fill_credentials(auth_data[1])
+ return self.credentials
+
+ @classmethod
+ def check_credentials(cls, credentials):
+ """Verify credentials are valid."""
+ return isinstance(credentials, Credentials) and credentials.is_valid()
+
+ @property
+ def auth_data(self):
+ return self.get_auth()
+
+ @auth_data.deleter
+ def auth_data(self):
+ self.clear_auth()
+
+ def get_auth(self):
+ """Returns auth from cache if available, else auth first"""
+ if self.cache is None or self.is_expired(self.cache):
+ self.set_auth()
+ return self.cache
+
+ def set_auth(self):
+ """Forces setting auth.
+
+ Forces setting auth, ignores cache if it exists.
+ Refills credentials
+ """
+ self.cache = self._get_auth()
+ self._fill_credentials(self.cache[1])
+
+ def clear_auth(self):
+ """Clear access cache
+
+ Can be called to clear the access cache so that next request
+ will fetch a new token and base_url.
+ """
+ self.cache = None
+ self.credentials.reset()
+
+ @abc.abstractmethod
+ def is_expired(self, auth_data):
+ return
+
+ def auth_request(self, method, url, headers=None, body=None, filters=None):
+ """Obtains auth data and decorates a request with that.
+
+ :param method: HTTP method of the request
+ :param url: relative URL of the request (path)
+ :param headers: HTTP headers of the request
+ :param body: HTTP body in case of POST / PUT
+ :param filters: select a base URL out of the catalog
+ :returns a Tuple (url, headers, body)
+ """
+ orig_req = dict(url=url, headers=headers, body=body)
+
+ auth_url, auth_headers, auth_body = self._decorate_request(
+ filters, method, url, headers, body)
+ auth_req = dict(url=auth_url, headers=auth_headers, body=auth_body)
+
+ # Overwrite part if the request if it has been requested
+ if self.alt_part is not None:
+ if self.alt_auth_data is not None:
+ alt_url, alt_headers, alt_body = self._decorate_request(
+ filters, method, url, headers, body,
+ auth_data=self.alt_auth_data)
+ alt_auth_req = dict(url=alt_url, headers=alt_headers,
+ body=alt_body)
+ if auth_req[self.alt_part] == alt_auth_req[self.alt_part]:
+ raise exceptions.BadAltAuth(part=self.alt_part)
+ auth_req[self.alt_part] = alt_auth_req[self.alt_part]
+
+ else:
+ # If the requested part is not affected by auth, we are
+ # not altering auth as expected, raise an exception
+ if auth_req[self.alt_part] == orig_req[self.alt_part]:
+ raise exceptions.BadAltAuth(part=self.alt_part)
+ # If alt auth data is None, skip auth in the requested part
+ auth_req[self.alt_part] = orig_req[self.alt_part]
+
+ # Next auth request will be normal, unless otherwise requested
+ self.reset_alt_auth_data()
+
+ return auth_req['url'], auth_req['headers'], auth_req['body']
+
+ def reset_alt_auth_data(self):
+ """Configure auth provider to provide valid authentication data"""
+ self.alt_part = None
+ self.alt_auth_data = None
+
+ def set_alt_auth_data(self, request_part, auth_data):
+ """Alternate auth data on next request
+
+ Configure auth provider to provide alt authentication data
+ on a part of the *next* auth_request. If credentials are None,
+ set invalid data.
+ :param request_part: request part to contain invalid auth: url,
+ headers, body
+ :param auth_data: alternative auth_data from which to get the
+ invalid data to be injected
+ """
+ self.alt_part = request_part
+ self.alt_auth_data = auth_data
+
+ @abc.abstractmethod
+ def base_url(self, filters, auth_data=None):
+ """Extracts the base_url based on provided filters"""
+ return
+
+
+class KeystoneAuthProvider(AuthProvider):
+
+ EXPIRY_DATE_FORMATS = (ISO8601_FLOAT_SECONDS, ISO8601_INT_SECONDS)
+
+ token_expiry_threshold = datetime.timedelta(seconds=60)
+
+ def __init__(self, credentials, auth_url,
+ disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ super(KeystoneAuthProvider, self).__init__(credentials)
+ self.dsvm = disable_ssl_certificate_validation
+ self.ca_certs = ca_certs
+ self.trace_requests = trace_requests
+ self.auth_client = self._auth_client(auth_url)
+
+ def _decorate_request(self, filters, method, url, headers=None, body=None,
+ auth_data=None):
+ if auth_data is None:
+ auth_data = self.auth_data
+ token, _ = auth_data
+ base_url = self.base_url(filters=filters, auth_data=auth_data)
+ # build authenticated request
+ # returns new request, it does not touch the original values
+ _headers = copy.deepcopy(headers) if headers is not None else {}
+ _headers['X-Auth-Token'] = str(token)
+ if url is None or url == "":
+ _url = base_url
+ else:
+ # Join base URL and url, and remove multiple contiguous slashes
+ _url = "/".join([base_url, url])
+ parts = [x for x in urlparse.urlparse(_url)]
+ parts[2] = re.sub("/{2,}", "/", parts[2])
+ _url = urlparse.urlunparse(parts)
+ # no change to method or body
+ return str(_url), _headers, body
+
+ @abc.abstractmethod
+ def _auth_client(self):
+ return
+
+ @abc.abstractmethod
+ def _auth_params(self):
+ return
+
+ def _get_auth(self):
+ # Bypasses the cache
+ auth_func = getattr(self.auth_client, 'get_token')
+ auth_params = self._auth_params()
+
+ # returns token, auth_data
+ token, auth_data = auth_func(**auth_params)
+ return token, auth_data
+
+ def _parse_expiry_time(self, expiry_string):
+ expiry = None
+ for date_format in self.EXPIRY_DATE_FORMATS:
+ try:
+ expiry = datetime.datetime.strptime(
+ expiry_string, date_format)
+ except ValueError:
+ pass
+ if expiry is None:
+ raise ValueError(
+ "time data '{data}' does not match any of the"
+ "expected formats: {formats}".format(
+ data=expiry_string, formats=self.EXPIRY_DATE_FORMATS))
+ return expiry
+
+ def get_token(self):
+ return self.auth_data[0]
+
+
+class KeystoneV2AuthProvider(KeystoneAuthProvider):
+
+ def _auth_client(self, auth_url):
+ return json_v2id.TokenClient(
+ auth_url, disable_ssl_certificate_validation=self.dsvm,
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+
+ def _auth_params(self):
+ return dict(
+ user=self.credentials.username,
+ password=self.credentials.password,
+ tenant=self.credentials.tenant_name,
+ auth_data=True)
+
+ def _fill_credentials(self, auth_data_body):
+ tenant = auth_data_body['token']['tenant']
+ user = auth_data_body['user']
+ if self.credentials.tenant_name is None:
+ self.credentials.tenant_name = tenant['name']
+ if self.credentials.tenant_id is None:
+ self.credentials.tenant_id = tenant['id']
+ if self.credentials.username is None:
+ self.credentials.username = user['name']
+ if self.credentials.user_id is None:
+ self.credentials.user_id = user['id']
+
+ def base_url(self, filters, auth_data=None):
+ """Base URL from catalog
+
+ Filters can be:
+ - service: compute, image, etc
+ - region: the service region
+ - endpoint_type: adminURL, publicURL, internalURL
+ - api_version: replace catalog version with this
+ - skip_path: take just the base URL
+ """
+ if auth_data is None:
+ auth_data = self.auth_data
+ token, _auth_data = auth_data
+ service = filters.get('service')
+ region = filters.get('region')
+ endpoint_type = filters.get('endpoint_type', 'publicURL')
+
+ if service is None:
+ raise exceptions.EndpointNotFound("No service provided")
+
+ _base_url = None
+ for ep in _auth_data['serviceCatalog']:
+ if ep["type"] == service:
+ for _ep in ep['endpoints']:
+ if region is not None and _ep['region'] == region:
+ _base_url = _ep.get(endpoint_type)
+ if not _base_url:
+ # No region matching, use the first
+ _base_url = ep['endpoints'][0].get(endpoint_type)
+ break
+ if _base_url is None:
+ raise exceptions.EndpointNotFound(service)
+
+ parts = urlparse.urlparse(_base_url)
+ if filters.get('api_version', None) is not None:
+ path = "/" + filters['api_version']
+ noversion_path = "/".join(parts.path.split("/")[2:])
+ if noversion_path != "":
+ path += "/" + noversion_path
+ _base_url = _base_url.replace(parts.path, path)
+ if filters.get('skip_path', None) is not None and parts.path != '':
+ _base_url = _base_url.replace(parts.path, "/")
+
+ return _base_url
+
+ def is_expired(self, auth_data):
+ _, access = auth_data
+ expiry = self._parse_expiry_time(access['token']['expires'])
+ return (expiry - self.token_expiry_threshold <=
+ datetime.datetime.utcnow())
+
+
+class KeystoneV3AuthProvider(KeystoneAuthProvider):
+
+ def _auth_client(self, auth_url):
+ return json_v3id.V3TokenClient(
+ auth_url, disable_ssl_certificate_validation=self.dsvm,
+ ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+
+ def _auth_params(self):
+ return dict(
+ user_id=self.credentials.user_id,
+ username=self.credentials.username,
+ password=self.credentials.password,
+ project_id=self.credentials.project_id,
+ project_name=self.credentials.project_name,
+ user_domain_id=self.credentials.user_domain_id,
+ user_domain_name=self.credentials.user_domain_name,
+ project_domain_id=self.credentials.project_domain_id,
+ project_domain_name=self.credentials.project_domain_name,
+ domain_id=self.credentials.domain_id,
+ domain_name=self.credentials.domain_name,
+ auth_data=True)
+
+ def _fill_credentials(self, auth_data_body):
+ # project or domain, depending on the scope
+ project = auth_data_body.get('project', None)
+ domain = auth_data_body.get('domain', None)
+ # user is always there
+ user = auth_data_body['user']
+ # Set project fields
+ if project is not None:
+ if self.credentials.project_name is None:
+ self.credentials.project_name = project['name']
+ if self.credentials.project_id is None:
+ self.credentials.project_id = project['id']
+ if self.credentials.project_domain_id is None:
+ self.credentials.project_domain_id = project['domain']['id']
+ if self.credentials.project_domain_name is None:
+ self.credentials.project_domain_name = (
+ project['domain']['name'])
+ # Set domain fields
+ if domain is not None:
+ if self.credentials.domain_id is None:
+ self.credentials.domain_id = domain['id']
+ if self.credentials.domain_name is None:
+ self.credentials.domain_name = domain['name']
+ # Set user fields
+ if self.credentials.username is None:
+ self.credentials.username = user['name']
+ if self.credentials.user_id is None:
+ self.credentials.user_id = user['id']
+ if self.credentials.user_domain_id is None:
+ self.credentials.user_domain_id = user['domain']['id']
+ if self.credentials.user_domain_name is None:
+ self.credentials.user_domain_name = user['domain']['name']
+
+ def base_url(self, filters, auth_data=None):
+ """Base URL from catalog
+
+ Filters can be:
+ - service: compute, image, etc
+ - region: the service region
+ - endpoint_type: adminURL, publicURL, internalURL
+ - api_version: replace catalog version with this
+ - skip_path: take just the base URL
+ """
+ if auth_data is None:
+ auth_data = self.auth_data
+ token, _auth_data = auth_data
+ service = filters.get('service')
+ region = filters.get('region')
+ endpoint_type = filters.get('endpoint_type', 'public')
+
+ if service is None:
+ raise exceptions.EndpointNotFound("No service provided")
+
+ if 'URL' in endpoint_type:
+ endpoint_type = endpoint_type.replace('URL', '')
+ _base_url = None
+ catalog = _auth_data['catalog']
+ # Select entries with matching service type
+ service_catalog = [ep for ep in catalog if ep['type'] == service]
+ if len(service_catalog) > 0:
+ service_catalog = service_catalog[0]['endpoints']
+ else:
+ # No matching service
+ raise exceptions.EndpointNotFound(service)
+ # Filter by endpoint type (interface)
+ filtered_catalog = [ep for ep in service_catalog if
+ ep['interface'] == endpoint_type]
+ if len(filtered_catalog) == 0:
+ # No matching type, keep all and try matching by region at least
+ filtered_catalog = service_catalog
+ # Filter by region
+ filtered_catalog = [ep for ep in filtered_catalog if
+ ep['region'] == region]
+ if len(filtered_catalog) == 0:
+ # No matching region, take the first endpoint
+ filtered_catalog = [service_catalog[0]]
+ # There should be only one match. If not take the first.
+ _base_url = filtered_catalog[0].get('url', None)
+ if _base_url is None:
+ raise exceptions.EndpointNotFound(service)
+
+ parts = urlparse.urlparse(_base_url)
+ if filters.get('api_version', None) is not None:
+ path = "/" + filters['api_version']
+ noversion_path = "/".join(parts.path.split("/")[2:])
+ if noversion_path != "":
+ path += "/" + noversion_path
+ _base_url = _base_url.replace(parts.path, path)
+ if filters.get('skip_path', None) is not None:
+ _base_url = _base_url.replace(parts.path, "/")
+
+ return _base_url
+
+ def is_expired(self, auth_data):
+ _, access = auth_data
+ expiry = self._parse_expiry_time(access['expires_at'])
+ return (expiry - self.token_expiry_threshold <=
+ datetime.datetime.utcnow())
+
+
+def is_identity_version_supported(identity_version):
+ return identity_version in IDENTITY_VERSION
+
+
+def get_credentials(auth_url, fill_in=True, identity_version='v2',
+ disable_ssl_certificate_validation=None, ca_certs=None,
+ trace_requests=None, **kwargs):
+ """Builds a credentials object based on the configured auth_version
+
+ :param auth_url (string): Full URI of the OpenStack Identity API(Keystone)
+ which is used to fetch the token from Identity service.
+ :param fill_in (boolean): obtain a token and fill in all credential
+ details provided by the identity service. When fill_in is not
+ specified, credentials are not validated. Validation can be invoked
+ by invoking ``is_valid()``
+ :param identity_version (string): identity API version is used to
+ select the matching auth provider and credentials class
+ :param disable_ssl_certificate_validation: whether to enforce SSL
+ certificate validation in SSL API requests to the auth system
+ :param ca_certs: CA certificate bundle for validation of certificates
+ in SSL API requests to the auth system
+ :param trace_requests: trace in log API requests to the auth system
+ :param kwargs (dict): Dict of credential key/value pairs
+
+ Examples:
+
+ Returns credentials from the provided parameters:
+ >>> get_credentials(username='foo', password='bar')
+
+ Returns credentials including IDs:
+ >>> get_credentials(username='foo', password='bar', fill_in=True)
+ """
+ if not is_identity_version_supported(identity_version):
+ raise exceptions.InvalidIdentityVersion(
+ identity_version=identity_version)
+
+ credential_class, auth_provider_class = IDENTITY_VERSION.get(
+ identity_version)
+
+ creds = credential_class(**kwargs)
+ # Fill in the credentials fields that were not specified
+ if fill_in:
+ dsvm = disable_ssl_certificate_validation
+ auth_provider = auth_provider_class(
+ creds, auth_url, disable_ssl_certificate_validation=dsvm,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+ creds = auth_provider.fill_credentials()
+ return creds
+
+
+class Credentials(object):
+ """Set of credentials for accessing OpenStack services
+
+ ATTRIBUTES: list of valid class attributes representing credentials.
+ """
+
+ ATTRIBUTES = []
+
+ def __init__(self, **kwargs):
+ """Enforce the available attributes at init time (only).
+
+ Additional attributes can still be set afterwards if tests need
+ to do so.
+ """
+ self._initial = kwargs
+ self._apply_credentials(kwargs)
+
+ def _apply_credentials(self, attr):
+ for key in attr.keys():
+ if key in self.ATTRIBUTES:
+ setattr(self, key, attr[key])
+ else:
+ msg = '%s is not a valid attr for %s' % (key, self.__class__)
+ raise exceptions.InvalidCredentials(msg)
+
+ def __str__(self):
+ """Represent only attributes included in self.ATTRIBUTES"""
+ attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
+ _repr = dict((k, getattr(self, k)) for k in attrs)
+ return str(_repr)
+
+ def __eq__(self, other):
+ """Credentials are equal if attributes in self.ATTRIBUTES are equal"""
+ return str(self) == str(other)
+
+ def __getattr__(self, key):
+ # If an attribute is set, __getattr__ is not invoked
+ # If an attribute is not set, and it is a known one, return None
+ if key in self.ATTRIBUTES:
+ return None
+ else:
+ raise AttributeError
+
+ def __delitem__(self, key):
+ # For backwards compatibility, support dict behaviour
+ if key in self.ATTRIBUTES:
+ delattr(self, key)
+ else:
+ raise AttributeError
+
+ def get(self, item, default=None):
+ # In this patch act as dict for backward compatibility
+ try:
+ return getattr(self, item)
+ except AttributeError:
+ return default
+
+ def get_init_attributes(self):
+ return self._initial.keys()
+
+ def is_valid(self):
+ raise NotImplementedError
+
+ def reset(self):
+ # First delete all known attributes
+ for key in self.ATTRIBUTES:
+ if getattr(self, key) is not None:
+ delattr(self, key)
+ # Then re-apply initial setup
+ self._apply_credentials(self._initial)
+
+
+class KeystoneV2Credentials(Credentials):
+
+ ATTRIBUTES = ['username', 'password', 'tenant_name', 'user_id',
+ 'tenant_id']
+
+ def is_valid(self):
+ """Check of credentials (no API call)
+
+ Minimum set of valid credentials, are username and password.
+ Tenant is optional.
+ """
+ return None not in (self.username, self.password)
+
+
+class KeystoneV3Credentials(Credentials):
+ """Credentials suitable for the Keystone Identity V3 API"""
+
+ ATTRIBUTES = ['domain_id', 'domain_name', 'password', 'username',
+ 'project_domain_id', 'project_domain_name', 'project_id',
+ 'project_name', 'tenant_id', 'tenant_name', 'user_domain_id',
+ 'user_domain_name', 'user_id']
+
+ def __setattr__(self, key, value):
+ parent = super(KeystoneV3Credentials, self)
+ # for tenant_* set both project and tenant
+ if key == 'tenant_id':
+ parent.__setattr__('project_id', value)
+ elif key == 'tenant_name':
+ parent.__setattr__('project_name', value)
+ # for project_* set both project and tenant
+ if key == 'project_id':
+ parent.__setattr__('tenant_id', value)
+ elif key == 'project_name':
+ parent.__setattr__('tenant_name', value)
+ # for *_domain_* set both user and project if not set yet
+ if key == 'user_domain_id':
+ if self.project_domain_id is None:
+ parent.__setattr__('project_domain_id', value)
+ if key == 'project_domain_id':
+ if self.user_domain_id is None:
+ parent.__setattr__('user_domain_id', value)
+ if key == 'user_domain_name':
+ if self.project_domain_name is None:
+ parent.__setattr__('project_domain_name', value)
+ if key == 'project_domain_name':
+ if self.user_domain_name is None:
+ parent.__setattr__('user_domain_name', value)
+ # support domain_name coming from config
+ if key == 'domain_name':
+ parent.__setattr__('user_domain_name', value)
+ parent.__setattr__('project_domain_name', value)
+ # finally trigger default behaviour for all attributes
+ parent.__setattr__(key, value)
+
+ def is_valid(self):
+ """Check of credentials (no API call)
+
+ Valid combinations of v3 credentials (excluding token, scope)
+ - User id, password (optional domain)
+ - User name, password and its domain id/name
+ For the scope, valid combinations are:
+ - None
+ - Project id (optional domain)
+ - Project name and its domain id/name
+ - Domain id
+ - Domain name
+ """
+ valid_user_domain = any(
+ [self.user_domain_id is not None,
+ self.user_domain_name is not None])
+ valid_project_domain = any(
+ [self.project_domain_id is not None,
+ self.project_domain_name is not None])
+ valid_user = any(
+ [self.user_id is not None,
+ self.username is not None and valid_user_domain])
+ valid_project_scope = any(
+ [self.project_name is None and self.project_id is None,
+ self.project_id is not None,
+ self.project_name is not None and valid_project_domain])
+ valid_domain_scope = any(
+ [self.domain_id is None and self.domain_name is None,
+ self.domain_id or self.domain_name])
+ return all([self.password is not None,
+ valid_user,
+ valid_project_scope and valid_domain_scope])
+
+
+IDENTITY_VERSION = {'v2': (KeystoneV2Credentials, KeystoneV2AuthProvider),
+ 'v3': (KeystoneV3Credentials, KeystoneV3AuthProvider)}
diff --git a/tempest/lib/base.py b/tempest/lib/base.py
new file mode 100644
index 0000000..227ac37
--- /dev/null
+++ b/tempest/lib/base.py
@@ -0,0 +1,71 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os
+
+import fixtures
+import testtools
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseTestCase(testtools.testcase.WithAttributes, testtools.TestCase):
+ setUpClassCalled = False
+
+ # NOTE(sdague): log_format is defined inline here instead of using the oslo
+ # default because going through the config path recouples config to the
+ # stress tests too early, and depending on testr order will fail unit tests
+ log_format = ('%(asctime)s %(process)d %(levelname)-8s '
+ '[%(name)s] %(message)s')
+
+ @classmethod
+ def setUpClass(cls):
+ if hasattr(super(BaseTestCase, cls), 'setUpClass'):
+ super(BaseTestCase, cls).setUpClass()
+ cls.setUpClassCalled = True
+
+ @classmethod
+ def tearDownClass(cls):
+ if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
+ super(BaseTestCase, cls).tearDownClass()
+
+ def setUp(self):
+ super(BaseTestCase, self).setUp()
+ if not self.setUpClassCalled:
+ raise RuntimeError("setUpClass does not calls the super's"
+ "setUpClass in the "
+ + self.__class__.__name__)
+ test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
+ try:
+ test_timeout = int(test_timeout)
+ except ValueError:
+ test_timeout = 0
+ if test_timeout > 0:
+ self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
+
+ if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
+ os.environ.get('OS_STDOUT_CAPTURE') == '1'):
+ stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+ if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
+ os.environ.get('OS_STDERR_CAPTURE') == '1'):
+ stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+ if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
+ os.environ.get('OS_LOG_CAPTURE') != '0'):
+ self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
+ format=self.log_format,
+ level=None))
diff --git a/tempest/lib/cli/__init__.py b/tempest/lib/cli/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/cli/__init__.py
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
new file mode 100644
index 0000000..54f35f4
--- /dev/null
+++ b/tempest/lib/cli/base.py
@@ -0,0 +1,410 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os
+import shlex
+import subprocess
+
+import six
+
+from tempest.lib import base
+import tempest.lib.cli.output_parser
+from tempest.lib import exceptions
+
+
+LOG = logging.getLogger(__name__)
+
+
+def execute(cmd, action, flags='', params='', fail_ok=False,
+ merge_stderr=False, cli_dir='/usr/bin'):
+ """Executes specified command for the given action.
+
+ :param cmd: command to be executed
+ :type cmd: string
+ :param action: string of the cli command to run
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: string of any optional positional args to use
+ :type params: string
+ :param fail_ok: boolean if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: boolean if True the stderr buffer is merged into
+ stdout
+ :type merge_stderr: boolean
+ :param cli_dir: The path where the cmd can be executed
+ :type cli_dir: string
+ """
+ cmd = ' '.join([os.path.join(cli_dir, cmd),
+ flags, action, params])
+ LOG.info("running: '%s'" % cmd)
+ if six.PY2:
+ cmd = cmd.encode('utf-8')
+ cmd = shlex.split(cmd)
+ result = ''
+ result_err = ''
+ stdout = subprocess.PIPE
+ stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
+ proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
+ result, result_err = proc.communicate()
+ if not fail_ok and proc.returncode != 0:
+ raise exceptions.CommandFailed(proc.returncode,
+ cmd,
+ result,
+ result_err)
+ if six.PY2:
+ return result
+ else:
+ return os.fsdecode(result)
+
+
+class CLIClient(object):
+ """Class to use OpenStack official python client CLI's with auth
+
+ :param username: The username to authenticate with
+ :type username: string
+ :param password: The password to authenticate with
+ :type password: string
+ :param tenant_name: The name of the tenant to use with the client calls
+ :type tenant_name: string
+ :param uri: The auth uri for the OpenStack Deployment
+ :type uri: string
+ :param cli_dir: The path where the python client binaries are installed.
+ defaults to /usr/bin
+ :type cli_dir: string
+ :param insecure: if True, --insecure is passed to python client binaries.
+ :type insecure: boolean
+ """
+
+ def __init__(self, username='', password='', tenant_name='', uri='',
+ cli_dir='', insecure=False, *args, **kwargs):
+ """Initialize a new CLIClient object."""
+ super(CLIClient, self).__init__()
+ self.cli_dir = cli_dir if cli_dir else '/usr/bin'
+ self.username = username
+ self.tenant_name = tenant_name
+ self.password = password
+ self.uri = uri
+ self.insecure = insecure
+
+ def nova(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes nova command for the given action.
+
+ :param action: the cli command to run using nova
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'nova', action, flags, params, fail_ok, merge_stderr)
+
+ def nova_manage(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes nova-manage command for the given action.
+
+ :param action: the cli command to run using nova-manage
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ return execute(
+ 'nova-manage', action, flags, params, fail_ok, merge_stderr,
+ self.cli_dir)
+
+ def keystone(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes keystone command for the given action.
+
+ :param action: the cli command to run using keystone
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ return self.cmd_with_auth(
+ 'keystone', action, flags, params, fail_ok, merge_stderr)
+
+ def glance(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes glance command for the given action.
+
+ :param action: the cli command to run using glance
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'glance', action, flags, params, fail_ok, merge_stderr)
+
+ def ceilometer(self, action, flags='', params='',
+ fail_ok=False, endpoint_type='publicURL',
+ merge_stderr=False):
+ """Executes ceilometer command for the given action.
+
+ :param action: the cli command to run using ceilometer
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'ceilometer', action, flags, params, fail_ok, merge_stderr)
+
+ def heat(self, action, flags='', params='',
+ fail_ok=False, endpoint_type='publicURL', merge_stderr=False):
+ """Executes heat command for the given action.
+
+ :param action: the cli command to run using heat
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'heat', action, flags, params, fail_ok, merge_stderr)
+
+ def cinder(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes cinder command for the given action.
+
+ :param action: the cli command to run using cinder
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'cinder', action, flags, params, fail_ok, merge_stderr)
+
+ def swift(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes swift command for the given action.
+
+ :param action: the cli command to run using swift
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --os-endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'swift', action, flags, params, fail_ok, merge_stderr)
+
+ def neutron(self, action, flags='', params='', fail_ok=False,
+ endpoint_type='publicURL', merge_stderr=False):
+ """Executes neutron command for the given action.
+
+ :param action: the cli command to run using neutron
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'neutron', action, flags, params, fail_ok, merge_stderr)
+
+ def sahara(self, action, flags='', params='',
+ fail_ok=False, endpoint_type='publicURL', merge_stderr=True):
+ """Executes sahara command for the given action.
+
+ :param action: the cli command to run using sahara
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param endpoint_type: the type of endpoint for the service
+ :type endpoint_type: string
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ flags += ' --endpoint-type %s' % endpoint_type
+ return self.cmd_with_auth(
+ 'sahara', action, flags, params, fail_ok, merge_stderr)
+
+ def openstack(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes openstack command for the given action.
+
+ :param action: the cli command to run using openstack
+ :type action: string
+ :param flags: any optional cli flags to use
+ :type flags: string
+ :param params: any optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the
+ cli return code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ return self.cmd_with_auth(
+ 'openstack', action, flags, params, fail_ok, merge_stderr)
+
+ def cmd_with_auth(self, cmd, action, flags='', params='',
+ fail_ok=False, merge_stderr=False):
+ """Executes given command with auth attributes appended.
+
+ :param cmd: command to be executed
+ :type cmd: string
+ :param action: command on cli to run
+ :type action: string
+ :param flags: optional cli flags to use
+ :type flags: string
+ :param params: optional positional args to use
+ :type params: string
+ :param fail_ok: if True an exception is not raised when the cli return
+ code is non-zero
+ :type fail_ok: boolean
+ :param merge_stderr: if True the stderr buffer is merged into stdout
+ :type merge_stderr: boolean
+ """
+ creds = ('--os-username %s --os-tenant-name %s --os-password %s '
+ '--os-auth-url %s' %
+ (self.username,
+ self.tenant_name,
+ self.password,
+ self.uri))
+ if self.insecure:
+ flags = creds + ' --insecure ' + flags
+ else:
+ flags = creds + ' ' + flags
+ return execute(cmd, action, flags, params, fail_ok, merge_stderr,
+ self.cli_dir)
+
+
+class ClientTestBase(base.BaseTestCase):
+ """Base test class for testing the OpenStack client CLI interfaces."""
+
+ def setUp(self):
+ super(ClientTestBase, self).setUp()
+ self.clients = self._get_clients()
+ self.parser = tempest.lib.cli.output_parser
+
+ def _get_clients(self):
+ """Abstract method to initialize CLIClient object.
+
+ This method must be overloaded in child test classes. It should be
+ used to initialize the CLIClient object with the appropriate
+ credentials during the setUp() phase of tests.
+ """
+ raise NotImplementedError
+
+ def assertTableStruct(self, items, field_names):
+ """Verify that all items has keys listed in field_names.
+
+ :param items: items to assert are field names in the output table
+ :type items: list
+ :param field_names: field names from the output table of the cmd
+ :type field_names: list
+ """
+ for item in items:
+ for field in field_names:
+ self.assertIn(field, item)
+
+ def assertFirstLineStartsWith(self, lines, beginning):
+ """Verify that the first line starts with a string
+
+ :param lines: strings for each line of output
+ :type lines: list
+ :param beginning: verify this is at the beginning of the first line
+ :type beginning: string
+ """
+ self.assertTrue(lines[0].startswith(beginning),
+ msg=('Beginning of first line has invalid content: %s'
+ % lines[:3]))
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
new file mode 100644
index 0000000..0313505
--- /dev/null
+++ b/tempest/lib/cli/output_parser.py
@@ -0,0 +1,170 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Collection of utilities for parsing CLI clients output."""
+
+import logging
+import re
+
+from tempest.lib import exceptions
+
+
+LOG = logging.getLogger(__name__)
+
+
+delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+
+
+def details_multiple(output_lines, with_label=False):
+ """Return list of dicts with item details from cli output tables.
+
+ If with_label is True, key '__label' is added to each items dict.
+ For more about 'label' see OutputParser.tables().
+ """
+ items = []
+ tables_ = tables(output_lines)
+ for table_ in tables_:
+ if ('Property' not in table_['headers']
+ or 'Value' not in table_['headers']):
+ raise exceptions.InvalidStructure()
+ item = {}
+ for value in table_['values']:
+ item[value[0]] = value[1]
+ if with_label:
+ item['__label'] = table_['label']
+ items.append(item)
+ return items
+
+
+def details(output_lines, with_label=False):
+ """Return dict with details of first item (table) found in output."""
+ items = details_multiple(output_lines, with_label)
+ return items[0]
+
+
+def listing(output_lines):
+ """Return list of dicts with basic item info parsed from cli output."""
+
+ items = []
+ table_ = table(output_lines)
+ for row in table_['values']:
+ item = {}
+ for col_idx, col_key in enumerate(table_['headers']):
+ item[col_key] = row[col_idx]
+ items.append(item)
+ return items
+
+
+def tables(output_lines):
+ """Find all ascii-tables in output and parse them.
+
+ Return list of tables parsed from cli output as dicts.
+ (see OutputParser.table())
+
+ And, if found, label key (separated line preceding the table)
+ is added to each tables dict.
+ """
+ tables_ = []
+
+ table_ = []
+ label = None
+
+ start = False
+ header = False
+
+ if not isinstance(output_lines, list):
+ output_lines = output_lines.split('\n')
+
+ for line in output_lines:
+ if delimiter_line.match(line):
+ if not start:
+ start = True
+ elif not header:
+ # we are after head area
+ header = True
+ else:
+ # table ends here
+ start = header = None
+ table_.append(line)
+
+ parsed = table(table_)
+ parsed['label'] = label
+ tables_.append(parsed)
+
+ table_ = []
+ label = None
+ continue
+ if start:
+ table_.append(line)
+ else:
+ if label is None:
+ label = line
+ else:
+ LOG.warning('Invalid line between tables: %s' % line)
+ if len(table_) > 0:
+ LOG.warning('Missing end of table')
+
+ return tables_
+
+
+def table(output_lines):
+ """Parse single table from cli output.
+
+ Return dict with list of column names in 'headers' key and
+ rows in 'values' key.
+ """
+ table_ = {'headers': [], 'values': []}
+ columns = None
+
+ if not isinstance(output_lines, list):
+ output_lines = output_lines.split('\n')
+
+ if not output_lines[-1]:
+ # skip last line if empty (just newline at the end)
+ output_lines = output_lines[:-1]
+
+ for line in output_lines:
+ if delimiter_line.match(line):
+ columns = _table_columns(line)
+ continue
+ if '|' not in line:
+ LOG.warning('skipping invalid table line: %s' % line)
+ continue
+ row = []
+ for col in columns:
+ row.append(line[col[0]:col[1]].strip())
+ if table_['headers']:
+ table_['values'].append(row)
+ else:
+ table_['headers'] = row
+
+ return table_
+
+
+def _table_columns(first_table_row):
+ """Find column ranges in output line.
+
+ Return list of tuples (start,end) for each column
+ detected by plus (+) characters in delimiter line.
+ """
+ positions = []
+ start = 1 # there is '+' at 0
+ while start < len(first_table_row):
+ end = first_table_row.find('+', start)
+ if end == -1:
+ break
+ positions.append((start, end))
+ start = end + 1
+ return positions
diff --git a/tempest/lib/cmd/__init__.py b/tempest/lib/cmd/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/cmd/__init__.py
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
new file mode 100755
index 0000000..3adeecd
--- /dev/null
+++ b/tempest/lib/cmd/check_uuid.py
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+
+# Copyright 2014 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import argparse
+import ast
+import importlib
+import inspect
+import os
+import sys
+import unittest
+import uuid
+
+import six.moves.urllib.parse as urlparse
+
+DECORATOR_MODULE = 'test'
+DECORATOR_NAME = 'idempotent_id'
+DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
+IMPORT_LINE = 'from tempest import %s' % DECORATOR_MODULE
+DECORATOR_TEMPLATE = "@%s.%s('%%s')" % (DECORATOR_MODULE,
+ DECORATOR_NAME)
+UNIT_TESTS_EXCLUDE = 'tempest.tests'
+
+
+class SourcePatcher(object):
+
+ """"Lazy patcher for python source files"""
+
+ def __init__(self):
+ self.source_files = None
+ self.patches = None
+ self.clear()
+
+ def clear(self):
+ """Clear inner state"""
+ self.source_files = {}
+ self.patches = {}
+
+ @staticmethod
+ def _quote(s):
+ return urlparse.quote(s)
+
+ @staticmethod
+ def _unquote(s):
+ return urlparse.unquote(s)
+
+ def add_patch(self, filename, patch, line_no):
+ """Add lazy patch"""
+ if filename not in self.source_files:
+ with open(filename) as f:
+ self.source_files[filename] = self._quote(f.read())
+ patch_id = str(uuid.uuid4())
+ if not patch.endswith('\n'):
+ patch += '\n'
+ self.patches[patch_id] = self._quote(patch)
+ lines = self.source_files[filename].split(self._quote('\n'))
+ lines[line_no - 1] = ''.join(('{%s:s}' % patch_id, lines[line_no - 1]))
+ self.source_files[filename] = self._quote('\n').join(lines)
+
+ def _save_changes(self, filename, source):
+ print('%s fixed' % filename)
+ with open(filename, 'w') as f:
+ f.write(source)
+
+ def apply_patches(self):
+ """Apply all patches"""
+ for filename in self.source_files:
+ patched_source = self._unquote(
+ self.source_files[filename].format(**self.patches)
+ )
+ self._save_changes(filename, patched_source)
+ self.clear()
+
+
+class TestChecker(object):
+
+ def __init__(self, package):
+ self.package = package
+ self.base_path = os.path.abspath(os.path.dirname(package.__file__))
+
+ def _path_to_package(self, path):
+ relative_path = path[len(self.base_path) + 1:]
+ if relative_path:
+ return '.'.join((self.package.__name__,) +
+ tuple(relative_path.split('/')))
+ else:
+ return self.package.__name__
+
+ def _modules_search(self):
+ """Recursive search for python modules in base package"""
+ modules = []
+ for root, dirs, files in os.walk(self.base_path):
+ if not os.path.exists(os.path.join(root, '__init__.py')):
+ continue
+ root_package = self._path_to_package(root)
+ for item in files:
+ if item.endswith('.py'):
+ module_name = '.'.join((root_package,
+ os.path.splitext(item)[0]))
+ if not module_name.startswith(UNIT_TESTS_EXCLUDE):
+ modules.append(module_name)
+ return modules
+
+ @staticmethod
+ def _get_idempotent_id(test_node):
+ """Return key-value dict with all metadata from @test.idempotent_id"""
+ idempotent_id = None
+ for decorator in test_node.decorator_list:
+ if (hasattr(decorator, 'func') and
+ hasattr(decorator.func, 'attr') and
+ decorator.func.attr == DECORATOR_NAME and
+ hasattr(decorator.func, 'value') and
+ decorator.func.value.id == DECORATOR_MODULE):
+ for arg in decorator.args:
+ idempotent_id = ast.literal_eval(arg)
+ return idempotent_id
+
+ @staticmethod
+ def _is_decorator(line):
+ return line.strip().startswith('@')
+
+ @staticmethod
+ def _is_def(line):
+ return line.strip().startswith('def ')
+
+ def _add_uuid_to_test(self, patcher, test_node, source_path):
+ with open(source_path) as src:
+ src_lines = src.read().split('\n')
+ lineno = test_node.lineno
+ insert_position = lineno
+ while True:
+ if (self._is_def(src_lines[lineno - 1]) or
+ (self._is_decorator(src_lines[lineno - 1]) and
+ (DECORATOR_TEMPLATE.split('(')[0] <=
+ src_lines[lineno - 1].strip().split('(')[0]))):
+ insert_position = lineno
+ break
+ lineno += 1
+ patcher.add_patch(
+ source_path,
+ ' ' * test_node.col_offset + DECORATOR_TEMPLATE % uuid.uuid4(),
+ insert_position
+ )
+
+ @staticmethod
+ def _is_test_case(module, node):
+ if (node.__class__ is ast.ClassDef and
+ hasattr(module, node.name) and
+ inspect.isclass(getattr(module, node.name))):
+ return issubclass(getattr(module, node.name), unittest.TestCase)
+
+ @staticmethod
+ def _is_test_method(node):
+ return (node.__class__ is ast.FunctionDef
+ and node.name.startswith('test_'))
+
+ @staticmethod
+ def _next_node(body, node):
+ if body.index(node) < len(body):
+ return body[body.index(node) + 1]
+
+ @staticmethod
+ def _import_name(node):
+ if type(node) == ast.Import:
+ return node.names[0].name
+ elif type(node) == ast.ImportFrom:
+ return '%s.%s' % (node.module, node.names[0].name)
+
+ def _add_import_for_test_uuid(self, patcher, src_parsed, source_path):
+ with open(source_path) as f:
+ src_lines = f.read().split('\n')
+ line_no = 0
+ tempest_imports = [node for node in src_parsed.body
+ if self._import_name(node) and
+ 'tempest.' in self._import_name(node)]
+ if not tempest_imports:
+ import_snippet = '\n'.join(('', IMPORT_LINE, ''))
+ else:
+ for node in tempest_imports:
+ if self._import_name(node) < DECORATOR_IMPORT:
+ continue
+ else:
+ line_no = node.lineno
+ import_snippet = IMPORT_LINE
+ break
+ else:
+ line_no = tempest_imports[-1].lineno
+ while True:
+ if (not src_lines[line_no - 1] or
+ getattr(self._next_node(src_parsed.body,
+ tempest_imports[-1]),
+ 'lineno') == line_no or
+ line_no == len(src_lines)):
+ break
+ line_no += 1
+ import_snippet = '\n'.join((IMPORT_LINE, ''))
+ patcher.add_patch(source_path, import_snippet, line_no)
+
+ def get_tests(self):
+ """Get test methods with sources from base package with metadata"""
+ tests = {}
+ for module_name in self._modules_search():
+ tests[module_name] = {}
+ module = importlib.import_module(module_name)
+ source_path = '.'.join(
+ (os.path.splitext(module.__file__)[0], 'py')
+ )
+ with open(source_path, 'r') as f:
+ source = f.read()
+ tests[module_name]['source_path'] = source_path
+ tests[module_name]['tests'] = {}
+ source_parsed = ast.parse(source)
+ tests[module_name]['ast'] = source_parsed
+ tests[module_name]['import_valid'] = (
+ hasattr(module, DECORATOR_MODULE) and
+ inspect.ismodule(getattr(module, DECORATOR_MODULE))
+ )
+ test_cases = (node for node in source_parsed.body
+ if self._is_test_case(module, node))
+ for node in test_cases:
+ for subnode in filter(self._is_test_method, node.body):
+ test_name = '%s.%s' % (node.name, subnode.name)
+ tests[module_name]['tests'][test_name] = subnode
+ return tests
+
+ @staticmethod
+ def _filter_tests(function, tests):
+ """Filter tests with condition 'function(test_node) == True'"""
+ result = {}
+ for module_name in tests:
+ for test_name in tests[module_name]['tests']:
+ if function(module_name, test_name, tests):
+ if module_name not in result:
+ result[module_name] = {
+ 'ast': tests[module_name]['ast'],
+ 'source_path': tests[module_name]['source_path'],
+ 'import_valid': tests[module_name]['import_valid'],
+ 'tests': {}
+ }
+ result[module_name]['tests'][test_name] = \
+ tests[module_name]['tests'][test_name]
+ return result
+
+ def find_untagged(self, tests):
+ """Filter all tests without uuid in metadata"""
+ def check_uuid_in_meta(module_name, test_name, tests):
+ idempotent_id = self._get_idempotent_id(
+ tests[module_name]['tests'][test_name])
+ return not idempotent_id
+ return self._filter_tests(check_uuid_in_meta, tests)
+
+ def report_collisions(self, tests):
+ """Reports collisions if there are any
+
+ Returns true if collisions exist.
+ """
+ uuids = {}
+
+ def report(module_name, test_name, tests):
+ test_uuid = self._get_idempotent_id(
+ tests[module_name]['tests'][test_name])
+ if not test_uuid:
+ return
+ if test_uuid in uuids:
+ error_str = "%s:%s\n uuid %s collision: %s<->%s\n%s:%s" % (
+ tests[module_name]['source_path'],
+ tests[module_name]['tests'][test_name].lineno,
+ test_uuid,
+ test_name,
+ uuids[test_uuid]['test_name'],
+ uuids[test_uuid]['source_path'],
+ uuids[test_uuid]['test_node'].lineno,
+ )
+ print(error_str)
+ print("cannot automatically resolve the collision, please "
+ "manually remove the duplicate value on the new test.")
+ return True
+ else:
+ uuids[test_uuid] = {
+ 'module': module_name,
+ 'test_name': test_name,
+ 'test_node': tests[module_name]['tests'][test_name],
+ 'source_path': tests[module_name]['source_path']
+ }
+ return bool(self._filter_tests(report, tests))
+
+ def report_untagged(self, tests):
+ """Reports untagged tests if there are any
+
+ Returns true if untagged tests exist.
+ """
+ def report(module_name, test_name, tests):
+ error_str = "%s:%s\nmissing @test.idempotent_id('...')\n%s\n" % (
+ tests[module_name]['source_path'],
+ tests[module_name]['tests'][test_name].lineno,
+ test_name
+ )
+ print(error_str)
+ return True
+ return bool(self._filter_tests(report, tests))
+
+ def fix_tests(self, tests):
+ """Add uuids to all specified in tests and fix it in source files"""
+ patcher = SourcePatcher()
+ for module_name in tests:
+ add_import_once = True
+ for test_name in tests[module_name]['tests']:
+ if not tests[module_name]['import_valid'] and add_import_once:
+ self._add_import_for_test_uuid(
+ patcher,
+ tests[module_name]['ast'],
+ tests[module_name]['source_path']
+ )
+ add_import_once = False
+ self._add_uuid_to_test(
+ patcher, tests[module_name]['tests'][test_name],
+ tests[module_name]['source_path'])
+ patcher.apply_patches()
+
+
+def run():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--package', action='store', dest='package',
+ default='tempest', type=str,
+ help='Package with tests')
+ parser.add_argument('--fix', action='store_true', dest='fix_tests',
+ help='Attempt to fix tests without UUIDs')
+ args = parser.parse_args()
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+ pkg = importlib.import_module(args.package)
+ checker = TestChecker(pkg)
+ errors = False
+ tests = checker.get_tests()
+ untagged = checker.find_untagged(tests)
+ errors = checker.report_collisions(tests) or errors
+ if args.fix_tests and untagged:
+ checker.fix_tests(untagged)
+ else:
+ errors = checker.report_untagged(untagged) or errors
+ if errors:
+ sys.exit("@test.idempotent_id existence and uniqueness checks failed\n"
+ "Run 'tox -v -euuidgen' to automatically fix tests with\n"
+ "missing @test.idempotent_id decorators.")
+
+if __name__ == '__main__':
+ run()
diff --git a/tempest/lib/cmd/skip_tracker.py b/tempest/lib/cmd/skip_tracker.py
new file mode 100755
index 0000000..b5c9b95
--- /dev/null
+++ b/tempest/lib/cmd/skip_tracker.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python2
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Track test skips via launchpadlib API and raise alerts if a bug
+is fixed but a skip is still in the Tempest test code
+"""
+
+import argparse
+import logging
+import os
+import re
+
+try:
+ from launchpadlib import launchpad
+except ImportError:
+ launchpad = None
+
+LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('test_path', help='Path of test dir')
+ return parser.parse_args()
+
+
+def info(msg, *args, **kwargs):
+ logging.info(msg, *args, **kwargs)
+
+
+def debug(msg, *args, **kwargs):
+ logging.debug(msg, *args, **kwargs)
+
+
+def find_skips(start):
+ """Find the entire list of skiped tests.
+
+ Returns a list of tuples (method, bug) that represent
+ test methods that have been decorated to skip because of
+ a particular bug.
+ """
+ results = {}
+ debug("Searching in %s", start)
+ for root, _dirs, files in os.walk(start):
+ for name in files:
+ if name.startswith('test_') and name.endswith('py'):
+ path = os.path.join(root, name)
+ debug("Searching in %s", path)
+ temp_result = find_skips_in_file(path)
+ for method_name, bug_no in temp_result:
+ if results.get(bug_no):
+ result_dict = results.get(bug_no)
+ if result_dict.get(name):
+ result_dict[name].append(method_name)
+ else:
+ result_dict[name] = [method_name]
+ results[bug_no] = result_dict
+ else:
+ results[bug_no] = {name: [method_name]}
+ return results
+
+
+def find_skips_in_file(path):
+ """Return the skip tuples in a test file."""
+ BUG_RE = re.compile(r'\s*@.*skip_because\(bug=[\'"](\d+)[\'"]')
+ DEF_RE = re.compile(r'\s*def (\w+)\(')
+ bug_found = False
+ results = []
+ lines = open(path, 'rb').readlines()
+ for x, line in enumerate(lines):
+ if not bug_found:
+ res = BUG_RE.match(line)
+ if res:
+ bug_no = int(res.group(1))
+ debug("Found bug skip %s on line %d", bug_no, x + 1)
+ bug_found = True
+ else:
+ res = DEF_RE.match(line)
+ if res:
+ method = res.group(1)
+ debug("Found test method %s skips for bug %d", method, bug_no)
+ results.append((method, bug_no))
+ bug_found = False
+ return results
+
+
+def get_results(result_dict):
+ results = []
+ for bug_no in result_dict.keys():
+ for method in result_dict[bug_no]:
+ results.append((method, bug_no))
+ return results
+
+
+def main():
+ logging.basicConfig(format='%(levelname)s: %(message)s',
+ level=logging.INFO)
+ parser = parse_args()
+ results = find_skips(parser.test_path)
+ unique_bugs = sorted(set([bug for (method, bug) in get_results(results)]))
+ unskips = []
+ duplicates = []
+ info("Total bug skips found: %d", len(results))
+ info("Total unique bugs causing skips: %d", len(unique_bugs))
+ if launchpad is not None:
+ lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
+ 'production',
+ LPCACHEDIR)
+ else:
+ print("To check the bug status launchpadlib should be installed")
+ exit(1)
+
+ for bug_no in unique_bugs:
+ bug = lp.bugs[bug_no]
+ duplicate = bug.duplicate_of_link
+ if duplicate is not None:
+ dup_id = duplicate.split('/')[-1]
+ duplicates.append((bug_no, dup_id))
+ for task in bug.bug_tasks:
+ info("Bug #%7s (%12s - %12s)", bug_no,
+ task.importance, task.status)
+ if task.status in ('Fix Released', 'Fix Committed'):
+ unskips.append(bug_no)
+
+ for bug_id, dup_id in duplicates:
+ if bug_id not in unskips:
+ dup_bug = lp.bugs[dup_id]
+ for task in dup_bug.bug_tasks:
+ info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)",
+ bug_id, dup_id, task.importance, task.status)
+ if task.status in ('Fix Released', 'Fix Committed'):
+ unskips.append(bug_id)
+
+ unskips = sorted(set(unskips))
+ if unskips:
+ print("The following bugs have been fixed and the corresponding skips")
+ print("should be removed from the test cases:")
+ print()
+ for bug in unskips:
+ message = " %7s in " % bug
+ locations = ["%s" % x for x in results[bug].keys()]
+ message += " and ".join(locations)
+ print(message)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tempest/lib/common/__init__.py b/tempest/lib/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/common/__init__.py
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
new file mode 100644
index 0000000..b3793bc
--- /dev/null
+++ b/tempest/lib/common/http.py
@@ -0,0 +1,25 @@
+# Copyright 2013 OpenStack Foundation
+# Copyright 2013 Citrix Systems, 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.
+
+import httplib2
+
+
+class ClosingHttp(httplib2.Http):
+ def request(self, *args, **kwargs):
+ original_headers = kwargs.get('headers', {})
+ new_headers = dict(original_headers, connection='close')
+ new_kwargs = dict(kwargs, headers=new_headers)
+ return super(ClosingHttp, self).request(*args, **new_kwargs)
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
new file mode 100644
index 0000000..7ce05e3
--- /dev/null
+++ b/tempest/lib/common/rest_client.py
@@ -0,0 +1,894 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import collections
+import logging as real_logging
+import re
+import time
+
+import jsonschema
+from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
+import six
+
+from tempest.lib.common import http
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions
+
+# redrive rate limited calls at most twice
+MAX_RECURSION_DEPTH = 2
+
+# All the successful HTTP status codes from RFC 7231 & 4918
+HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207)
+
+# All the redirection HTTP status codes from RFC 7231 & 4918
+HTTP_REDIRECTION = (300, 301, 302, 303, 304, 305, 306, 307)
+
+# JSON Schema validator and format checker used for JSON Schema validation
+JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
+FORMAT_CHECKER = jsonschema.draft4_format_checker
+
+
+class RestClient(object):
+ """Unified OpenStack RestClient class
+
+ This class is used for building openstack api clients on top of. It is
+ intended to provide a base layer for wrapping outgoing http requests in
+ keystone auth as well as providing response code checking and error
+ handling.
+
+ :param auth_provider: an auth provider object used to wrap requests in auth
+ :param str service: The service name to use for the catalog lookup
+ :param str region: The region to use for the catalog lookup
+ :param str endpoint_type: The endpoint type to use for the catalog lookup
+ :param int build_interval: Time in seconds between to status checks in
+ wait loops
+ :param int build_timeout: Timeout in seconds to wait for a wait operation.
+ :param bool disable_ssl_certificate_validation: Set to true to disable ssl
+ certificate validation
+ :param str ca_certs: File containing the CA Bundle to use in verifying a
+ TLS server cert
+ :param str trace_request: Regex to use for specifying logging the entirety
+ of the request and response payload
+ """
+ TYPE = "json"
+
+ # The version of the API this client implements
+ api_version = None
+
+ LOG = logging.getLogger(__name__)
+
+ def __init__(self, auth_provider, service, region,
+ endpoint_type='publicURL',
+ build_interval=1, build_timeout=60,
+ disable_ssl_certificate_validation=False, ca_certs=None,
+ trace_requests=''):
+ self.auth_provider = auth_provider
+ self.service = service
+ self.region = region
+ self.endpoint_type = endpoint_type
+ self.build_interval = build_interval
+ self.build_timeout = build_timeout
+ self.trace_requests = trace_requests
+
+ self._skip_path = False
+ self.general_header_lc = set(('cache-control', 'connection',
+ 'date', 'pragma', 'trailer',
+ 'transfer-encoding', 'via',
+ 'warning'))
+ self.response_header_lc = set(('accept-ranges', 'age', 'etag',
+ 'location', 'proxy-authenticate',
+ 'retry-after', 'server',
+ 'vary', 'www-authenticate'))
+ dscv = disable_ssl_certificate_validation
+ self.http_obj = http.ClosingHttp(
+ disable_ssl_certificate_validation=dscv, ca_certs=ca_certs)
+
+ def _get_type(self):
+ return self.TYPE
+
+ def get_headers(self, accept_type=None, send_type=None):
+ """Return the default headers which will be used with outgoing requests
+
+ :param str accept_type: The media type to use for the Accept header, if
+ one isn't provided the object var TYPE will be
+ used
+ :param str send_type: The media-type to use for the Content-Type
+ header, if one isn't provided the object var
+ TYPE will be used
+ :rtype: dict
+ :return: The dictionary of headers which can be used in the headers
+ dict for outgoing request
+ """
+ if accept_type is None:
+ accept_type = self._get_type()
+ if send_type is None:
+ send_type = self._get_type()
+ return {'Content-Type': 'application/%s' % send_type,
+ 'Accept': 'application/%s' % accept_type}
+
+ def __str__(self):
+ STRING_LIMIT = 80
+ str_format = ("service:%s, base_url:%s, "
+ "filters: %s, build_interval:%s, build_timeout:%s"
+ "\ntoken:%s..., \nheaders:%s...")
+ return str_format % (self.service, self.base_url,
+ self.filters, self.build_interval,
+ self.build_timeout,
+ str(self.token)[0:STRING_LIMIT],
+ str(self.get_headers())[0:STRING_LIMIT])
+
+ @property
+ def user(self):
+ """The username used for requests
+
+ :rtype: string
+ :return: The username being used for requests
+ """
+
+ return self.auth_provider.credentials.username
+
+ @property
+ def user_id(self):
+ """The user_id used for requests
+
+ :rtype: string
+ :return: The user id being used for requests
+ """
+ return self.auth_provider.credentials.user_id
+
+ @property
+ def tenant_name(self):
+ """The tenant/project being used for requests
+
+ :rtype: string
+ :return: The tenant/project name being used for requests
+ """
+ return self.auth_provider.credentials.tenant_name
+
+ @property
+ def tenant_id(self):
+ """The tenant/project id being used for requests
+
+ :rtype: string
+ :return: The tenant/project id being used for requests
+ """
+ return self.auth_provider.credentials.tenant_id
+
+ @property
+ def password(self):
+ """The password being used for requests
+
+ :rtype: string
+ :return: The password being used for requests
+ """
+ return self.auth_provider.credentials.password
+
+ @property
+ def base_url(self):
+ return self.auth_provider.base_url(filters=self.filters)
+
+ @property
+ def token(self):
+ return self.auth_provider.get_token()
+
+ @property
+ def filters(self):
+ _filters = dict(
+ service=self.service,
+ endpoint_type=self.endpoint_type,
+ region=self.region
+ )
+ if self.api_version is not None:
+ _filters['api_version'] = self.api_version
+ if self._skip_path:
+ _filters['skip_path'] = self._skip_path
+ return _filters
+
+ def skip_path(self):
+ """When set, ignore the path part of the base URL from the catalog"""
+ self._skip_path = True
+
+ def reset_path(self):
+ """When reset, use the base URL from the catalog as-is"""
+ self._skip_path = False
+
+ @classmethod
+ def expected_success(cls, expected_code, read_code):
+ """Check expected success response code against the http response
+
+ :param int expected_code: The response code that is expected.
+ Optionally a list of integers can be used
+ to specify multiple valid success codes
+ :param int read_code: The response code which was returned in the
+ response
+ :raises AssertionError: if the expected_code isn't a valid http success
+ response code
+ :raises exceptions.InvalidHttpSuccessCode: if the read code isn't an
+ expected http success code
+ """
+ assert_msg = ("This function only allowed to use for HTTP status"
+ "codes which explicitly defined in the RFC 7231 & 4918."
+ "{0} is not a defined Success Code!"
+ ).format(expected_code)
+ if isinstance(expected_code, list):
+ for code in expected_code:
+ assert code in HTTP_SUCCESS + HTTP_REDIRECTION, assert_msg
+ else:
+ assert expected_code in HTTP_SUCCESS + HTTP_REDIRECTION, assert_msg
+
+ # NOTE(afazekas): the http status code above 400 is processed by
+ # the _error_checker method
+ if read_code < 400:
+ pattern = """Unexpected http success status code {0},
+ The expected status code is {1}"""
+ if ((not isinstance(expected_code, list) and
+ (read_code != expected_code)) or
+ (isinstance(expected_code, list) and
+ (read_code not in expected_code))):
+ details = pattern.format(read_code, expected_code)
+ raise exceptions.InvalidHttpSuccessCode(details)
+
+ def post(self, url, body, headers=None, extra_headers=False):
+ """Send a HTTP POST request using keystone auth
+
+ :param str url: the relative url to send the post request to
+ :param dict body: the request body
+ :param dict headers: The headers to use for the request
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('POST', url, extra_headers, headers, body)
+
+ def get(self, url, headers=None, extra_headers=False):
+ """Send a HTTP GET request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('GET', url, extra_headers, headers)
+
+ def delete(self, url, headers=None, body=None, extra_headers=False):
+ """Send a HTTP DELETE request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param dict body: the request body
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('DELETE', url, extra_headers, headers, body)
+
+ def patch(self, url, body, headers=None, extra_headers=False):
+ """Send a HTTP PATCH request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict body: the request body
+ :param dict headers: The headers to use for the request
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('PATCH', url, extra_headers, headers, body)
+
+ def put(self, url, body, headers=None, extra_headers=False):
+ """Send a HTTP PUT request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict body: the request body
+ :param dict headers: The headers to use for the request
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('PUT', url, extra_headers, headers, body)
+
+ def head(self, url, headers=None, extra_headers=False):
+ """Send a HTTP HEAD request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('HEAD', url, extra_headers, headers)
+
+ def copy(self, url, headers=None, extra_headers=False):
+ """Send a HTTP COPY request using keystone service catalog and auth
+
+ :param str url: the relative url to send the post request to
+ :param dict headers: The headers to use for the request
+ :param dict extra_headers: If the headers returned by the get_headers()
+ method are to be used but additional headers
+ are needed in the request pass them in as a
+ dict
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :rtype: tuple
+ """
+ return self.request('COPY', url, extra_headers, headers)
+
+ def get_versions(self):
+ """Get the versions on a endpoint from the keystone catalog
+
+ This method will make a GET request on the baseurl from the keystone
+ catalog to return a list of API versions. It is expected that a GET
+ on the endpoint in the catalog will return a list of supported API
+ versions.
+
+ :return tuple with response headers and list of version numbers
+ :rtype: tuple
+ """
+ resp, body = self.get('')
+ body = self._parse_resp(body)
+ versions = map(lambda x: x['id'], body)
+ return resp, versions
+
+ def _get_request_id(self, resp):
+ for i in ('x-openstack-request-id', 'x-compute-request-id'):
+ if i in resp:
+ return resp[i]
+ return ""
+
+ def _safe_body(self, body, maxlen=4096):
+ # convert a structure into a string safely
+ try:
+ text = six.text_type(body)
+ except UnicodeDecodeError:
+ # if this isn't actually text, return marker that
+ return "<BinaryData: removed>"
+ if len(text) > maxlen:
+ return text[:maxlen]
+ else:
+ return text
+
+ def _log_request_start(self, method, req_url, req_headers=None,
+ req_body=None):
+ if req_headers is None:
+ req_headers = {}
+ caller_name = misc_utils.find_test_caller()
+ if self.trace_requests and re.search(self.trace_requests, caller_name):
+ self.LOG.debug('Starting Request (%s): %s %s' %
+ (caller_name, method, req_url))
+
+ def _log_request_full(self, method, req_url, resp,
+ secs="", req_headers=None,
+ req_body=None, resp_body=None,
+ caller_name=None, extra=None):
+ if 'X-Auth-Token' in req_headers:
+ req_headers['X-Auth-Token'] = '<omitted>'
+ log_fmt = """Request - Headers: %s
+ Body: %s
+ Response - Headers: %s
+ Body: %s"""
+
+ self.LOG.debug(
+ log_fmt % (
+ str(req_headers),
+ self._safe_body(req_body),
+ str(resp),
+ self._safe_body(resp_body)),
+ extra=extra)
+
+ def _log_request(self, method, req_url, resp,
+ secs="", req_headers=None,
+ req_body=None, resp_body=None):
+ if req_headers is None:
+ req_headers = {}
+ # if we have the request id, put it in the right part of the log
+ extra = dict(request_id=self._get_request_id(resp))
+ # NOTE(sdague): while we still have 6 callers to this function
+ # we're going to just provide work around on who is actually
+ # providing timings by gracefully adding no content if they don't.
+ # Once we're down to 1 caller, clean this up.
+ caller_name = misc_utils.find_test_caller()
+ if secs:
+ secs = " %.3fs" % secs
+ self.LOG.info(
+ 'Request (%s): %s %s %s%s' % (
+ caller_name,
+ resp['status'],
+ method,
+ req_url,
+ secs),
+ extra=extra)
+
+ # Also look everything at DEBUG if you want to filter this
+ # out, don't run at debug.
+ if self.LOG.isEnabledFor(real_logging.DEBUG):
+ self._log_request_full(method, req_url, resp, secs, req_headers,
+ req_body, resp_body, caller_name, extra)
+
+ def _parse_resp(self, body):
+ try:
+ body = json.loads(body)
+ except ValueError:
+ return body
+
+ # We assume, that if the first value of the deserialized body's
+ # item set is a dict or a list, that we just return the first value
+ # of deserialized body.
+ # Essentially "cutting out" the first placeholder element in a body
+ # that looks like this:
+ #
+ # {
+ # "users": [
+ # ...
+ # ]
+ # }
+ try:
+ # Ensure there are not more than one top-level keys
+ # NOTE(freerunner): Ensure, that JSON is not nullable to
+ # to prevent StopIteration Exception
+ if len(body.keys()) != 1:
+ return body
+ # Just return the "wrapped" element
+ first_key, first_item = six.next(six.iteritems(body))
+ if isinstance(first_item, (dict, list)):
+ return first_item
+ except (ValueError, IndexError):
+ pass
+ return body
+
+ def response_checker(self, method, resp, resp_body):
+ """A sanity check on the response from a HTTP request
+
+ This method does a sanity check on whether the response from an HTTP
+ request conforms the HTTP RFC.
+
+ :param str method: The HTTP verb of the request associated with the
+ response being passed in.
+ :param resp: The response headers
+ :param resp_body: The body of the response
+ :raises ResponseWithNonEmptyBody: If the response with the status code
+ is not supposed to have a body
+ :raises ResponseWithEntity: If the response code is 205 but has an
+ entity
+ """
+ if (resp.status in set((204, 205, 304)) or resp.status < 200 or
+ method.upper() == 'HEAD') and resp_body:
+ raise exceptions.ResponseWithNonEmptyBody(status=resp.status)
+ # NOTE(afazekas):
+ # If the HTTP Status Code is 205
+ # 'The response MUST NOT include an entity.'
+ # A HTTP entity has an entity-body and an 'entity-header'.
+ # In the HTTP response specification (Section 6) the 'entity-header'
+ # 'generic-header' and 'response-header' are in OR relation.
+ # All headers not in the above two group are considered as entity
+ # header in every interpretation.
+
+ if (resp.status == 205 and
+ 0 != len(set(resp.keys()) - set(('status',)) -
+ self.response_header_lc - self.general_header_lc)):
+ raise exceptions.ResponseWithEntity()
+ # NOTE(afazekas)
+ # Now the swift sometimes (delete not empty container)
+ # returns with non json error response, we can create new rest class
+ # for swift.
+ # Usually RFC2616 says error responses SHOULD contain an explanation.
+ # The warning is normal for SHOULD/SHOULD NOT case
+
+ # Likely it will cause an error
+ if method != 'HEAD' and not resp_body and resp.status >= 400:
+ self.LOG.warning("status >= 400 response with empty body")
+
+ def _request(self, method, url, headers=None, body=None):
+ """A simple HTTP request interface."""
+ # Authenticate the request with the auth provider
+ req_url, req_headers, req_body = self.auth_provider.auth_request(
+ method, url, headers, body, self.filters)
+
+ # Do the actual request, and time it
+ start = time.time()
+ self._log_request_start(method, req_url)
+ resp, resp_body = self.raw_request(
+ req_url, method, headers=req_headers, body=req_body)
+ end = time.time()
+ self._log_request(method, req_url, resp, secs=(end - start),
+ req_headers=req_headers, req_body=req_body,
+ resp_body=resp_body)
+
+ # Verify HTTP response codes
+ self.response_checker(method, resp, resp_body)
+
+ return resp, resp_body
+
+ def raw_request(self, url, method, headers=None, body=None):
+ """Send a raw HTTP request without the keystone catalog or auth
+
+ This method sends a HTTP request in the same manner as the request()
+ method, however it does so without using keystone auth or the catalog
+ to determine the base url. Additionally no response handling is done
+ the results from the request are just returned.
+
+ :param str url: Full url to send the request
+ :param str method: The HTTP verb to use for the request
+ :param str headers: Headers to use for the request if none are specifed
+ the headers
+ :param str body: Body to to send with the request
+ :rtype: tuple
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ """
+ if headers is None:
+ headers = self.get_headers()
+ return self.http_obj.request(url, method,
+ headers=headers, body=body)
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None):
+ """Send a HTTP request with keystone auth and using the catalog
+
+ This method will send an HTTP request using keystone auth in the
+ headers and the catalog to determine the endpoint to use for the
+ baseurl to send the request to. Additionally
+
+ When a response is received it will check it to see if an error
+ response was received. If it was an exception will be raised to enable
+ it to be handled quickly.
+
+ This method will also handle rate-limiting, if a 413 response code is
+ received it will retry the request after waiting the 'retry-after'
+ duration from the header.
+
+ :param str method: The HTTP verb to use for the request
+ :param str url: Relative url to send the request to
+ :param dict extra_headers: If specified without the headers kwarg the
+ headers sent with the request will be the
+ combination from the get_headers() method
+ and this kwarg
+ :param dict headers: Headers to use for the request if none are
+ specifed the headers returned from the
+ get_headers() method are used. If the request
+ explicitly requires no headers use an empty dict.
+ :param str body: Body to to send with the request
+ :rtype: tuple
+ :return: a tuple with the first entry containing the response headers
+ and the second the response body
+ :raises UnexpectedContentType: If the content-type of the response
+ isn't an expect type
+ :raises Unauthorized: If a 401 response code is received
+ :raises Forbidden: If a 403 response code is received
+ :raises NotFound: If a 404 response code is received
+ :raises BadRequest: If a 400 response code is received
+ :raises Gone: If a 410 response code is received
+ :raises Conflict: If a 409 response code is received
+ :raises OverLimit: If a 413 response code is received and over_limit is
+ not in the response body
+ :raises RateLimitExceeded: If a 413 response code is received and
+ over_limit is in the response body
+ :raises InvalidContentType: If a 415 response code is received
+ :raises UnprocessableEntity: If a 422 response code is received
+ :raises InvalidHTTPResponseBody: The response body wasn't valid JSON
+ and couldn't be parsed
+ :raises NotImplemented: If a 501 response code is received
+ :raises ServerFault: If a 500 response code is received
+ :raises UnexpectedResponseCode: If a response code above 400 is
+ received and it doesn't fall into any
+ of the handled checks
+ """
+ # if extra_headers is True
+ # default headers would be added to headers
+ retry = 0
+
+ if headers is None:
+ # NOTE(vponomaryov): if some client do not need headers,
+ # it should explicitly pass empty dict
+ headers = self.get_headers()
+ elif extra_headers:
+ try:
+ headers = headers.copy()
+ headers.update(self.get_headers())
+ except (ValueError, TypeError):
+ headers = self.get_headers()
+
+ resp, resp_body = self._request(method, url,
+ headers=headers, body=body)
+
+ while (resp.status == 413 and
+ 'retry-after' in resp and
+ not self.is_absolute_limit(
+ resp, self._parse_resp(resp_body)) and
+ retry < MAX_RECURSION_DEPTH):
+ retry += 1
+ delay = int(resp['retry-after'])
+ time.sleep(delay)
+ resp, resp_body = self._request(method, url,
+ headers=headers, body=body)
+ self._error_checker(method, url, headers, body,
+ resp, resp_body)
+ return resp, resp_body
+
+ def _error_checker(self, method, url,
+ headers, body, resp, resp_body):
+
+ # NOTE(mtreinish): Check for httplib response from glance_http. The
+ # object can't be used here because importing httplib breaks httplib2.
+ # If another object from a class not imported were passed here as
+ # resp this could possibly fail
+ if str(type(resp)) == "<type 'instance'>":
+ ctype = resp.getheader('content-type')
+ else:
+ try:
+ ctype = resp['content-type']
+ # NOTE(mtreinish): Keystone delete user responses doesn't have a
+ # content-type header. (They don't have a body) So just pretend it
+ # is set.
+ except KeyError:
+ ctype = 'application/json'
+
+ # It is not an error response
+ if resp.status < 400:
+ return
+
+ JSON_ENC = ['application/json', 'application/json; charset=utf-8']
+ # NOTE(mtreinish): This is for compatibility with Glance and swift
+ # APIs. These are the return content types that Glance api v1
+ # (and occasionally swift) are using.
+ TXT_ENC = ['text/plain', 'text/html', 'text/html; charset=utf-8',
+ 'text/plain; charset=utf-8']
+
+ if ctype.lower() in JSON_ENC:
+ parse_resp = True
+ elif ctype.lower() in TXT_ENC:
+ parse_resp = False
+ else:
+ raise exceptions.UnexpectedContentType(str(resp.status),
+ resp=resp)
+
+ if resp.status == 401:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Unauthorized(resp_body, resp=resp)
+
+ if resp.status == 403:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Forbidden(resp_body, resp=resp)
+
+ if resp.status == 404:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.NotFound(resp_body, resp=resp)
+
+ if resp.status == 400:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.BadRequest(resp_body, resp=resp)
+
+ if resp.status == 410:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Gone(resp_body, resp=resp)
+
+ if resp.status == 409:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.Conflict(resp_body, resp=resp)
+
+ if resp.status == 413:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ if self.is_absolute_limit(resp, resp_body):
+ raise exceptions.OverLimit(resp_body, resp=resp)
+ else:
+ raise exceptions.RateLimitExceeded(resp_body, resp=resp)
+
+ if resp.status == 415:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.InvalidContentType(resp_body, resp=resp)
+
+ if resp.status == 422:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.UnprocessableEntity(resp_body, resp=resp)
+
+ if resp.status in (500, 501):
+ message = resp_body
+ if parse_resp:
+ try:
+ resp_body = self._parse_resp(resp_body)
+ except ValueError:
+ # If response body is a non-json string message.
+ # Use resp_body as is and raise InvalidResponseBody
+ # exception.
+ raise exceptions.InvalidHTTPResponseBody(message)
+ else:
+ if isinstance(resp_body, dict):
+ # I'm seeing both computeFault
+ # and cloudServersFault come back.
+ # Will file a bug to fix, but leave as is for now.
+ if 'cloudServersFault' in resp_body:
+ message = resp_body['cloudServersFault']['message']
+ elif 'computeFault' in resp_body:
+ message = resp_body['computeFault']['message']
+ elif 'error' in resp_body:
+ message = resp_body['error']['message']
+ elif 'message' in resp_body:
+ message = resp_body['message']
+ else:
+ message = resp_body
+
+ if resp.status == 501:
+ raise exceptions.NotImplemented(resp_body, resp=resp,
+ message=message)
+ else:
+ raise exceptions.ServerFault(resp_body, resp=resp,
+ message=message)
+
+ if resp.status >= 400:
+ raise exceptions.UnexpectedResponseCode(str(resp.status),
+ resp=resp)
+
+ def is_absolute_limit(self, resp, resp_body):
+ if (not isinstance(resp_body, collections.Mapping) or
+ 'retry-after' not in resp):
+ return True
+ over_limit = resp_body.get('overLimit', None)
+ if not over_limit:
+ return True
+ return 'exceed' in over_limit.get('message', 'blabla')
+
+ def wait_for_resource_deletion(self, id):
+ """Waits for a resource to be deleted
+
+ This method will loop over is_resource_deleted until either
+ is_resource_deleted returns True or the build timeout is reached. This
+ depends on is_resource_deleted being implemented
+
+ :param str id: The id of the resource to check
+ :raises TimeoutException: If the build_timeout has elapsed and the
+ resource still hasn't been deleted
+ """
+ start_time = int(time.time())
+ while True:
+ if self.is_resource_deleted(id):
+ return
+ if int(time.time()) - start_time >= self.build_timeout:
+ message = ('Failed to delete %(resource_type)s %(id)s within '
+ 'the required time (%(timeout)s s).' %
+ {'resource_type': self.resource_type, 'id': id,
+ 'timeout': self.build_timeout})
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
+ time.sleep(self.build_interval)
+
+ def is_resource_deleted(self, id):
+ """Subclasses override with specific deletion detection."""
+ message = ('"%s" does not implement is_resource_deleted'
+ % self.__class__.__name__)
+ raise NotImplementedError(message)
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'resource'
+
+ @classmethod
+ def validate_response(cls, schema, resp, body):
+ # Only check the response if the status code is a success code
+ # TODO(cyeoh): Eventually we should be able to verify that a failure
+ # code if it exists is something that we expect. This is explicitly
+ # declared in the V3 API and so we should be able to export this in
+ # the response schema. For now we'll ignore it.
+ if resp.status in HTTP_SUCCESS + HTTP_REDIRECTION:
+ cls.expected_success(schema['status_code'], resp.status)
+
+ # Check the body of a response
+ body_schema = schema.get('response_body')
+ if body_schema:
+ try:
+ jsonschema.validate(body, body_schema,
+ cls=JSONSCHEMA_VALIDATOR,
+ format_checker=FORMAT_CHECKER)
+ except jsonschema.ValidationError as ex:
+ msg = ("HTTP response body is invalid (%s)") % ex
+ raise exceptions.InvalidHTTPResponseBody(msg)
+ else:
+ if body:
+ msg = ("HTTP response body should not exist (%s)") % body
+ raise exceptions.InvalidHTTPResponseBody(msg)
+
+ # Check the header of a response
+ header_schema = schema.get('response_header')
+ if header_schema:
+ try:
+ jsonschema.validate(resp, header_schema,
+ cls=JSONSCHEMA_VALIDATOR,
+ format_checker=FORMAT_CHECKER)
+ except jsonschema.ValidationError as ex:
+ msg = ("HTTP response header is invalid (%s)") % ex
+ raise exceptions.InvalidHTTPResponseHeader(msg)
+
+
+class ResponseBody(dict):
+ """Class that wraps an http response and dict body into a single value.
+
+ Callers that receive this object will normally use it as a dict but
+ can extract the response if needed.
+ """
+
+ def __init__(self, response, body=None):
+ body_data = body or {}
+ self.update(body_data)
+ self.response = response
+
+ def __str__(self):
+ body = super(ResponseBody, self).__str__()
+ return "response: %s\nBody: %s" % (self.response, body)
+
+
+class ResponseBodyData(object):
+ """Class that wraps an http response and string data into a single value.
+
+ """
+
+ def __init__(self, response, data):
+ self.response = response
+ self.data = data
+
+ def __str__(self):
+ return "response: %s\nBody: %s" % (self.response, self.data)
+
+
+class ResponseBodyList(list):
+ """Class that wraps an http response and list body into a single value.
+
+ Callers that receive this object will normally use it as a list but
+ can extract the response if needed.
+ """
+
+ def __init__(self, response, body=None):
+ body_data = body or []
+ self.extend(body_data)
+ self.response = response
+
+ def __str__(self):
+ body = super(ResponseBodyList, self).__str__()
+ return "response: %s\nBody: %s" % (self.response, body)
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
new file mode 100644
index 0000000..511dd08
--- /dev/null
+++ b/tempest/lib/common/ssh.py
@@ -0,0 +1,174 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import select
+import socket
+import time
+import warnings
+
+from oslo_log import log as logging
+import six
+
+from tempest.lib import exceptions
+
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ import paramiko
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Client(object):
+
+ def __init__(self, host, username, password=None, timeout=300, pkey=None,
+ channel_timeout=10, look_for_keys=False, key_filename=None):
+ self.host = host
+ self.username = username
+ self.password = password
+ if isinstance(pkey, six.string_types):
+ pkey = paramiko.RSAKey.from_private_key(
+ six.StringIO(str(pkey)))
+ self.pkey = pkey
+ self.look_for_keys = look_for_keys
+ self.key_filename = key_filename
+ self.timeout = int(timeout)
+ self.channel_timeout = float(channel_timeout)
+ self.buf_size = 1024
+
+ def _get_ssh_connection(self, sleep=1.5, backoff=1):
+ """Returns an ssh connection to the specified host."""
+ bsleep = sleep
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(
+ paramiko.AutoAddPolicy())
+ _start_time = time.time()
+ if self.pkey is not None:
+ LOG.info("Creating ssh connection to '%s' as '%s'"
+ " with public key authentication",
+ self.host, self.username)
+ else:
+ LOG.info("Creating ssh connection to '%s' as '%s'"
+ " with password %s",
+ self.host, self.username, str(self.password))
+ attempts = 0
+ while True:
+ try:
+ ssh.connect(self.host, username=self.username,
+ password=self.password,
+ look_for_keys=self.look_for_keys,
+ key_filename=self.key_filename,
+ timeout=self.channel_timeout, pkey=self.pkey)
+ LOG.info("ssh connection to %s@%s successfully created",
+ self.username, self.host)
+ return ssh
+ except (EOFError,
+ socket.error,
+ paramiko.SSHException) as e:
+ if self._is_timed_out(_start_time):
+ LOG.exception("Failed to establish authenticated ssh"
+ " connection to %s@%s after %d attempts",
+ self.username, self.host, attempts)
+ raise exceptions.SSHTimeout(host=self.host,
+ user=self.username,
+ password=self.password)
+ bsleep += backoff
+ attempts += 1
+ LOG.warning("Failed to establish authenticated ssh"
+ " connection to %s@%s (%s). Number attempts: %s."
+ " Retry after %d seconds.",
+ self.username, self.host, e, attempts, bsleep)
+ time.sleep(bsleep)
+
+ def _is_timed_out(self, start_time):
+ return (time.time() - self.timeout) > start_time
+
+ @staticmethod
+ def _can_system_poll():
+ return hasattr(select, 'poll')
+
+ def exec_command(self, cmd, encoding="utf-8"):
+ """Execute the specified command on the server
+
+ Note that this method is reading whole command outputs to memory, thus
+ shouldn't be used for large outputs.
+
+ :param str cmd: Command to run at remote server.
+ :param str encoding: Encoding for result from paramiko.
+ Result will not be decoded if None.
+ :returns: data read from standard output of the command.
+ :raises: SSHExecCommandFailed if command returns nonzero
+ status. The exception contains command status stderr content.
+ :raises: TimeoutException if cmd doesn't end when timeout expires.
+ """
+ ssh = self._get_ssh_connection()
+ transport = ssh.get_transport()
+ channel = transport.open_session()
+ channel.fileno() # Register event pipe
+ channel.exec_command(cmd)
+ channel.shutdown_write()
+ exit_status = channel.recv_exit_status()
+
+ # If the executing host is linux-based, poll the channel
+ if self._can_system_poll():
+ out_data_chunks = []
+ err_data_chunks = []
+ poll = select.poll()
+ poll.register(channel, select.POLLIN)
+ start_time = time.time()
+
+ while True:
+ ready = poll.poll(self.channel_timeout)
+ if not any(ready):
+ if not self._is_timed_out(start_time):
+ continue
+ raise exceptions.TimeoutException(
+ "Command: '{0}' executed on host '{1}'.".format(
+ cmd, self.host))
+ if not ready[0]: # If there is nothing to read.
+ continue
+ out_chunk = err_chunk = None
+ if channel.recv_ready():
+ out_chunk = channel.recv(self.buf_size)
+ out_data_chunks += out_chunk,
+ if channel.recv_stderr_ready():
+ err_chunk = channel.recv_stderr(self.buf_size)
+ err_data_chunks += err_chunk,
+ if channel.closed and not err_chunk and not out_chunk:
+ break
+ out_data = b''.join(out_data_chunks)
+ err_data = b''.join(err_data_chunks)
+ # Just read from the channels
+ else:
+ out_file = channel.makefile('rb', self.buf_size)
+ err_file = channel.makefile_stderr('rb', self.buf_size)
+ out_data = out_file.read()
+ err_data = err_file.read()
+ if encoding:
+ out_data = out_data.decode(encoding)
+ err_data = err_data.decode(encoding)
+
+ if 0 != exit_status:
+ raise exceptions.SSHExecCommandFailed(
+ command=cmd, exit_status=exit_status,
+ stderr=err_data, stdout=out_data)
+ return out_data
+
+ def test_connection_auth(self):
+ """Raises an exception when we can not connect to server via ssh."""
+ connection = self._get_ssh_connection()
+ connection.close()
diff --git a/tempest/lib/common/utils/__init__.py b/tempest/lib/common/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/common/utils/__init__.py
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
new file mode 100644
index 0000000..01b6477
--- /dev/null
+++ b/tempest/lib/common/utils/data_utils.py
@@ -0,0 +1,186 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import itertools
+import netaddr
+import random
+import string
+import uuid
+
+
+def rand_uuid():
+ """Generate a random UUID string
+
+ :return: a random UUID (e.g. '1dc12c7d-60eb-4b61-a7a2-17cf210155b6')
+ :rtype: string
+ """
+ return str(uuid.uuid4())
+
+
+def rand_uuid_hex():
+ """Generate a random UUID hex string
+
+ :return: a random UUID (e.g. '0b98cf96d90447bda4b46f31aeb1508c')
+ :rtype: string
+ """
+ return uuid.uuid4().hex
+
+
+def rand_name(name='', prefix=None):
+ """Generate a random name that inclues a random number
+
+ :param str name: The name that you want to include
+ :param str prefix: The prefix that you want to include
+ :return: a random name. The format is
+ '<prefix>-<random number>-<name>-<random number>'.
+ (e.g. 'prefixfoo-1308607012-namebar-154876201')
+ :rtype: string
+ """
+ randbits = str(random.randint(1, 0x7fffffff))
+ rand_name = randbits
+ if name:
+ rand_name = name + '-' + rand_name
+ if prefix:
+ rand_name = prefix + '-' + rand_name
+ return rand_name
+
+
+def rand_password(length=15):
+ """Generate a random password
+
+ :param int length: The length of password that you expect to set
+ (If it's smaller than 3, it's same as 3.)
+ :return: a random password. The format is
+ '<random upper letter>-<random number>-<random special character>
+ -<random ascii letters or digit characters or special symbols>'
+ (e.g. 'G2*ac8&lKFFgh%2')
+ :rtype: string
+ """
+ upper = random.choice(string.ascii_uppercase)
+ ascii_char = string.ascii_letters
+ digits = string.digits
+ digit = random.choice(string.digits)
+ puncs = '~!@#$%^&*_=+'
+ punc = random.choice(puncs)
+ seed = ascii_char + digits + puncs
+ pre = upper + digit + punc
+ password = pre + ''.join(random.choice(seed) for x in range(length - 3))
+ return password
+
+
+def rand_url():
+ """Generate a random url that inclues a random number
+
+ :return: a random url. The format is 'https://url-<random number>.com'.
+ (e.g. 'https://url-154876201.com')
+ :rtype: string
+ """
+ randbits = str(random.randint(1, 0x7fffffff))
+ return 'https://url-' + randbits + '.com'
+
+
+def rand_int_id(start=0, end=0x7fffffff):
+ """Generate a random integer value
+
+ :param int start: The value that you expect to start here
+ :param int end: The value that you expect to end here
+ :return: a random integer value
+ :rtype: int
+ """
+ return random.randint(start, end)
+
+
+def rand_mac_address():
+ """Generate an Ethernet MAC address
+
+ :return: an random Ethernet MAC address
+ :rtype: string
+ """
+ # NOTE(vish): We would prefer to use 0xfe here to ensure that linux
+ # bridge mac addresses don't change, but it appears to
+ # conflict with libvirt, so we use the next highest octet
+ # that has the unicast and locally administered bits set
+ # properly: 0xfa.
+ # Discussion: https://bugs.launchpad.net/nova/+bug/921838
+ mac = [0xfa, 0x16, 0x3e,
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff)]
+ return ':'.join(["%02x" % x for x in mac])
+
+
+def parse_image_id(image_ref):
+ """Return the image id from a given image ref
+
+ This function just returns the last word of the given image ref string
+ splitting with '/'.
+ :param str image_ref: a string that includes the image id
+ :return: the image id string
+ :rtype: string
+ """
+ return image_ref.rsplit('/')[-1]
+
+
+def arbitrary_string(size=4, base_text=None):
+ """Return size characters from base_text
+
+ This generates a string with an arbitrary number of characters, generated
+ by looping the base_text string. If the size is smaller than the size of
+ base_text, returning string is shrinked to the size.
+ :param int size: a returning charactors size
+ :param str base_text: a string you want to repeat
+ :return: size string
+ :rtype: string
+ """
+ if not base_text:
+ base_text = 'test'
+ return ''.join(itertools.islice(itertools.cycle(base_text), size))
+
+
+def random_bytes(size=1024):
+ """Return size randomly selected bytes as a string
+
+ :param int size: a returning bytes size
+ :return: size randomly bytes
+ :rtype: string
+ """
+ return ''.join([chr(random.randint(0, 255))
+ for i in range(size)])
+
+
+def get_ipv6_addr_by_EUI64(cidr, mac):
+ """Generate a IPv6 addr by EUI-64 with CIDR and MAC
+
+ :param str cidr: a IPv6 CIDR
+ :param str mac: a MAC address
+ :return: an IPv6 Address
+ :rtype: netaddr.IPAddress
+ """
+ # Check if the prefix is IPv4 address
+ is_ipv4 = netaddr.valid_ipv4(cidr)
+ if is_ipv4:
+ msg = "Unable to generate IP address by EUI64 for IPv4 prefix"
+ raise TypeError(msg)
+ try:
+ eui64 = int(netaddr.EUI(mac).eui64())
+ prefix = netaddr.IPNetwork(cidr)
+ return netaddr.IPAddress(prefix.first + eui64 ^ (1 << 57))
+ except (ValueError, netaddr.AddrFormatError):
+ raise TypeError('Bad prefix or mac format for generating IPv6 '
+ 'address by EUI-64: %(prefix)s, %(mac)s:'
+ % {'prefix': cidr, 'mac': mac})
+ except TypeError:
+ raise TypeError('Bad prefix type for generate IPv6 address by '
+ 'EUI-64: %s' % cidr)
diff --git a/tempest/lib/common/utils/misc.py b/tempest/lib/common/utils/misc.py
new file mode 100644
index 0000000..b97dd86
--- /dev/null
+++ b/tempest/lib/common/utils/misc.py
@@ -0,0 +1,87 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import inspect
+import re
+
+from oslo_log import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+def singleton(cls):
+ """Simple wrapper for classes that should only have a single instance."""
+ instances = {}
+
+ def getinstance():
+ if cls not in instances:
+ instances[cls] = cls()
+ return instances[cls]
+ return getinstance
+
+
+def find_test_caller():
+ """Find the caller class and test name.
+
+ Because we know that the interesting things that call us are
+ test_* methods, and various kinds of setUp / tearDown, we
+ can look through the call stack to find appropriate methods,
+ and the class we were in when those were called.
+ """
+ caller_name = None
+ names = []
+ frame = inspect.currentframe()
+ is_cleanup = False
+ # Start climbing the ladder until we hit a good method
+ while True:
+ try:
+ frame = frame.f_back
+ name = frame.f_code.co_name
+ names.append(name)
+ if re.search("^(test_|setUp|tearDown)", name):
+ cname = ""
+ if 'self' in frame.f_locals:
+ cname = frame.f_locals['self'].__class__.__name__
+ if 'cls' in frame.f_locals:
+ cname = frame.f_locals['cls'].__name__
+ caller_name = cname + ":" + name
+ break
+ elif re.search("^_run_cleanup", name):
+ is_cleanup = True
+ elif name == 'main':
+ caller_name = 'main'
+ break
+ else:
+ cname = ""
+ if 'self' in frame.f_locals:
+ cname = frame.f_locals['self'].__class__.__name__
+ if 'cls' in frame.f_locals:
+ cname = frame.f_locals['cls'].__name__
+
+ # the fact that we are running cleanups is indicated pretty
+ # deep in the stack, so if we see that we want to just
+ # start looking for a real class name, and declare victory
+ # once we do.
+ if is_cleanup and cname:
+ if not re.search("^RunTest", cname):
+ caller_name = cname + ":_run_cleanups"
+ break
+ except Exception:
+ break
+ # prevents frame leaks
+ del frame
+ if caller_name is None:
+ LOG.debug("Sane call name not found in %s" % names)
+ return caller_name
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
new file mode 100644
index 0000000..e78e624
--- /dev/null
+++ b/tempest/lib/decorators.py
@@ -0,0 +1,80 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 functools
+import uuid
+
+import six
+import testtools
+
+
+def skip_because(*args, **kwargs):
+ """A decorator useful to skip tests hitting known bugs
+
+ @param bug: bug number causing the test to skip
+ @param condition: optional condition to be True for the skip to have place
+ """
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ skip = False
+ if "condition" in kwargs:
+ if kwargs["condition"] is True:
+ skip = True
+ else:
+ skip = True
+ if "bug" in kwargs and skip is True:
+ if not kwargs['bug'].isdigit():
+ raise ValueError('bug must be a valid bug number')
+ msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
+def idempotent_id(id):
+ """Stub for metadata decorator"""
+ if not isinstance(id, six.string_types):
+ raise TypeError('Test idempotent_id must be string not %s'
+ '' % type(id).__name__)
+ uuid.UUID(id)
+
+ def decorator(f):
+ f = testtools.testcase.attr('id-%s' % id)(f)
+ if f.__doc__:
+ f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
+ else:
+ f.__doc__ = 'Test idempotent id: %s' % id
+ return f
+ return decorator
+
+
+class skip_unless_attr(object):
+ """Decorator to skip tests if a specified attr does not exists or False"""
+ def __init__(self, attr, msg=None):
+ self.attr = attr
+ self.message = msg or ("Test case attribute %s not found "
+ "or False") % attr
+
+ def __call__(self, func):
+ def _skipper(*args, **kw):
+ """Wrapped skipper function."""
+ testobj = args[0]
+ if not getattr(testobj, self.attr, False):
+ raise testtools.TestCase.skipException(self.message)
+ func(*args, **kw)
+ _skipper.__name__ = func.__name__
+ _skipper.__doc__ = func.__doc__
+ return _skipper
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
new file mode 100644
index 0000000..2bf7cdd
--- /dev/null
+++ b/tempest/lib/exceptions.py
@@ -0,0 +1,205 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import testtools
+
+
+class TempestException(Exception):
+ """Base Tempest Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+ """
+ message = "An unknown exception occurred"
+
+ def __init__(self, *args, **kwargs):
+ super(TempestException, self).__init__()
+ try:
+ self._error_string = self.message % kwargs
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+ if len(args) > 0:
+ # If there is a non-kwarg parameter, assume it's the error
+ # message or reason description and tack it on to the end
+ # of the exception message
+ # Convert all arguments into their string representations...
+ args = ["%s" % arg for arg in args]
+ self._error_string = (self._error_string +
+ "\nDetails: %s" % '\n'.join(args))
+
+ def __str__(self):
+ return self._error_string
+
+
+class RestClientException(TempestException,
+ testtools.TestCase.failureException):
+ def __init__(self, resp_body=None, *args, **kwargs):
+ if 'resp' in kwargs:
+ self.resp = kwargs.get('resp')
+ self.resp_body = resp_body
+ message = kwargs.get("message", resp_body)
+ super(RestClientException, self).__init__(message, *args, **kwargs)
+
+
+class OtherRestClientException(RestClientException):
+ pass
+
+
+class ServerRestClientException(RestClientException):
+ pass
+
+
+class ClientRestClientException(RestClientException):
+ pass
+
+
+class InvalidHttpSuccessCode(OtherRestClientException):
+ message = "The success code is different than the expected one"
+
+
+class NotFound(ClientRestClientException):
+ message = "Object not found"
+
+
+class Unauthorized(ClientRestClientException):
+ message = 'Unauthorized'
+
+
+class Forbidden(ClientRestClientException):
+ message = "Forbidden"
+
+
+class TimeoutException(OtherRestClientException):
+ message = "Request timed out"
+
+
+class BadRequest(ClientRestClientException):
+ message = "Bad request"
+
+
+class UnprocessableEntity(ClientRestClientException):
+ message = "Unprocessable entity"
+
+
+class RateLimitExceeded(ClientRestClientException):
+ message = "Rate limit exceeded"
+
+
+class OverLimit(ClientRestClientException):
+ message = "Quota exceeded"
+
+
+class ServerFault(ServerRestClientException):
+ message = "Got server fault"
+
+
+class NotImplemented(ServerRestClientException):
+ message = "Got NotImplemented error"
+
+
+class Conflict(ClientRestClientException):
+ message = "An object with that identifier already exists"
+
+
+class Gone(ClientRestClientException):
+ message = "The requested resource is no longer available"
+
+
+class ResponseWithNonEmptyBody(OtherRestClientException):
+ message = ("RFC Violation! Response with %(status)d HTTP Status Code "
+ "MUST NOT have a body")
+
+
+class ResponseWithEntity(OtherRestClientException):
+ message = ("RFC Violation! Response with 205 HTTP Status Code "
+ "MUST NOT have an entity")
+
+
+class InvalidHTTPResponseBody(OtherRestClientException):
+ message = "HTTP response body is invalid json or xml"
+
+
+class InvalidHTTPResponseHeader(OtherRestClientException):
+ message = "HTTP response header is invalid"
+
+
+class InvalidContentType(ClientRestClientException):
+ message = "Invalid content type provided"
+
+
+class UnexpectedContentType(OtherRestClientException):
+ message = "Unexpected content type provided"
+
+
+class UnexpectedResponseCode(OtherRestClientException):
+ message = "Unexpected response code received"
+
+
+class InvalidStructure(TempestException):
+ message = "Invalid structure of table with details"
+
+
+class BadAltAuth(TempestException):
+ """Used when trying and failing to change to alt creds.
+
+ If alt creds end up the same as primary creds, use this
+ exception. This is often going to be the case when you assume
+ project_id is in the url, but it's not.
+
+ """
+ message = "The alt auth looks the same as primary auth for %(part)s"
+
+
+class CommandFailed(Exception):
+ def __init__(self, returncode, cmd, output, stderr):
+ super(CommandFailed, self).__init__()
+ self.returncode = returncode
+ self.cmd = cmd
+ self.stdout = output
+ self.stderr = stderr
+
+ def __str__(self):
+ return ("Command '%s' returned non-zero exit status %d.\n"
+ "stdout:\n%s\n"
+ "stderr:\n%s" % (self.cmd,
+ self.returncode,
+ self.stdout,
+ self.stderr))
+
+
+class IdentityError(TempestException):
+ message = "Got identity error"
+
+
+class EndpointNotFound(TempestException):
+ message = "Endpoint not found"
+
+
+class InvalidCredentials(TempestException):
+ message = "Invalid Credentials"
+
+
+class SSHTimeout(TempestException):
+ message = ("Connection to the %(host)s via SSH timed out.\n"
+ "User: %(user)s, Password: %(password)s")
+
+
+class SSHExecCommandFailed(TempestException):
+ """Raised when remotely executed command returns nonzero status."""
+ message = ("Command '%(command)s', exit status: %(exit_status)d, "
+ "stderr:\n%(stderr)s\n"
+ "stdout:\n%(stdout)s")
diff --git a/tempest/lib/services/__init__.py b/tempest/lib/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/__init__.py
diff --git a/tempest/lib/services/compute/__init__.py b/tempest/lib/services/compute/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/compute/__init__.py
diff --git a/tempest/lib/services/compute/agents_client.py b/tempest/lib/services/compute/agents_client.py
new file mode 100644
index 0000000..8b11e64
--- /dev/null
+++ b/tempest/lib/services/compute/agents_client.py
@@ -0,0 +1,63 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import agents as schema
+from tempest.lib.common import rest_client
+
+
+class AgentsClient(rest_client.RestClient):
+ """Tests Agents API"""
+
+ def list_agents(self, **params):
+ """List all agent builds."""
+ url = 'os-agents'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_agents, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_agent(self, **kwargs):
+ """Create an agent build.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#agentbuild
+ """
+ post_body = json.dumps({'agent': kwargs})
+ resp, body = self.post('os-agents', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_agent, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_agent(self, agent_id):
+ """Delete an existing agent build."""
+ resp, body = self.delete("os-agents/%s" % agent_id)
+ self.validate_response(schema.delete_agent, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_agent(self, agent_id, **kwargs):
+ """Update an agent build.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updatebuild
+ """
+ put_body = json.dumps({'para': kwargs})
+ resp, body = self.put('os-agents/%s' % agent_id, put_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_agent, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/aggregates_client.py b/tempest/lib/services/compute/aggregates_client.py
new file mode 100644
index 0000000..b481674
--- /dev/null
+++ b/tempest/lib/services/compute/aggregates_client.py
@@ -0,0 +1,116 @@
+# Copyright 2013 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import aggregates as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class AggregatesClient(rest_client.RestClient):
+
+ def list_aggregates(self):
+ """Get aggregate list."""
+ resp, body = self.get("os-aggregates")
+ body = json.loads(body)
+ self.validate_response(schema.list_aggregates, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_aggregate(self, aggregate_id):
+ """Get details of the given aggregate."""
+ resp, body = self.get("os-aggregates/%s" % aggregate_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_aggregate(self, **kwargs):
+ """Create a new aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createaggregate
+ """
+ post_body = json.dumps({'aggregate': kwargs})
+ resp, body = self.post('os-aggregates', post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.create_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_aggregate(self, aggregate_id, **kwargs):
+ """Update an aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateaggregate
+ """
+ put_body = json.dumps({'aggregate': kwargs})
+ resp, body = self.put('os-aggregates/%s' % aggregate_id, put_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.update_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_aggregate(self, aggregate_id):
+ """Delete the given aggregate."""
+ resp, body = self.delete("os-aggregates/%s" % aggregate_id)
+ self.validate_response(schema.delete_aggregate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_aggregate(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'aggregate'
+
+ def add_host(self, aggregate_id, **kwargs):
+ """Add a host to the given aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#addhost
+ """
+ post_body = json.dumps({'add_host': kwargs})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.aggregate_add_remove_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_host(self, aggregate_id, **kwargs):
+ """Remove a host from the given aggregate.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#removehost
+ """
+ post_body = json.dumps({'remove_host': kwargs})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.aggregate_add_remove_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_metadata(self, aggregate_id, **kwargs):
+ """Replace the aggregate's existing metadata with new metadata."""
+ post_body = json.dumps({'set_metadata': kwargs})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.aggregate_set_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/availability_zone_client.py b/tempest/lib/services/compute/availability_zone_client.py
new file mode 100644
index 0000000..00f66d6
--- /dev/null
+++ b/tempest/lib/services/compute/availability_zone_client.py
@@ -0,0 +1,35 @@
+# Copyright 2013 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import availability_zone \
+ as schema
+from tempest.lib.common import rest_client
+
+
+class AvailabilityZoneClient(rest_client.RestClient):
+
+ def list_availability_zones(self, detail=False):
+ url = 'os-availability-zone'
+ schema_list = schema.list_availability_zone_list
+ if detail:
+ url += '/detail'
+ schema_list = schema.list_availability_zone_list_detail
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema_list, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/baremetal_nodes_client.py b/tempest/lib/services/compute/baremetal_nodes_client.py
new file mode 100644
index 0000000..d9a712e
--- /dev/null
+++ b/tempest/lib/services/compute/baremetal_nodes_client.py
@@ -0,0 +1,42 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import baremetal_nodes \
+ as schema
+from tempest.lib.common import rest_client
+
+
+class BaremetalNodesClient(rest_client.RestClient):
+ """Tests Baremetal API"""
+
+ def list_baremetal_nodes(self, **params):
+ """List all baremetal nodes."""
+ url = 'os-baremetal-nodes'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_baremetal_nodes, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_baremetal_node(self, baremetal_node_id):
+ """Return the details of a single baremetal node."""
+ url = 'os-baremetal-nodes/%s' % baremetal_node_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_baremetal_node, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/certificates_client.py b/tempest/lib/services/compute/certificates_client.py
new file mode 100644
index 0000000..76d830e
--- /dev/null
+++ b/tempest/lib/services/compute/certificates_client.py
@@ -0,0 +1,37 @@
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import certificates as schema
+from tempest.lib.common import rest_client
+
+
+class CertificatesClient(rest_client.RestClient):
+
+ def show_certificate(self, certificate_id):
+ url = "os-certificates/%s" % certificate_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_certificate, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_certificate(self):
+ """Create a certificate."""
+ url = "os-certificates"
+ resp, body = self.post(url, None)
+ body = json.loads(body)
+ self.validate_response(schema.create_certificate, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/extensions_client.py b/tempest/lib/services/compute/extensions_client.py
new file mode 100644
index 0000000..85f8f0c
--- /dev/null
+++ b/tempest/lib/services/compute/extensions_client.py
@@ -0,0 +1,34 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import extensions as schema
+from tempest.lib.common import rest_client
+
+
+class ExtensionsClient(rest_client.RestClient):
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_extensions, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_extension(self, extension_alias):
+ resp, body = self.get('extensions/%s' % extension_alias)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/fixed_ips_client.py b/tempest/lib/services/compute/fixed_ips_client.py
new file mode 100644
index 0000000..76ec59f
--- /dev/null
+++ b/tempest/lib/services/compute/fixed_ips_client.py
@@ -0,0 +1,40 @@
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import fixed_ips as schema
+from tempest.lib.common import rest_client
+
+
+class FixedIPsClient(rest_client.RestClient):
+
+ def show_fixed_ip(self, fixed_ip):
+ url = "os-fixed-ips/%s" % fixed_ip
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_fixed_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def reserve_fixed_ip(self, fixed_ip, **kwargs):
+ """Reserve/Unreserve a fixed IP.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#reserveIP
+ """
+ url = "os-fixed-ips/%s/action" % fixed_ip
+ resp, body = self.post(url, json.dumps(kwargs))
+ self.validate_response(schema.reserve_unreserve_fixed_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
new file mode 100644
index 0000000..50f1dcc
--- /dev/null
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -0,0 +1,176 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import flavors as schema
+from tempest.lib.api_schema.response.compute.v2_1 import flavors_access \
+ as schema_access
+from tempest.lib.api_schema.response.compute.v2_1 import flavors_extra_specs \
+ as schema_extra_specs
+from tempest.lib.common import rest_client
+
+
+class FlavorsClient(rest_client.RestClient):
+
+ def list_flavors(self, detail=False, **params):
+ url = 'flavors'
+ _schema = schema.list_flavors
+
+ if detail:
+ url += '/detail'
+ _schema = schema.list_flavors_details
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_flavor(self, flavor_id):
+ resp, body = self.get("flavors/%s" % flavor_id)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_flavor_details, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_flavor(self, **kwargs):
+ """Create a new flavor or instance type.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#create-flavors
+ """
+ if kwargs.get('ephemeral'):
+ kwargs['OS-FLV-EXT-DATA:ephemeral'] = kwargs.pop('ephemeral')
+ if kwargs.get('is_public'):
+ kwargs['os-flavor-access:is_public'] = kwargs.pop('is_public')
+
+ post_body = json.dumps({'flavor': kwargs})
+ resp, body = self.post('flavors', post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.create_get_flavor_details, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_flavor(self, flavor_id):
+ """Delete the given flavor."""
+ resp, body = self.delete("flavors/{0}".format(flavor_id))
+ self.validate_response(schema.delete_flavor, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ # Did not use show_flavor(id) for verification as it gives
+ # 200 ok even for deleted id. LP #981263
+ # we can remove the loop here and use get by ID when bug gets sortedout
+ flavors = self.list_flavors(detail=True)['flavors']
+ for flavor in flavors:
+ if flavor['id'] == id:
+ return False
+ return True
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'flavor'
+
+ def set_flavor_extra_spec(self, flavor_id, **kwargs):
+ """Set extra Specs to the mentioned flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateFlavorExtraSpec
+ """
+ post_body = json.dumps({'extra_specs': kwargs})
+ resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_flavor_extra_specs(self, flavor_id):
+ """Get extra Specs details of the mentioned flavor."""
+ resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
+ body = json.loads(body)
+ self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_flavor_extra_spec(self, flavor_id, key):
+ """Get extra Specs key-value of the mentioned flavor and key."""
+ resp, body = self.get('flavors/%s/os-extra_specs/%s' % (flavor_id,
+ key))
+ body = json.loads(body)
+ self.validate_response(
+ schema_extra_specs.set_get_flavor_extra_specs_key,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+ """Update specified extra Specs of the mentioned flavor and key.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateflavorspec
+ """
+ resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key), json.dumps(kwargs))
+ body = json.loads(body)
+ self.validate_response(
+ schema_extra_specs.set_get_flavor_extra_specs_key,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def unset_flavor_extra_spec(self, flavor_id, key):
+ """Unset extra Specs from the mentioned flavor."""
+ resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key))
+ self.validate_response(schema.unset_flavor_extra_specs, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_flavor_access(self, flavor_id):
+ """Get flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id)
+ body = json.loads(body)
+ self.validate_response(schema_access.add_remove_list_flavor_access,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_flavor_access(self, flavor_id, tenant_id):
+ """Add flavor access for the specified tenant."""
+ post_body = {
+ 'addTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema_access.add_remove_list_flavor_access,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_flavor_access(self, flavor_id, tenant_id):
+ """Remove flavor access from the specified tenant."""
+ post_body = {
+ 'removeTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema_access.add_remove_list_flavor_access,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ip_pools_client.py b/tempest/lib/services/compute/floating_ip_pools_client.py
new file mode 100644
index 0000000..d4a0193
--- /dev/null
+++ b/tempest/lib/services/compute/floating_ip_pools_client.py
@@ -0,0 +1,34 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
+from tempest.lib.common import rest_client
+
+
+class FloatingIPPoolsClient(rest_client.RestClient):
+
+ def list_floating_ip_pools(self, params=None):
+ """Gets all floating IP Pools list."""
+ url = 'os-floating-ip-pools'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ip_pools, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ips_bulk_client.py b/tempest/lib/services/compute/floating_ips_bulk_client.py
new file mode 100644
index 0000000..bfcf74b
--- /dev/null
+++ b/tempest/lib/services/compute/floating_ips_bulk_client.py
@@ -0,0 +1,50 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
+from tempest.lib.common import rest_client
+
+
+class FloatingIPsBulkClient(rest_client.RestClient):
+
+ def create_floating_ips_bulk(self, ip_range, pool, interface):
+ """Allocate floating IPs in bulk."""
+ post_body = {
+ 'ip_range': ip_range,
+ 'pool': pool,
+ 'interface': interface
+ }
+ post_body = json.dumps({'floating_ips_bulk_create': post_body})
+ resp, body = self.post('os-floating-ips-bulk', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_floating_ips_bulk, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_floating_ips_bulk(self):
+ """Gets all floating IPs in bulk."""
+ resp, body = self.get('os-floating-ips-bulk')
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ips_bulk, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_floating_ips_bulk(self, ip_range):
+ """Deletes the provided floating IPs in bulk."""
+ post_body = json.dumps({'ip_range': ip_range})
+ resp, body = self.put('os-floating-ips-bulk/delete', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.delete_floating_ips_bulk, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/floating_ips_client.py b/tempest/lib/services/compute/floating_ips_client.py
new file mode 100644
index 0000000..2569bf9
--- /dev/null
+++ b/tempest/lib/services/compute/floating_ips_client.py
@@ -0,0 +1,103 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class FloatingIPsClient(rest_client.RestClient):
+
+ def list_floating_ips(self, **params):
+ """Returns a list of all floating IPs filtered by any parameters."""
+ url = 'os-floating-ips'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_floating_ips, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_floating_ip(self, floating_ip_id):
+ """Get the details of a floating IP."""
+ url = "os-floating-ips/%s" % floating_ip_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_floating_ip(self, **kwargs):
+ """Allocate a floating IP to the project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createFloatingIP
+ """
+ url = 'os-floating-ips'
+ post_body = json.dumps(kwargs)
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_floating_ip(self, floating_ip_id):
+ """Deletes the provided floating IP from the project."""
+ url = "os-floating-ips/%s" % floating_ip_id
+ resp, body = self.delete(url)
+ self.validate_response(schema.add_remove_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def associate_floating_ip_to_server(self, floating_ip, server_id):
+ """Associate the provided floating IP to a specific server."""
+ url = "servers/%s/action" % server_id
+ post_body = {
+ 'addFloatingIp': {
+ 'address': floating_ip,
+ }
+ }
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post(url, post_body)
+ self.validate_response(schema.add_remove_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def disassociate_floating_ip_from_server(self, floating_ip, server_id):
+ """Disassociate the provided floating IP from a specific server."""
+ url = "servers/%s/action" % server_id
+ post_body = {
+ 'removeFloatingIp': {
+ 'address': floating_ip,
+ }
+ }
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post(url, post_body)
+ self.validate_response(schema.add_remove_floating_ip, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_floating_ip(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'floating_ip'
diff --git a/tempest/lib/services/compute/hosts_client.py b/tempest/lib/services/compute/hosts_client.py
new file mode 100644
index 0000000..269160e
--- /dev/null
+++ b/tempest/lib/services/compute/hosts_client.py
@@ -0,0 +1,85 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# 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.api_schema.response.compute.v2_1 import hosts as schema
+from tempest.lib.common import rest_client
+
+
+class HostsClient(rest_client.RestClient):
+
+ def list_hosts(self, **params):
+ """List all hosts."""
+
+ url = 'os-hosts'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_hosts, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_host(self, hostname):
+ """Show detail information for the host."""
+
+ resp, body = self.get("os-hosts/%s" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.get_host_detail, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_host(self, hostname, **kwargs):
+ """Update a host.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#enablehost
+ """
+
+ request_body = {
+ 'status': None,
+ 'maintenance_mode': None,
+ }
+ request_body.update(**kwargs)
+ request_body = json.dumps(request_body)
+
+ resp, body = self.put("os-hosts/%s" % hostname, request_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def startup_host(self, hostname):
+ """Startup a host."""
+
+ resp, body = self.get("os-hosts/%s/startup" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.startup_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def shutdown_host(self, hostname):
+ """Shutdown a host."""
+
+ resp, body = self.get("os-hosts/%s/shutdown" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.shutdown_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def reboot_host(self, hostname):
+ """Reboot a host."""
+
+ resp, body = self.get("os-hosts/%s/reboot" % hostname)
+ body = json.loads(body)
+ self.validate_response(schema.reboot_host, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/hypervisor_client.py b/tempest/lib/services/compute/hypervisor_client.py
new file mode 100644
index 0000000..2e6df1f
--- /dev/null
+++ b/tempest/lib/services/compute/hypervisor_client.py
@@ -0,0 +1,70 @@
+# Copyright 2013 IBM Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import hypervisors as schema
+from tempest.lib.common import rest_client
+
+
+class HypervisorClient(rest_client.RestClient):
+
+ def list_hypervisors(self, detail=False):
+ """List hypervisors information."""
+ url = 'os-hypervisors'
+ _schema = schema.list_search_hypervisors
+ if detail:
+ url += '/detail'
+ _schema = schema.list_hypervisors_detail
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_hypervisor(self, hypervisor_id):
+ """Display the details of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s' % hypervisor_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisor, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_servers_on_hypervisor(self, hypervisor_name):
+ """List instances belonging to the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/servers' % hypervisor_name)
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisors_servers, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_hypervisor_statistics(self):
+ """Get hypervisor statistics over all compute nodes."""
+ resp, body = self.get('os-hypervisors/statistics')
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisor_statistics, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_hypervisor_uptime(self, hypervisor_id):
+ """Display the uptime of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/uptime' % hypervisor_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_hypervisor_uptime, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def search_hypervisor(self, hypervisor_name):
+ """Search specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/search' % hypervisor_name)
+ body = json.loads(body)
+ self.validate_response(schema.list_search_hypervisors, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/images_client.py b/tempest/lib/services/compute/images_client.py
new file mode 100644
index 0000000..30ff484
--- /dev/null
+++ b/tempest/lib/services/compute/images_client.py
@@ -0,0 +1,142 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import images as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class ImagesClient(rest_client.RestClient):
+
+ def create_image(self, server_id, **kwargs):
+ """Create an image of the original server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createImage
+ """
+
+ post_body = {'createImage': kwargs}
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/action' % server_id,
+ post_body)
+ self.validate_response(schema.create_image, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_images(self, detail=False, **params):
+ """Return a list of all images filtered by any parameter.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listImages
+ """
+ url = 'images'
+ _schema = schema.list_images
+ if detail:
+ url += '/detail'
+ _schema = schema.list_images_details
+
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_image(self, image_id):
+ """Return the details of a single image."""
+ resp, body = self.get("images/%s" % image_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ self.validate_response(schema.get_image, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_image(self, image_id):
+ """Delete the provided image."""
+ resp, body = self.delete("images/%s" % image_id)
+ self.validate_response(schema.delete, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_image_metadata(self, image_id):
+ """List all metadata items for an image."""
+ resp, body = self.get("images/%s/metadata" % image_id)
+ body = json.loads(body)
+ self.validate_response(schema.image_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_image_metadata(self, image_id, meta):
+ """Set the metadata for an image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createImageMetadata
+ """
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('images/%s/metadata' % image_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.image_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_image_metadata(self, image_id, meta):
+ """Update the metadata for an image.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateImageMetadata
+ """
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.post('images/%s/metadata' % image_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.image_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_image_metadata_item(self, image_id, key):
+ """Return the value for a specific image metadata key."""
+ resp, body = self.get("images/%s/metadata/%s" % (image_id, key))
+ body = json.loads(body)
+ self.validate_response(schema.image_meta_item, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_image_metadata_item(self, image_id, key, meta):
+ """Set the value for a specific image metadata key.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#setImageMetadataItem
+ """
+ post_body = json.dumps({'meta': meta})
+ resp, body = self.put('images/%s/metadata/%s' % (image_id, key),
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.image_meta_item, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_image_metadata_item(self, image_id, key):
+ """Delete a single image metadata key/value pair."""
+ resp, body = self.delete("images/%s/metadata/%s" %
+ (image_id, key))
+ self.validate_response(schema.delete, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_image(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'image'
diff --git a/tempest/lib/services/compute/instance_usage_audit_log_client.py b/tempest/lib/services/compute/instance_usage_audit_log_client.py
new file mode 100644
index 0000000..4651b2a
--- /dev/null
+++ b/tempest/lib/services/compute/instance_usage_audit_log_client.py
@@ -0,0 +1,38 @@
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ instance_usage_audit_logs as schema
+from tempest.lib.common import rest_client
+
+
+class InstanceUsagesAuditLogClient(rest_client.RestClient):
+
+ def list_instance_usage_audit_logs(self):
+ url = 'os-instance_usage_audit_log'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_instance_usage_audit_log,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_instance_usage_audit_log(self, time_before):
+ url = 'os-instance_usage_audit_log/%s' % time_before
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_instance_usage_audit_log, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/interfaces_client.py b/tempest/lib/services/compute/interfaces_client.py
new file mode 100644
index 0000000..e7da5a1
--- /dev/null
+++ b/tempest/lib/services/compute/interfaces_client.py
@@ -0,0 +1,55 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import interfaces as schema
+from tempest.lib.common import rest_client
+
+
+class InterfacesClient(rest_client.RestClient):
+
+ def list_interfaces(self, server_id):
+ resp, body = self.get('servers/%s/os-interface' % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_interface(self, server_id, **kwargs):
+ """Create an interface.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createAttachInterface
+ """
+ post_body = {'interfaceAttachment': kwargs}
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/os-interface' % server_id,
+ body=post_body)
+ body = json.loads(body)
+ self.validate_response(schema.get_create_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_interface(self, server_id, port_id):
+ resp, body = self.get('servers/%s/os-interface/%s' % (server_id,
+ port_id))
+ body = json.loads(body)
+ self.validate_response(schema.get_create_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_interface(self, server_id, port_id):
+ resp, body = self.delete('servers/%s/os-interface/%s' % (server_id,
+ port_id))
+ self.validate_response(schema.delete_interface, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
new file mode 100644
index 0000000..3e3cf8d
--- /dev/null
+++ b/tempest/lib/services/compute/keypairs_client.py
@@ -0,0 +1,51 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import keypairs as schema
+from tempest.lib.common import rest_client
+
+
+class KeyPairsClient(rest_client.RestClient):
+
+ def list_keypairs(self):
+ resp, body = self.get("os-keypairs")
+ body = json.loads(body)
+ self.validate_response(schema.list_keypairs, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_keypair(self, keypair_name):
+ resp, body = self.get("os-keypairs/%s" % keypair_name)
+ body = json.loads(body)
+ self.validate_response(schema.get_keypair, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_keypair(self, **kwargs):
+ """Create a keypair.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createKeypair
+ """
+ post_body = json.dumps({'keypair': kwargs})
+ resp, body = self.post("os-keypairs", body=post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_keypair, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_keypair(self, keypair_name):
+ resp, body = self.delete("os-keypairs/%s" % keypair_name)
+ self.validate_response(schema.delete_keypair, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/limits_client.py b/tempest/lib/services/compute/limits_client.py
new file mode 100644
index 0000000..c7eba4e
--- /dev/null
+++ b/tempest/lib/services/compute/limits_client.py
@@ -0,0 +1,28 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import limits as schema
+from tempest.lib.common import rest_client
+
+
+class LimitsClient(rest_client.RestClient):
+
+ def show_limits(self):
+ resp, body = self.get("limits")
+ body = json.loads(body)
+ self.validate_response(schema.get_limit, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
new file mode 100644
index 0000000..21bc37a
--- /dev/null
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -0,0 +1,38 @@
+# Copyright 2014 NEC Corporation.
+#
+# 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.api_schema.response.compute.v2_1 import migrations as schema
+from tempest.lib.common import rest_client
+
+
+class MigrationsClient(rest_client.RestClient):
+
+ def list_migrations(self, **params):
+ """List all migrations.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#returnmigrations
+ """
+
+ url = 'os-migrations'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_migrations, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/networks_client.py b/tempest/lib/services/compute/networks_client.py
new file mode 100644
index 0000000..c0eb5ff
--- /dev/null
+++ b/tempest/lib/services/compute/networks_client.py
@@ -0,0 +1,33 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class NetworksClient(rest_client.RestClient):
+
+ def list_networks(self):
+ resp, body = self.get("os-networks")
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_network(self, network_id):
+ resp, body = self.get("os-networks/%s" % network_id)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/quota_classes_client.py b/tempest/lib/services/compute/quota_classes_client.py
new file mode 100644
index 0000000..ff4eec0
--- /dev/null
+++ b/tempest/lib/services/compute/quota_classes_client.py
@@ -0,0 +1,48 @@
+# Copyright 2012 NTT Data
+# 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.api_schema.response.compute.v2_1\
+ import quota_classes as classes_schema
+from tempest.lib.common import rest_client
+
+
+class QuotaClassesClient(rest_client.RestClient):
+
+ def show_quota_class_set(self, quota_class_id):
+ """List the quota class set for a quota class."""
+
+ url = 'os-quota-class-sets/%s' % quota_class_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(classes_schema.get_quota_class_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """Update the quota class's limits for one or more resources.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updatequota
+ """
+ post_body = json.dumps({'quota_class_set': kwargs})
+
+ resp, body = self.put('os-quota-class-sets/%s' % quota_class_id,
+ post_body)
+
+ body = json.loads(body)
+ self.validate_response(classes_schema.update_quota_class_set,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
new file mode 100644
index 0000000..697d004
--- /dev/null
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -0,0 +1,68 @@
+# Copyright 2012 NTT Data
+# 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.api_schema.response.compute.v2_1 import quotas as schema
+from tempest.lib.common import rest_client
+
+
+class QuotasClient(rest_client.RestClient):
+
+ def show_quota_set(self, tenant_id, user_id=None):
+ """List the quota set for a tenant."""
+
+ url = 'os-quota-sets/%s' % tenant_id
+ if user_id:
+ url += '?user_id=%s' % user_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_quota_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_default_quota_set(self, tenant_id):
+ """List the default quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % tenant_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_quota_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_set(self, tenant_id, user_id=None, **kwargs):
+ """Updates the tenant's quota limits for one or more resources.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updatesquotatenant
+ """
+
+ post_body = json.dumps({'quota_set': kwargs})
+
+ if user_id:
+ resp, body = self.put('os-quota-sets/%s?user_id=%s' %
+ (tenant_id, user_id), post_body)
+ else:
+ resp, body = self.put('os-quota-sets/%s' % tenant_id,
+ post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.update_quota_set, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+ self.validate_response(schema.delete_quota, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_group_default_rules_client.py b/tempest/lib/services/compute/security_group_default_rules_client.py
new file mode 100644
index 0000000..e5f291c
--- /dev/null
+++ b/tempest/lib/services/compute/security_group_default_rules_client.py
@@ -0,0 +1,64 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ security_group_default_rule as schema
+from tempest.lib.common import rest_client
+
+
+class SecurityGroupDefaultRulesClient(rest_client.RestClient):
+
+ def create_security_default_group_rule(self, **kwargs):
+ """Create security group default rule.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html
+ #createSecGroupDefaultRule
+ """
+ post_body = json.dumps({'security_group_default_rule': kwargs})
+ url = 'os-security-group-default-rules'
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_security_group_default_rule,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_security_group_default_rule(self,
+ security_group_default_rule_id):
+ """Delete the provided Security Group default rule."""
+ resp, body = self.delete('os-security-group-default-rules/%s' % (
+ security_group_default_rule_id))
+ self.validate_response(schema.delete_security_group_default_rule,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_security_group_default_rules(self):
+ """List all Security Group default rules."""
+ resp, body = self.get('os-security-group-default-rules')
+ body = json.loads(body)
+ self.validate_response(schema.list_security_group_default_rules,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_security_group_default_rule(self, security_group_default_rule_id):
+ """Return the details of provided Security Group default rule."""
+ resp, body = self.get('os-security-group-default-rules/%s' %
+ security_group_default_rule_id)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_security_group_default_rule,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_group_rules_client.py b/tempest/lib/services/compute/security_group_rules_client.py
new file mode 100644
index 0000000..c0e1245
--- /dev/null
+++ b/tempest/lib/services/compute/security_group_rules_client.py
@@ -0,0 +1,43 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ security_groups as schema
+from tempest.lib.common import rest_client
+
+
+class SecurityGroupRulesClient(rest_client.RestClient):
+
+ def create_security_group_rule(self, **kwargs):
+ """Create a new security group rule.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createSecGroupRule
+ """
+ post_body = json.dumps({'security_group_rule': kwargs})
+ url = 'os-security-group-rules'
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_security_group_rule, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_security_group_rule(self, group_rule_id):
+ """Deletes the provided Security Group rule."""
+ resp, body = self.delete('os-security-group-rules/%s' %
+ group_rule_id)
+ self.validate_response(schema.delete_security_group_rule, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/security_groups_client.py b/tempest/lib/services/compute/security_groups_client.py
new file mode 100644
index 0000000..4db98c9
--- /dev/null
+++ b/tempest/lib/services/compute/security_groups_client.py
@@ -0,0 +1,89 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import \
+ security_groups as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class SecurityGroupsClient(rest_client.RestClient):
+
+ def list_security_groups(self, **params):
+ """List all security groups for a user."""
+
+ url = 'os-security-groups'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_security_groups, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_security_group(self, security_group_id):
+ """Get the details of a Security Group."""
+ url = "os-security-groups/%s" % security_group_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.get_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_security_group(self, **kwargs):
+ """Create a new security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createSecGroup
+ """
+ post_body = json.dumps({'security_group': kwargs})
+ resp, body = self.post('os-security-groups', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.get_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_security_group(self, security_group_id, **kwargs):
+ """Update a security group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateSecGroup
+ """
+ post_body = json.dumps({'security_group': kwargs})
+ resp, body = self.put('os-security-groups/%s' % security_group_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_security_group(self, security_group_id):
+ """Delete the provided Security Group."""
+ resp, body = self.delete(
+ 'os-security-groups/%s' % security_group_id)
+ self.validate_response(schema.delete_security_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_security_group(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'security_group'
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
new file mode 100644
index 0000000..ea60e98
--- /dev/null
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -0,0 +1,56 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
+from tempest.lib.common import rest_client
+
+
+class ServerGroupsClient(rest_client.RestClient):
+
+ def create_server_group(self, **kwargs):
+ """Create the server group.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createServerGroup
+ """
+ post_body = json.dumps({'server_group': kwargs})
+ resp, body = self.post('os-server-groups', post_body)
+
+ body = json.loads(body)
+ self.validate_response(schema.create_show_server_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_server_group(self, server_group_id):
+ """Delete the given server-group."""
+ resp, body = self.delete("os-server-groups/%s" % server_group_id)
+ self.validate_response(schema.delete_server_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_server_groups(self):
+ """List the server-groups."""
+ resp, body = self.get("os-server-groups")
+ body = json.loads(body)
+ self.validate_response(schema.list_server_groups, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_server_group(self, server_group_id):
+ """Get the details of given server_group."""
+ resp, body = self.get("os-server-groups/%s" % server_group_id)
+ body = json.loads(body)
+ self.validate_response(schema.create_show_server_group, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
new file mode 100644
index 0000000..46c4a49
--- /dev/null
+++ b/tempest/lib/services/compute/servers_client.py
@@ -0,0 +1,570 @@
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
+from tempest.lib.common import rest_client
+
+
+class ServersClient(rest_client.RestClient):
+
+ def __init__(self, auth_provider, service, region,
+ enable_instance_password=True, **kwargs):
+ super(ServersClient, self).__init__(
+ auth_provider, service, region, **kwargs)
+ self.enable_instance_password = enable_instance_password
+
+ def create_server(self, **kwargs):
+ """Create server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createServer
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ :param scheduler_hints: The name is changed to os:scheduler_hints and
+ the parameter is set in the same level as the parameter 'server'.
+ """
+ body = copy.deepcopy(kwargs)
+ if body.get('disk_config'):
+ body['OS-DCF:diskConfig'] = body.pop('disk_config')
+
+ hints = None
+ if body.get('scheduler_hints'):
+ hints = {'os:scheduler_hints': body.pop('scheduler_hints')}
+
+ post_body = {'server': body}
+
+ if hints:
+ post_body = dict(post_body.items() + hints.items())
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers', post_body)
+
+ body = json.loads(body)
+ # NOTE(maurosr): this deals with the case of multiple server create
+ # with return reservation id set True
+ if 'reservation_id' in body:
+ return rest_client.ResponseBody(resp, body)
+ if self.enable_instance_password:
+ create_schema = schema.create_server_with_admin_pass
+ else:
+ create_schema = schema.create_server
+ self.validate_response(create_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_server(self, server_id, **kwargs):
+ """Update server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#updateServer
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ """
+ if kwargs.get('disk_config'):
+ kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
+
+ post_body = json.dumps({'server': kwargs})
+ resp, body = self.put("servers/%s" % server_id, post_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_server, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_server(self, server_id):
+ """Get server details."""
+ resp, body = self.get("servers/%s" % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.get_server, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_server(self, server_id):
+ """Delete server."""
+ resp, body = self.delete("servers/%s" % server_id)
+ self.validate_response(schema.delete_server, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_servers(self, detail=False, **params):
+ """List servers.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listServers
+ and http://developer.openstack.org/
+ api-ref-compute-v2.1.html#listDetailServers
+ """
+
+ url = 'servers'
+ _schema = schema.list_servers
+
+ if detail:
+ url += '/detail'
+ _schema = schema.list_servers_detail
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_addresses(self, server_id):
+ """Lists all addresses for a server."""
+ resp, body = self.get("servers/%s/ips" % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_addresses, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_addresses_by_network(self, server_id, network_id):
+ """Lists all addresses of a specific network type for a server."""
+ resp, body = self.get("servers/%s/ips/%s" %
+ (server_id, network_id))
+ body = json.loads(body)
+ self.validate_response(schema.list_addresses_by_network, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def action(self, server_id, action_name,
+ schema=schema.server_actions_common_schema,
+ **kwargs):
+ post_body = json.dumps({action_name: kwargs})
+ resp, body = self.post('servers/%s/action' % server_id,
+ post_body)
+ if body:
+ body = json.loads(body)
+ self.validate_response(schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_backup(self, server_id, **kwargs):
+ """Backup a server instance.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createBackup
+ """
+ return self.action(server_id, "createBackup", **kwargs)
+
+ def change_password(self, server_id, **kwargs):
+ """Change the root password for the server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#changePassword
+ """
+ return self.action(server_id, 'changePassword', **kwargs)
+
+ def show_password(self, server_id):
+ resp, body = self.get("servers/%s/os-server-password" %
+ server_id)
+ body = json.loads(body)
+ self.validate_response(schema.show_password, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_password(self, server_id):
+ """Removes the encrypted server password from the metadata server
+
+ Note that this does not actually change the instance server
+ password.
+ """
+ resp, body = self.delete("servers/%s/os-server-password" %
+ server_id)
+ self.validate_response(schema.server_actions_delete_password,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def reboot_server(self, server_id, **kwargs):
+ """Reboot a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#reboot
+ """
+ return self.action(server_id, 'reboot', **kwargs)
+
+ def rebuild_server(self, server_id, image_ref, **kwargs):
+ """Rebuild a server with a new image.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#rebuild
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ """
+ kwargs['imageRef'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
+ if self.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild',
+ rebuild_schema, **kwargs)
+
+ def resize_server(self, server_id, flavor_ref, **kwargs):
+ """Change the flavor of a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resize
+
+ Most parameters except the following are passed to the API without
+ any changes.
+ :param disk_config: The name is changed to OS-DCF:diskConfig
+ """
+ kwargs['flavorRef'] = flavor_ref
+ if 'disk_config' in kwargs:
+ kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
+ return self.action(server_id, 'resize', **kwargs)
+
+ def confirm_resize_server(self, server_id, **kwargs):
+ """Confirm the flavor change for a server.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#confirmResize
+ """
+ return self.action(server_id, 'confirmResize',
+ schema.server_actions_confirm_resize,
+ **kwargs)
+
+ def revert_resize_server(self, server_id, **kwargs):
+ """Revert a server back to its original flavor.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#revertResize
+ """
+ return self.action(server_id, 'revertResize', **kwargs)
+
+ def list_server_metadata(self, server_id):
+ resp, body = self.get("servers/%s/metadata" % server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_server_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ if no_metadata_field:
+ post_body = ""
+ else:
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('servers/%s/metadata' % server_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.set_server_metadata, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_server_metadata(self, server_id, meta):
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.post('servers/%s/metadata' % server_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.update_server_metadata,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_server_metadata_item(self, server_id, key):
+ resp, body = self.get("servers/%s/metadata/%s" % (server_id, key))
+ body = json.loads(body)
+ self.validate_response(schema.set_show_server_metadata_item,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_server_metadata_item(self, server_id, key, meta):
+ post_body = json.dumps({'meta': meta})
+ resp, body = self.put('servers/%s/metadata/%s' % (server_id, key),
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.set_show_server_metadata_item,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_server_metadata_item(self, server_id, key):
+ resp, body = self.delete("servers/%s/metadata/%s" %
+ (server_id, key))
+ self.validate_response(schema.delete_server_metadata_item,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def stop_server(self, server_id, **kwargs):
+ return self.action(server_id, 'os-stop', **kwargs)
+
+ def start_server(self, server_id, **kwargs):
+ return self.action(server_id, 'os-start', **kwargs)
+
+ def attach_volume(self, server_id, **kwargs):
+ """Attaches a volume to a server instance."""
+ post_body = json.dumps({'volumeAttachment': kwargs})
+ resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
+ post_body)
+ body = json.loads(body)
+ self.validate_response(schema.attach_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_attached_volume(self, server_id, attachment_id, **kwargs):
+ """Swaps a volume attached to an instance for another volume"""
+ post_body = json.dumps({'volumeAttachment': kwargs})
+ resp, body = self.put('servers/%s/os-volume_attachments/%s' %
+ (server_id, attachment_id),
+ post_body)
+ self.validate_response(schema.update_attached_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def detach_volume(self, server_id, volume_id): # noqa
+ """Detaches a volume from a server instance."""
+ resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
+ (server_id, volume_id))
+ self.validate_response(schema.detach_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_attachment(self, server_id, volume_id):
+ """Return details about the given volume attachment."""
+ resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
+ server_id, volume_id))
+ body = json.loads(body)
+ self.validate_response(schema.show_volume_attachment, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_volume_attachments(self, server_id):
+ """Returns the list of volume attachments for a given instance."""
+ resp, body = self.get('servers/%s/os-volume_attachments' % (
+ server_id))
+ body = json.loads(body)
+ self.validate_response(schema.list_volume_attachments, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_security_group(self, server_id, **kwargs):
+ """Add a security group to the server.
+
+ Available params: TODO
+ """
+ # TODO(oomichi): The api-site doesn't contain this API description.
+ # So the above should be changed to the api-site link after
+ # adding the description on the api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524199
+ return self.action(server_id, 'addSecurityGroup', **kwargs)
+
+ def remove_security_group(self, server_id, **kwargs):
+ """Remove a security group from the server.
+
+ Available params: TODO
+ """
+ # TODO(oomichi): The api-site doesn't contain this API description.
+ # So the above should be changed to the api-site link after
+ # adding the description on the api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524199
+ return self.action(server_id, 'removeSecurityGroup', **kwargs)
+
+ def live_migrate_server(self, server_id, **kwargs):
+ """This should be called with administrator privileges.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#migrateLive
+ """
+ return self.action(server_id, 'os-migrateLive', **kwargs)
+
+ def migrate_server(self, server_id, **kwargs):
+ """Migrate a server to a new host.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#migrate
+ """
+ return self.action(server_id, 'migrate', **kwargs)
+
+ def lock_server(self, server_id, **kwargs):
+ """Lock the given server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#lock
+ """
+ return self.action(server_id, 'lock', **kwargs)
+
+ def unlock_server(self, server_id, **kwargs):
+ """UNlock the given server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unlock
+ """
+ return self.action(server_id, 'unlock', **kwargs)
+
+ def suspend_server(self, server_id, **kwargs):
+ """Suspend the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#suspend
+ """
+ return self.action(server_id, 'suspend', **kwargs)
+
+ def resume_server(self, server_id, **kwargs):
+ """Un-suspend the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resume
+ """
+ return self.action(server_id, 'resume', **kwargs)
+
+ def pause_server(self, server_id, **kwargs):
+ """Pause the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#pause
+ """
+ return self.action(server_id, 'pause', **kwargs)
+
+ def unpause_server(self, server_id, **kwargs):
+ """Un-pause the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unpause
+ """
+ return self.action(server_id, 'unpause', **kwargs)
+
+ def reset_state(self, server_id, **kwargs):
+ """Reset the state of a server to active/error.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resetState
+ """
+ return self.action(server_id, 'os-resetState', **kwargs)
+
+ def shelve_server(self, server_id, **kwargs):
+ """Shelve the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#shelve
+ """
+ return self.action(server_id, 'shelve', **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelve the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#unshelve
+ """
+ return self.action(server_id, 'unshelve', **kwargs)
+
+ def shelve_offload_server(self, server_id, **kwargs):
+ """Shelve-offload the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#shelveOffload
+ """
+ return self.action(server_id, 'shelveOffload', **kwargs)
+
+ def get_console_output(self, server_id, **kwargs):
+ """Get console output.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#getConsoleOutput
+ """
+ return self.action(server_id, 'os-getConsoleOutput',
+ schema.get_console_output, **kwargs)
+
+ def list_virtual_interfaces(self, server_id):
+ """List the virtual interfaces used in an instance."""
+ resp, body = self.get('/'.join(['servers', server_id,
+ 'os-virtual-interfaces']))
+ body = json.loads(body)
+ self.validate_response(schema.list_virtual_interfaces, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def rescue_server(self, server_id, **kwargs):
+ """Rescue the provided server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#rescue
+ """
+ return self.action(server_id, 'rescue', schema.rescue_server, **kwargs)
+
+ def unrescue_server(self, server_id):
+ """Unrescue the provided server."""
+ return self.action(server_id, 'unrescue')
+
+ def show_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/diagnostics" % server_id)
+ return rest_client.ResponseBody(resp, json.loads(body))
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" %
+ server_id)
+ body = json.loads(body)
+ self.validate_response(schema.list_instance_actions, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (server_id, request_id))
+ body = json.loads(body)
+ self.validate_response(schema.show_instance_action, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#forceDelete
+ """
+ return self.action(server_id, 'forceDelete', **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#restore
+ """
+ return self.action(server_id, 'restore', **kwargs)
+
+ def reset_network(self, server_id, **kwargs):
+ """Reset the Network of a server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#resetNetwork
+ """
+ return self.action(server_id, 'resetNetwork', **kwargs)
+
+ def inject_network_info(self, server_id, **kwargs):
+ """Inject the Network Info into server.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#injectNetworkInfo
+ """
+ return self.action(server_id, 'injectNetworkInfo', **kwargs)
+
+ def get_vnc_console(self, server_id, **kwargs):
+ """Get URL of VNC console.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#getVNCConsole
+ """
+ return self.action(server_id, "os-getVNCConsole",
+ schema.get_vnc_console, **kwargs)
+
+ def add_fixed_ip(self, server_id, **kwargs):
+ """Add a fixed IP to server instance.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#addFixedIp
+ """
+ return self.action(server_id, 'addFixedIp', **kwargs)
+
+ def remove_fixed_ip(self, server_id, **kwargs):
+ """Remove input fixed IP from input server instance.
+
+ Available params: http://developer.openstack.org/
+ api-ref-compute-v2.1.html#removeFixedIp
+ """
+ return self.action(server_id, 'removeFixedIp', **kwargs)
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
new file mode 100644
index 0000000..06aad77
--- /dev/null
+++ b/tempest/lib/services/compute/services_client.py
@@ -0,0 +1,58 @@
+# Copyright 2013 NEC Corporation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import services as schema
+from tempest.lib.common import rest_client
+
+
+class ServicesClient(rest_client.RestClient):
+
+ def list_services(self, **params):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_services, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def enable_service(self, **kwargs):
+ """Enable service on a host.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#enableScheduling
+ """
+ post_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/enable', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.enable_disable_service, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def disable_service(self, **kwargs):
+ """Disable service on a host.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#disableScheduling
+ """
+ post_body = json.dumps(kwargs)
+ resp, body = self.put('os-services/disable', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.enable_disable_service, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/snapshots_client.py b/tempest/lib/services/compute/snapshots_client.py
new file mode 100644
index 0000000..de776bd
--- /dev/null
+++ b/tempest/lib/services/compute/snapshots_client.py
@@ -0,0 +1,76 @@
+# Copyright 2015 Fujitsu(fnst) 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import snapshots as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class SnapshotsClient(rest_client.RestClient):
+
+ def create_snapshot(self, volume_id, **kwargs):
+ """Create a snapshot.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createSnapshot
+ """
+ post_body = {
+ 'volume_id': volume_id
+ }
+ post_body.update(kwargs)
+ post_body = json.dumps({'snapshot': post_body})
+ resp, body = self.post('os-snapshots', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_snapshot, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_snapshot(self, snapshot_id):
+ url = "os-snapshots/%s" % snapshot_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_snapshot, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_snapshots(self, detail=False, params=None):
+ url = 'os-snapshots'
+
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_snapshots, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_snapshot(self, snapshot_id):
+ resp, body = self.delete("os-snapshots/%s" % snapshot_id)
+ self.validate_response(schema.delete_snapshot, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_snapshot(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'snapshot'
diff --git a/tempest/lib/services/compute/tenant_networks_client.py b/tempest/lib/services/compute/tenant_networks_client.py
new file mode 100644
index 0000000..44a97a9
--- /dev/null
+++ b/tempest/lib/services/compute/tenant_networks_client.py
@@ -0,0 +1,34 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.api_schema.response.compute.v2_1 import tenant_networks
+from tempest.lib.common import rest_client
+
+
+class TenantNetworksClient(rest_client.RestClient):
+
+ def list_tenant_networks(self):
+ resp, body = self.get("os-tenant-networks")
+ body = json.loads(body)
+ self.validate_response(tenant_networks.list_tenant_networks, resp,
+ body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_tenant_network(self, network_id):
+ resp, body = self.get("os-tenant-networks/%s" % network_id)
+ body = json.loads(body)
+ self.validate_response(tenant_networks.get_tenant_network, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/tenant_usages_client.py b/tempest/lib/services/compute/tenant_usages_client.py
new file mode 100644
index 0000000..e8da465
--- /dev/null
+++ b/tempest/lib/services/compute/tenant_usages_client.py
@@ -0,0 +1,43 @@
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import tenant_usages
+from tempest.lib.common import rest_client
+
+
+class TenantUsagesClient(rest_client.RestClient):
+
+ def list_tenant_usages(self, **params):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(tenant_usages.list_tenant_usage, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_tenant_usage(self, tenant_id, **params):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(tenant_usages.get_tenant_usage, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/versions_client.py b/tempest/lib/services/compute/versions_client.py
new file mode 100644
index 0000000..5898f93
--- /dev/null
+++ b/tempest/lib/services/compute/versions_client.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 import urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import versions as schema
+from tempest.lib.common import rest_client
+
+
+class VersionsClient(rest_client.RestClient):
+
+ def _get_base_version_url(self):
+ # NOTE: The URL which is gotten from keystone's catalog contains
+ # API version and project-id like "v2/{project-id}", but we need
+ # to access the URL which doesn't contain them for getting API
+ # versions. For that, here should use raw_request() instead of
+ # get().
+ endpoint = self.base_url
+ url = urllib.parse.urlparse(endpoint)
+ return '%s://%s/' % (url.scheme, url.netloc)
+
+ def list_versions(self):
+ version_url = self._get_base_version_url()
+ resp, body = self.raw_request(version_url, 'GET')
+ body = json.loads(body)
+ self.validate_response(schema.list_versions, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def get_version_by_url(self, version_url):
+ """Get the version document by url.
+
+ This gets the version document for a url, useful in testing
+ the contents of things like /v2/ or /v2.1/ in Nova. That
+ controller needs authenticated access, so we have to get
+ ourselves a token before making the request.
+
+ """
+ # we need a token for this request
+ resp, body = self.raw_request(version_url, 'GET',
+ {'X-Auth-Token': self.token})
+ body = json.loads(body)
+ self.validate_response(schema.get_one_version, resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/volumes_client.py b/tempest/lib/services/compute/volumes_client.py
new file mode 100644
index 0000000..45a44de
--- /dev/null
+++ b/tempest/lib/services/compute/volumes_client.py
@@ -0,0 +1,76 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.api_schema.response.compute.v2_1 import volumes as schema
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class VolumesClient(rest_client.RestClient):
+
+ def list_volumes(self, detail=False, **params):
+ """List all the volumes created."""
+ url = 'os-volumes'
+
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_volumes, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume(self, volume_id):
+ """Return the details of a single volume."""
+ url = "os-volumes/%s" % volume_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume(self, **kwargs):
+ """Create a new Volume.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#createVolume
+ """
+ post_body = json.dumps({'volume': kwargs})
+ resp, body = self.post('os-volumes', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume(self, volume_id):
+ """Delete the Specified Volume."""
+ resp, body = self.delete("os-volumes/%s" % volume_id)
+ self.validate_response(schema.delete_volume, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_volume(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Return the primary type of resource this client works with."""
+ return 'volume'
diff --git a/tempest/lib/services/identity/__init__.py b/tempest/lib/services/identity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/identity/__init__.py
diff --git a/tempest/lib/services/identity/v2/__init__.py b/tempest/lib/services/identity/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/identity/v2/__init__.py
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
new file mode 100644
index 0000000..0350175
--- /dev/null
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -0,0 +1,121 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+
+
+class TokenClient(rest_client.RestClient):
+
+ def __init__(self, auth_url, disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ dscv = disable_ssl_certificate_validation
+ super(TokenClient, self).__init__(
+ None, None, None, disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+
+ if auth_url is None:
+ raise exceptions.IdentityError("Couldn't determine auth_url")
+
+ # Normalize URI to ensure /tokens is in it.
+ if 'tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/tokens'
+
+ self.auth_url = auth_url
+
+ def auth(self, user, password, tenant=None):
+ creds = {
+ 'auth': {
+ 'passwordCredentials': {
+ 'username': user,
+ 'password': password,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds, sort_keys=True)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(200, resp.status)
+
+ return rest_client.ResponseBody(resp, body['access'])
+
+ def auth_token(self, token_id, tenant=None):
+ creds = {
+ 'auth': {
+ 'token': {
+ 'id': token_id,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(200, resp.status)
+
+ return rest_client.ResponseBody(resp, body['access'])
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None):
+ """A simple HTTP request interface."""
+ if headers is None:
+ headers = self.get_headers(accept_type="json")
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers(accept_type="json"))
+ except (ValueError, TypeError):
+ headers = self.get_headers(accept_type="json")
+
+ resp, resp_body = self.raw_request(url, method,
+ headers=headers, body=body)
+ self._log_request(method, url, resp, req_headers=headers,
+ req_body='<omitted>', resp_body=resp_body)
+
+ if resp.status in [401, 403]:
+ resp_body = json.loads(resp_body)
+ raise exceptions.Unauthorized(resp_body['error']['message'])
+ elif resp.status not in [200, 201]:
+ raise exceptions.IdentityError(
+ 'Unexpected status code {0}'.format(resp.status))
+
+ return resp, json.loads(resp_body)
+
+ def get_token(self, user, password, tenant, auth_data=False):
+ """Returns (token id, token data) for supplied credentials."""
+ body = self.auth(user, password, tenant)
+
+ if auth_data:
+ return body['token']['id'], body
+ else:
+ return body['token']['id']
+
+
+class TokenClientJSON(TokenClient):
+ LOG = logging.getLogger(__name__)
+
+ def _warn(self):
+ self.LOG.warning("%s class was deprecated and renamed to %s" %
+ (self.__class__.__name__, 'TokenClient'))
+
+ def __init__(self, *args, **kwargs):
+ self._warn()
+ super(TokenClientJSON, self).__init__(*args, **kwargs)
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/identity/v3/__init__.py
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
new file mode 100644
index 0000000..f342a49
--- /dev/null
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -0,0 +1,183 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+
+
+class V3TokenClient(rest_client.RestClient):
+
+ def __init__(self, auth_url, disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ dscv = disable_ssl_certificate_validation
+ super(V3TokenClient, self).__init__(
+ None, None, None, disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+
+ if auth_url is None:
+ raise exceptions.IdentityError("Couldn't determine auth_url")
+
+ if 'auth/tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/auth/tokens'
+
+ self.auth_url = auth_url
+
+ def auth(self, user_id=None, username=None, password=None, project_id=None,
+ project_name=None, user_domain_id=None, user_domain_name=None,
+ project_domain_id=None, project_domain_name=None, domain_id=None,
+ domain_name=None, token=None):
+ """Obtains a token from the authentication service
+
+ :param user_id: user id
+ :param username: user name
+ :param user_domain_id: the user domain id
+ :param user_domain_name: the user domain name
+ :param project_domain_id: the project domain id
+ :param project_domain_name: the project domain name
+ :param domain_id: a domain id to scope to
+ :param domain_name: a domain name to scope to
+ :param project_id: a project id to scope to
+ :param project_name: a project name to scope to
+ :param token: a token to re-scope.
+
+ Accepts different combinations of credentials.
+ Sample sample valid combinations:
+ - token
+ - token, project_name, project_domain_id
+ - user_id, password
+ - username, password, user_domain_id
+ - username, password, project_name, user_domain_id, project_domain_id
+ Validation is left to the server side.
+ """
+ creds = {
+ 'auth': {
+ 'identity': {
+ 'methods': [],
+ }
+ }
+ }
+ id_obj = creds['auth']['identity']
+ if token:
+ id_obj['methods'].append('token')
+ id_obj['token'] = {
+ 'id': token
+ }
+
+ if (user_id or username) and password:
+ id_obj['methods'].append('password')
+ id_obj['password'] = {
+ 'user': {
+ 'password': password,
+ }
+ }
+ if user_id:
+ id_obj['password']['user']['id'] = user_id
+ else:
+ id_obj['password']['user']['name'] = username
+
+ _domain = None
+ if user_domain_id is not None:
+ _domain = dict(id=user_domain_id)
+ elif user_domain_name is not None:
+ _domain = dict(name=user_domain_name)
+ if _domain:
+ id_obj['password']['user']['domain'] = _domain
+
+ if (project_id or project_name):
+ _project = dict()
+
+ if project_id:
+ _project['id'] = project_id
+ elif project_name:
+ _project['name'] = project_name
+
+ if project_domain_id is not None:
+ _project['domain'] = {'id': project_domain_id}
+ elif project_domain_name is not None:
+ _project['domain'] = {'name': project_domain_name}
+
+ creds['auth']['scope'] = dict(project=_project)
+ elif domain_id:
+ creds['auth']['scope'] = dict(domain={'id': domain_id})
+ elif domain_name:
+ creds['auth']['scope'] = dict(domain={'name': domain_name})
+
+ body = json.dumps(creds, sort_keys=True)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(201, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None):
+ """A simple HTTP request interface."""
+ if headers is None:
+ # Always accept 'json', for xml token client too.
+ # Because XML response is not easily
+ # converted to the corresponding JSON one
+ headers = self.get_headers(accept_type="json")
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers(accept_type="json"))
+ except (ValueError, TypeError):
+ headers = self.get_headers(accept_type="json")
+
+ resp, resp_body = self.raw_request(url, method,
+ headers=headers, body=body)
+ self._log_request(method, url, resp, req_headers=headers,
+ req_body='<omitted>', resp_body=resp_body)
+
+ if resp.status in [401, 403]:
+ resp_body = json.loads(resp_body)
+ raise exceptions.Unauthorized(resp_body['error']['message'])
+ elif resp.status not in [200, 201, 204]:
+ raise exceptions.IdentityError(
+ 'Unexpected status code {0}'.format(resp.status))
+
+ return resp, json.loads(resp_body)
+
+ def get_token(self, **kwargs):
+ """Returns (token id, token data) for supplied credentials"""
+
+ auth_data = kwargs.pop('auth_data', False)
+
+ if not (kwargs.get('user_domain_id') or
+ kwargs.get('user_domain_name')):
+ kwargs['user_domain_name'] = 'Default'
+
+ if not (kwargs.get('project_domain_id') or
+ kwargs.get('project_domain_name')):
+ kwargs['project_domain_name'] = 'Default'
+
+ body = self.auth(**kwargs)
+
+ token = body.response.get('x-subject-token')
+ if auth_data:
+ return token, body['token']
+ else:
+ return token
+
+
+class V3TokenClientJSON(V3TokenClient):
+ LOG = logging.getLogger(__name__)
+
+ def _warn(self):
+ self.LOG.warning("%s class was deprecated and renamed to %s" %
+ (self.__class__.__name__, 'V3TokenClient'))
+
+ def __init__(self, *args, **kwargs):
+ self._warn()
+ super(V3TokenClientJSON, self).__init__(*args, **kwargs)
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/network/__init__.py
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
new file mode 100644
index 0000000..c5d4c66
--- /dev/null
+++ b/tempest/lib/services/network/agents_client.py
@@ -0,0 +1,68 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import base
+
+
+class AgentsClient(base.BaseNetworkClient):
+
+ def update_agent(self, agent_id, **kwargs):
+ """Update agent."""
+ # 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/1526673
+ uri = '/agents/%s' % agent_id
+ return self.update_resource(uri, kwargs)
+
+ def show_agent(self, agent_id, **fields):
+ uri = '/agents/%s' % agent_id
+ return self.show_resource(uri, **fields)
+
+ def list_agents(self, **filters):
+ uri = '/agents'
+ return self.list_resources(uri, **filters)
+
+ def list_routers_on_l3_agent(self, agent_id):
+ uri = '/agents/%s/l3-routers' % agent_id
+ return self.list_resources(uri)
+
+ def create_router_on_l3_agent(self, agent_id, **kwargs):
+ # TODO(piyush): Current api-site doesn't contain this API description.
+ # After fixing the api-site, we need to fix here also for putting the
+ # link to api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526670
+ uri = '/agents/%s/l3-routers' % agent_id
+ return self.create_resource(uri, kwargs)
+
+ def delete_router_from_l3_agent(self, agent_id, router_id):
+ uri = '/agents/%s/l3-routers/%s' % (agent_id, router_id)
+ return self.delete_resource(uri)
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '/agents/%s/dhcp-networks' % agent_id
+ return self.list_resources(uri)
+
+ def delete_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '/agents/%s/dhcp-networks/%s' % (agent_id,
+ network_id)
+ return self.delete_resource(uri)
+
+ def add_dhcp_agent_to_network(self, agent_id, **kwargs):
+ # TODO(piyush): Current api-site doesn't contain this API description.
+ # After fixing the api-site, we need to fix here also for putting the
+ # link to api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
+ uri = '/agents/%s/dhcp-networks' % agent_id
+ return self.create_resource(uri, kwargs)
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
new file mode 100644
index 0000000..a6ada04
--- /dev/null
+++ b/tempest/lib/services/network/base.py
@@ -0,0 +1,71 @@
+# 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 BaseNetworkClient(rest_client.RestClient):
+
+ """Base class for Tempest REST clients for Neutron.
+
+ Child classes use v2 of the Neutron API, since the V1 API has been
+ removed from the code base.
+ """
+
+ version = '2.0'
+ uri_prefix = "v2.0"
+
+ def list_resources(self, uri, **filters):
+ req_uri = self.uri_prefix + uri
+ if filters:
+ req_uri += '?' + urllib.urlencode(filters, doseq=1)
+ resp, body = self.get(req_uri)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_resource(self, uri):
+ req_uri = self.uri_prefix + uri
+ resp, body = self.delete(req_uri)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_resource(self, uri, **fields):
+ # fields is a dict which key is 'fields' and value is a
+ # list of field's name. An example:
+ # {'fields': ['id', 'name']}
+ req_uri = self.uri_prefix + uri
+ if fields:
+ req_uri += '?' + urllib.urlencode(fields, doseq=1)
+ resp, body = self.get(req_uri)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_resource(self, uri, post_data):
+ req_uri = self.uri_prefix + uri
+ req_post_data = json.dumps(post_data)
+ resp, body = self.post(req_uri, req_post_data)
+ body = json.loads(body)
+ self.expected_success(201, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_resource(self, uri, post_data):
+ req_uri = self.uri_prefix + uri
+ req_post_data = json.dumps(post_data)
+ resp, body = self.put(req_uri, req_post_data)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/extensions_client.py b/tempest/lib/services/network/extensions_client.py
new file mode 100644
index 0000000..3910c84
--- /dev/null
+++ b/tempest/lib/services/network/extensions_client.py
@@ -0,0 +1,24 @@
+# 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.network import base
+
+
+class ExtensionsClient(base.BaseNetworkClient):
+
+ def show_extension(self, ext_alias, **fields):
+ uri = '/extensions/%s' % ext_alias
+ return self.show_resource(uri, **fields)
+
+ def list_extensions(self, **filters):
+ uri = '/extensions'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/floating_ips_client.py b/tempest/lib/services/network/floating_ips_client.py
new file mode 100644
index 0000000..1968e05
--- /dev/null
+++ b/tempest/lib/services/network/floating_ips_client.py
@@ -0,0 +1,38 @@
+# 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.network import base
+
+
+class FloatingIPsClient(base.BaseNetworkClient):
+
+ def create_floatingip(self, **kwargs):
+ uri = '/floatingips'
+ post_data = {'floatingip': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_floatingip(self, floatingip_id, **kwargs):
+ uri = '/floatingips/%s' % floatingip_id
+ post_data = {'floatingip': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_floatingip(self, floatingip_id, **fields):
+ uri = '/floatingips/%s' % floatingip_id
+ return self.show_resource(uri, **fields)
+
+ def delete_floatingip(self, floatingip_id):
+ uri = '/floatingips/%s' % floatingip_id
+ return self.delete_resource(uri)
+
+ def list_floatingips(self, **filters):
+ uri = '/floatingips'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/metering_label_rules_client.py b/tempest/lib/services/network/metering_label_rules_client.py
new file mode 100644
index 0000000..36cf8e3
--- /dev/null
+++ b/tempest/lib/services/network/metering_label_rules_client.py
@@ -0,0 +1,33 @@
+# 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.network import base
+
+
+class MeteringLabelRulesClient(base.BaseNetworkClient):
+
+ def create_metering_label_rule(self, **kwargs):
+ uri = '/metering/metering-label-rules'
+ post_data = {'metering_label_rule': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_metering_label_rule(self, metering_label_rule_id, **fields):
+ uri = '/metering/metering-label-rules/%s' % metering_label_rule_id
+ return self.show_resource(uri, **fields)
+
+ def delete_metering_label_rule(self, metering_label_rule_id):
+ uri = '/metering/metering-label-rules/%s' % metering_label_rule_id
+ return self.delete_resource(uri)
+
+ def list_metering_label_rules(self, **filters):
+ uri = '/metering/metering-label-rules'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/metering_labels_client.py b/tempest/lib/services/network/metering_labels_client.py
new file mode 100644
index 0000000..2350ecd
--- /dev/null
+++ b/tempest/lib/services/network/metering_labels_client.py
@@ -0,0 +1,33 @@
+# 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.network import base
+
+
+class MeteringLabelsClient(base.BaseNetworkClient):
+
+ def create_metering_label(self, **kwargs):
+ uri = '/metering/metering-labels'
+ post_data = {'metering_label': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_metering_label(self, metering_label_id, **fields):
+ uri = '/metering/metering-labels/%s' % metering_label_id
+ return self.show_resource(uri, **fields)
+
+ def delete_metering_label(self, metering_label_id):
+ uri = '/metering/metering-labels/%s' % metering_label_id
+ return self.delete_resource(uri)
+
+ def list_metering_labels(self, **filters):
+ uri = '/metering/metering-labels'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
new file mode 100644
index 0000000..0926634
--- /dev/null
+++ b/tempest/lib/services/network/networks_client.py
@@ -0,0 +1,47 @@
+# 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.network import base
+
+
+class NetworksClient(base.BaseNetworkClient):
+
+ def create_network(self, **kwargs):
+ uri = '/networks'
+ post_data = {'network': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_network(self, network_id, **kwargs):
+ uri = '/networks/%s' % network_id
+ post_data = {'network': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_network(self, network_id, **fields):
+ uri = '/networks/%s' % network_id
+ return self.show_resource(uri, **fields)
+
+ def delete_network(self, network_id):
+ uri = '/networks/%s' % network_id
+ return self.delete_resource(uri)
+
+ def list_networks(self, **filters):
+ uri = '/networks'
+ return self.list_resources(uri, **filters)
+
+ def create_bulk_networks(self, **kwargs):
+ """Create multiple networks in a single request.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#bulkCreateNetwork
+ """
+ uri = '/networks'
+ return self.create_resource(uri, kwargs)
diff --git a/tempest/lib/services/network/ports_client.py b/tempest/lib/services/network/ports_client.py
new file mode 100644
index 0000000..1793d6a
--- /dev/null
+++ b/tempest/lib/services/network/ports_client.py
@@ -0,0 +1,47 @@
+# 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.network import base
+
+
+class PortsClient(base.BaseNetworkClient):
+
+ def create_port(self, **kwargs):
+ uri = '/ports'
+ post_data = {'port': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_port(self, port_id, **kwargs):
+ uri = '/ports/%s' % port_id
+ post_data = {'port': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_port(self, port_id, **fields):
+ uri = '/ports/%s' % port_id
+ return self.show_resource(uri, **fields)
+
+ def delete_port(self, port_id):
+ uri = '/ports/%s' % port_id
+ return self.delete_resource(uri)
+
+ def list_ports(self, **filters):
+ uri = '/ports'
+ return self.list_resources(uri, **filters)
+
+ def create_bulk_ports(self, **kwargs):
+ """Create multiple ports in a single request.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#bulkCreatePorts
+ """
+ uri = '/ports'
+ return self.create_resource(uri, kwargs)
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
new file mode 100644
index 0000000..b5cf35b
--- /dev/null
+++ b/tempest/lib/services/network/quotas_client.py
@@ -0,0 +1,35 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import base
+
+
+class QuotasClient(base.BaseNetworkClient):
+
+ def update_quotas(self, tenant_id, **kwargs):
+ put_body = {'quota': kwargs}
+ uri = '/quotas/%s' % tenant_id
+ return self.update_resource(uri, put_body)
+
+ def reset_quotas(self, tenant_id):
+ uri = '/quotas/%s' % tenant_id
+ return self.delete_resource(uri)
+
+ def show_quotas(self, tenant_id, **fields):
+ uri = '/quotas/%s' % tenant_id
+ return self.show_resource(uri, **fields)
+
+ def list_quotas(self, **filters):
+ uri = '/quotas'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/security_group_rules_client.py b/tempest/lib/services/network/security_group_rules_client.py
new file mode 100644
index 0000000..944eba6
--- /dev/null
+++ b/tempest/lib/services/network/security_group_rules_client.py
@@ -0,0 +1,33 @@
+# 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.network import base
+
+
+class SecurityGroupRulesClient(base.BaseNetworkClient):
+
+ def create_security_group_rule(self, **kwargs):
+ uri = '/security-group-rules'
+ post_data = {'security_group_rule': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_security_group_rule(self, security_group_rule_id, **fields):
+ uri = '/security-group-rules/%s' % security_group_rule_id
+ return self.show_resource(uri, **fields)
+
+ def delete_security_group_rule(self, security_group_rule_id):
+ uri = '/security-group-rules/%s' % security_group_rule_id
+ return self.delete_resource(uri)
+
+ def list_security_group_rules(self, **filters):
+ uri = '/security-group-rules'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/security_groups_client.py b/tempest/lib/services/network/security_groups_client.py
new file mode 100644
index 0000000..0e25339
--- /dev/null
+++ b/tempest/lib/services/network/security_groups_client.py
@@ -0,0 +1,38 @@
+# 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.network import base
+
+
+class SecurityGroupsClient(base.BaseNetworkClient):
+
+ def create_security_group(self, **kwargs):
+ uri = '/security-groups'
+ post_data = {'security_group': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_security_group(self, security_group_id, **kwargs):
+ uri = '/security-groups/%s' % security_group_id
+ post_data = {'security_group': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_security_group(self, security_group_id, **fields):
+ uri = '/security-groups/%s' % security_group_id
+ return self.show_resource(uri, **fields)
+
+ def delete_security_group(self, security_group_id):
+ uri = '/security-groups/%s' % security_group_id
+ return self.delete_resource(uri)
+
+ def list_security_groups(self, **filters):
+ uri = '/security-groups'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
new file mode 100644
index 0000000..12349b1
--- /dev/null
+++ b/tempest/lib/services/network/subnetpools_client.py
@@ -0,0 +1,40 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import base
+
+
+class SubnetpoolsClient(base.BaseNetworkClient):
+
+ def list_subnetpools(self, **filters):
+ uri = '/subnetpools'
+ return self.list_resources(uri, **filters)
+
+ def create_subnetpool(self, **kwargs):
+ uri = '/subnetpools'
+ post_data = {'subnetpool': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_subnetpool(self, subnetpool_id, **fields):
+ uri = '/subnetpools/%s' % subnetpool_id
+ return self.show_resource(uri, **fields)
+
+ def update_subnetpool(self, subnetpool_id, **kwargs):
+ uri = '/subnetpools/%s' % subnetpool_id
+ post_data = {'subnetpool': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def delete_subnetpool(self, subnetpool_id):
+ uri = '/subnetpools/%s' % subnetpool_id
+ return self.delete_resource(uri)
diff --git a/tempest/lib/services/network/subnets_client.py b/tempest/lib/services/network/subnets_client.py
new file mode 100644
index 0000000..63ed13e
--- /dev/null
+++ b/tempest/lib/services/network/subnets_client.py
@@ -0,0 +1,47 @@
+# 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.network import base
+
+
+class SubnetsClient(base.BaseNetworkClient):
+
+ def create_subnet(self, **kwargs):
+ uri = '/subnets'
+ post_data = {'subnet': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def update_subnet(self, subnet_id, **kwargs):
+ uri = '/subnets/%s' % subnet_id
+ post_data = {'subnet': kwargs}
+ return self.update_resource(uri, post_data)
+
+ def show_subnet(self, subnet_id, **fields):
+ uri = '/subnets/%s' % subnet_id
+ return self.show_resource(uri, **fields)
+
+ def delete_subnet(self, subnet_id):
+ uri = '/subnets/%s' % subnet_id
+ return self.delete_resource(uri)
+
+ def list_subnets(self, **filters):
+ uri = '/subnets'
+ return self.list_resources(uri, **filters)
+
+ def create_bulk_subnets(self, **kwargs):
+ """Create multiple subnets in a single request.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2.html#bulkCreateSubnet
+ """
+ uri = '/subnets'
+ return self.create_resource(uri, kwargs)
diff --git a/tempest/manager.py b/tempest/manager.py
index 6e86c78..c97e0d1 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -13,11 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import auth
-
from tempest.common import cred_provider
from tempest import config
from tempest import exceptions
+from tempest.lib import auth
CONF = config.CONF
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a996ffe..eb29176 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -20,8 +20,6 @@
from oslo_log import log
from oslo_serialization import jsonutils as json
import six
-from tempest_lib.common.utils import misc as misc_utils
-from tempest_lib import exceptions as lib_exc
from tempest.common import compute
from tempest.common.utils import data_utils
@@ -29,6 +27,8 @@
from tempest.common import waiters
from tempest import config
from tempest import exceptions
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions as lib_exc
from tempest.services.network import resources as net_resources
import tempest.test
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index 402077f..9ed831c 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -13,12 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions as lib_exc
-
from tempest.common import fixed_network
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
from tempest import test
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 1d09fe7..6121a90 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -16,13 +16,13 @@
import time
from oslo_log import log as logging
-from tempest_lib import decorators
-from tempest_lib import exceptions as lib_exc
import testtools
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
from tempest import test
diff --git a/tempest/scenario/utils.py b/tempest/scenario/utils.py
index 3cbb3bc..75fd000 100644
--- a/tempest/scenario/utils.py
+++ b/tempest/scenario/utils.py
@@ -18,14 +18,14 @@
import unicodedata
from oslo_serialization import jsonutils as json
-from tempest_lib.common.utils import misc
-from tempest_lib import exceptions as exc_lib
import testscenarios
import testtools
from tempest import clients
from tempest.common import credentials_factory as credentials
from tempest import config
+from tempest.lib.common.utils import misc
+from tempest.lib import exceptions as exc_lib
CONF = config.CONF
diff --git a/tempest/services/base_microversion_client.py b/tempest/services/base_microversion_client.py
deleted file mode 100644
index 4c750f5..0000000
--- a/tempest/services/base_microversion_client.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2016 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest_lib.common import rest_client
-
-
-class BaseMicroversionClient(rest_client.RestClient):
- """Base class to support microversion in service clients
-
- This class is used to support microversion in service clients.
- This provides feature to make API request with microversion.
- Service clients derived from this class will be able to send API
- request to server with or without microversion.
- If api_microversion is not set on service client then API request will be
- normal request without microversion.
-
- """
- def __init__(self, auth_provider, service, region,
- api_microversion_header_name, **kwargs):
- """Base Microversion Client __init__
-
- :param auth_provider: an auth provider object used to wrap requests in
- auth
- :param str service: The service name to use for the catalog lookup
- :param str region: The region to use for the catalog lookup
- :param str api_microversion_header_name: The microversion header name
- to use for sending API
- request with microversion
- :param kwargs: kwargs required by rest_client.RestClient
- """
- super(BaseMicroversionClient, self).__init__(
- auth_provider, service, region, **kwargs)
- self.api_microversion_header_name = api_microversion_header_name
- self.api_microversion = None
-
- def get_headers(self):
- headers = super(BaseMicroversionClient, self).get_headers()
- if self.api_microversion:
- headers[self.api_microversion_header_name] = self.api_microversion
- return headers
-
- def set_api_microversion(self, microversion):
- self.api_microversion = microversion
diff --git a/tempest/services/compute/json/base_compute_client.py b/tempest/services/compute/json/base_compute_client.py
index 5349af6..8cfde4b 100644
--- a/tempest/services/compute/json/base_compute_client.py
+++ b/tempest/services/compute/json/base_compute_client.py
@@ -11,30 +11,38 @@
# 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 rest_client
from tempest.common import api_version_request
from tempest.common import api_version_utils
from tempest import exceptions
-from tempest.services import base_microversion_client
+
+COMPUTE_MICROVERSION = None
-class BaseComputeClient(base_microversion_client.BaseMicroversionClient):
+class BaseComputeClient(rest_client.RestClient):
+ api_microversion_header_name = 'X-OpenStack-Nova-API-Version'
def __init__(self, auth_provider, service, region,
- api_microversion_header_name='X-OpenStack-Nova-API-Version',
**kwargs):
super(BaseComputeClient, self).__init__(
- auth_provider, service, region,
- api_microversion_header_name, **kwargs)
+ auth_provider, service, region, **kwargs)
+
+ def get_headers(self):
+ headers = super(BaseComputeClient, self).get_headers()
+ if COMPUTE_MICROVERSION:
+ headers[self.api_microversion_header_name] = COMPUTE_MICROVERSION
+ return headers
def request(self, method, url, extra_headers=False, headers=None,
body=None):
resp, resp_body = super(BaseComputeClient, self).request(
method, url, extra_headers, headers, body)
- if self.api_microversion and self.api_microversion != 'latest':
+ if (COMPUTE_MICROVERSION and
+ COMPUTE_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
api_version_utils.assert_version_header_matches_request(
self.api_microversion_header_name,
- self.api_microversion,
+ COMPUTE_MICROVERSION,
resp)
return resp, resp_body
@@ -52,7 +60,7 @@
{'min': '2.10', 'max': None, 'schema': schemav210}]
"""
schema = None
- version = api_version_request.APIVersionRequest(self.api_microversion)
+ version = api_version_request.APIVersionRequest(COMPUTE_MICROVERSION)
for items in schema_versions_info:
min_version = api_version_request.APIVersionRequest(items['min'])
max_version = api_version_request.APIVersionRequest(items['max'])
diff --git a/tempest/services/identity/v3/json/domains_client.py b/tempest/services/identity/v3/json/domains_client.py
new file mode 100644
index 0000000..626a474
--- /dev/null
+++ b/tempest/services/identity/v3/json/domains_client.py
@@ -0,0 +1,77 @@
+# Copyright 2016 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.common import service_client
+
+
+class DomainsClient(service_client.ServiceClient):
+ api_version = "v3"
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.post('domains', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_domain(self, domain_id):
+ """Deletes a domain."""
+ resp, body = self.delete('domains/%s' % str(domain_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_domains(self, params=None):
+ """List Domains."""
+ url = 'domains'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ body = self.show_domain(domain_id)['domain']
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.patch('domains/%s' % domain_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def show_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 809d0b5..e4daf7a 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -14,7 +14,6 @@
# under the License.
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
from tempest.common import service_client
@@ -74,61 +73,6 @@
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body)
- def create_domain(self, name, **kwargs):
- """Creates a domain."""
- description = kwargs.get('description', None)
- en = kwargs.get('enabled', True)
- post_body = {
- 'description': description,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'domain': post_body})
- resp, body = self.post('domains', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_domain(self, domain_id):
- """Delete a domain."""
- resp, body = self.delete('domains/%s' % str(domain_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_domains(self, params=None):
- """List Domains."""
- url = 'domains'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_domain(self, domain_id, **kwargs):
- """Updates a domain."""
- body = self.show_domain(domain_id)['domain']
- description = kwargs.get('description', body['description'])
- en = kwargs.get('enabled', body['enabled'])
- name = kwargs.get('name', body['name'])
- post_body = {
- 'description': description,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'domain': post_body})
- resp, body = self.patch('domains/%s' % domain_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_domain(self, domain_id):
- """Get Domain details."""
- resp, body = self.get('domains/%s' % domain_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
def show_token(self, resp_token):
"""Get token details."""
headers = {'X-Subject-Token': resp_token}
@@ -263,64 +207,3 @@
(domain_id, group_id, role_id))
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp)
-
- def create_trust(self, **kwargs):
- """Creates a trust.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3-ext.html#createTrust
- """
- post_body = json.dumps({'trust': kwargs})
- resp, body = self.post('OS-TRUST/trusts', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_trust(self, trust_id):
- """Deletes a trust."""
- resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_trusts(self, trustor_user_id=None, trustee_user_id=None):
- """GET trusts."""
- if trustor_user_id:
- resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
- % trustor_user_id)
- elif trustee_user_id:
- resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
- % trustee_user_id)
- else:
- resp, body = self.get("OS-TRUST/trusts")
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_trust(self, trust_id):
- """GET trust."""
- resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_trust_roles(self, trust_id):
- """GET roles delegated by a trust."""
- resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_trust_role(self, trust_id, role_id):
- """GET role delegated by a trust."""
- resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
- % (trust_id, role_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def check_trust_role(self, trust_id, role_id):
- """HEAD Check if role is delegated by a trust."""
- resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
- % (trust_id, role_id))
- self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/trusts_client.py b/tempest/services/identity/v3/json/trusts_client.py
new file mode 100644
index 0000000..42b2bdb
--- /dev/null
+++ b/tempest/services/identity/v3/json/trusts_client.py
@@ -0,0 +1,82 @@
+# Copyright 2016 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 tempest.common import service_client
+
+
+class TrustsClient(service_client.ServiceClient):
+ api_version = "v3"
+
+ def create_trust(self, **kwargs):
+ """Creates a trust.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3-ext.html#createTrust
+ """
+ post_body = json.dumps({'trust': kwargs})
+ resp, body = self.post('OS-TRUST/trusts', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_trust(self, trust_id):
+ """Deletes a trust."""
+ resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_trusts(self, trustor_user_id=None, trustee_user_id=None):
+ """GET trusts."""
+ if trustor_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
+ % trustor_user_id)
+ elif trustee_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
+ % trustee_user_id)
+ else:
+ resp, body = self.get("OS-TRUST/trusts")
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def show_trust(self, trust_id):
+ """GET trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_trust_roles(self, trust_id):
+ """GET roles delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def show_trust_role(self, trust_id, role_id):
+ """GET role delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def check_trust_role(self, trust_id, role_id):
+ """HEAD Check if role is delegated by a trust."""
+ resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index af2e68c..b581203 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -22,12 +22,12 @@
from oslo_serialization import jsonutils as json
import six
from six.moves.urllib import parse as urllib
-from tempest_lib.common.utils import misc as misc_utils
-from tempest_lib import exceptions as lib_exc
from tempest.common import glance_http
from tempest.common import service_client
from tempest import exceptions
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index 72b203a..b4744e4 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -15,10 +15,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.common import glance_http
from tempest.common import service_client
+from tempest.lib import exceptions as lib_exc
class ImagesClientV2(service_client.ServiceClient):
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index c6b22df..5106b73 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -12,10 +12,9 @@
import time
-from tempest_lib.common.utils import misc
-from tempest_lib import exceptions as lib_exc
-
from tempest import exceptions
+from tempest.lib.common.utils import misc
+from tempest.lib import exceptions as lib_exc
from tempest.services.network.json import base
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 22e53f5..c0f5309 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -18,10 +18,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
class OrchestrationClient(service_client.ServiceClient):
diff --git a/tempest/services/volume/base/admin/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
index 867273e..f117f0a 100644
--- a/tempest/services/volume/base/admin/base_types_client.py
+++ b/tempest/services/volume/base/admin/base_types_client.py
@@ -15,9 +15,9 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
+from tempest.lib import exceptions as lib_exc
class BaseTypesClient(service_client.ServiceClient):
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/services/volume/base/base_backups_client.py
index fc9a40a..d6f2a50 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -16,10 +16,10 @@
import time
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
class BaseBackupsClient(service_client.ServiceClient):
diff --git a/tempest/services/volume/base/base_qos_client.py b/tempest/services/volume/base/base_qos_client.py
index 697e902..f68f9aa 100644
--- a/tempest/services/volume/base/base_qos_client.py
+++ b/tempest/services/volume/base/base_qos_client.py
@@ -15,10 +15,10 @@
import time
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
class BaseQosSpecsClient(service_client.ServiceClient):
diff --git a/tempest/services/volume/base/base_snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
index 1388e9c..ed2a642 100644
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -15,10 +15,10 @@
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
index d4435bc..5c0c7f7 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
import six
from six.moves.urllib import parse as urllib
-from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
from tempest.common import waiters
+from tempest.lib import exceptions as lib_exc
class BaseVolumesClient(service_client.ServiceClient):
diff --git a/tempest/stress/__init__.py b/tempest/stress/__init__.py
index e69de29..987a023 100644
--- a/tempest/stress/__init__.py
+++ b/tempest/stress/__init__.py
@@ -0,0 +1,22 @@
+# Copyright 2016 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import warnings
+
+warnings.simplefilter('once', category=DeprecationWarning)
+warnings.warn(
+ 'Stress tests are deprecated and will be removed from Tempest '
+ 'in the Newton release.',
+ DeprecationWarning)
+warnings.resetwarnings()
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 3c69a8b..468578d 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -21,7 +21,6 @@
from oslo_utils import importutils
import six
from six import moves
-from tempest_lib.common import ssh
from tempest import clients
@@ -30,6 +29,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib.common import ssh
from tempest.stress import cleanup
CONF = config.CONF
@@ -149,16 +149,18 @@
projects_client = admin_manager.tenants_client
roles_client = admin_manager.roles_client
users_client = admin_manager.users_client
+ domains_client = None
else:
identity_client = admin_manager.identity_v3_client
projects_client = admin_manager.projects_client
roles_client = None
users_client = admin_manager.users_v3_client
+ domains_client = admin_manager.domains_client
domain = (identity_client.auth_provider.credentials.
get('project_domain_name', 'Default'))
credentials_client = cred_client.get_creds_client(
identity_client, projects_client, users_client,
- roles_client, project_domain_name=domain)
+ roles_client, domains_client, project_domain_name=domain)
project = credentials_client.create_project(
name=tenant_name, description=tenant_name)
user = credentials_client.create_user(username, password,
diff --git a/tempest/test.py b/tempest/test.py
index ee3288c..45b818f 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -27,7 +27,6 @@
from oslo_utils import importutils
import six
from six.moves import urllib
-from tempest_lib import decorators
import testscenarios
import testtools
@@ -39,6 +38,7 @@
import tempest.common.validation_resources as vresources
from tempest import config
from tempest import exceptions
+from tempest.lib import decorators
LOG = logging.getLogger(__name__)
@@ -226,7 +226,6 @@
# Resources required to validate a server using ssh
validation_resources = {}
network_resources = {}
- services_microversion = {}
# NOTE(sdague): log_format is defined inline here instead of using the oslo
# default because going through the config path recouples config to the
@@ -443,11 +442,13 @@
users_client = self.os_admin.users_client
project_client = self.os_admin.tenants_client
roles_client = self.os_admin.roles_client
+ domains_client = None
else:
client = self.os_admin.identity_v3_client
project_client = self.os_adm.projects_client
users_client = self.os_admin.users_v3_client
roles_client = None
+ domains_client = self.os_admin.domains_client
try:
domain = client.auth_provider.credentials.project_domain_name
@@ -457,6 +458,7 @@
return cred_client.get_creds_client(client, project_client,
users_client,
roles_client,
+ domains_client,
project_domain_name=domain)
@classmethod
@@ -522,8 +524,7 @@
else:
raise exceptions.InvalidCredentials(
"Invalid credentials type %s" % credential_type)
- return cls.client_manager(credentials=creds, service=cls._service,
- api_microversions=cls.services_microversion)
+ return cls.client_manager(credentials=creds, service=cls._service)
@classmethod
def clear_credentials(cls):
@@ -610,8 +611,7 @@
credentials.is_admin_available(
identity_version=cls.get_identity_version())):
admin_creds = cred_provider.get_admin_creds()
- admin_manager = clients.Manager(
- admin_creds, api_microversions=cls.services_microversion)
+ admin_manager = clients.Manager(admin_creds)
networks_client = admin_manager.compute_networks_client
return fixed_network.get_tenant_network(
cred_provider, networks_client, CONF.compute.fixed_network_name)
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index 108b50d..d604b28 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -17,8 +17,8 @@
import six
import stevedore
-from tempest_lib.common.utils import misc
+from tempest.lib.common.utils import misc
LOG = logging.getLogger(__name__)
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
index ab6a7a0..3380f84 100644
--- a/tempest/tests/cmd/test_javelin.py
+++ b/tempest/tests/cmd/test_javelin.py
@@ -14,9 +14,9 @@
import mock
from oslotest import mockpatch
-from tempest_lib import exceptions as lib_exc
from tempest.cmd import javelin
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
diff --git a/tempest/tests/common/test_configured_creds.py b/tempest/tests/common/test_configured_creds.py
index 96b75fd..be24595 100644
--- a/tempest/tests/common/test_configured_creds.py
+++ b/tempest/tests/common/test_configured_creds.py
@@ -13,14 +13,14 @@
# under the License.
from oslo_config import cfg
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
-from tempest_lib.services.identity.v2 import token_client as v2_client
-from tempest_lib.services.identity.v3 import token_client as v3_client
from tempest.common import credentials_factory as common_creds
from tempest.common import tempest_fixtures as fixtures
from tempest import config
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.identity.v2 import token_client as v2_client
+from tempest.lib.services.identity.v3 import token_client as v3_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests import fake_identity
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index 7a21d96..059269e 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -15,13 +15,13 @@
import mock
from oslo_config import cfg
from oslotest import mockpatch
-from tempest_lib.services.identity.v2 import token_client as json_token_client
from tempest.common import credentials_factory as credentials
from tempest.common import dynamic_creds
from tempest.common import service_client
from tempest import config
from tempest import exceptions
+from tempest.lib.services.identity.v2 import token_client as json_token_client
from tempest.services.identity.v2.json import identity_client as \
json_iden_client
from tempest.services.identity.v2.json import roles_client as \
@@ -159,7 +159,7 @@
return_value={'router': {'id': id, 'name': name}}))
return router_fix
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_primary_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -174,7 +174,7 @@
self.assertEqual(primary_creds.tenant_id, '1234')
self.assertEqual(primary_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_admin_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -197,7 +197,7 @@
self.assertEqual(admin_creds.tenant_id, '1234')
self.assertEqual(admin_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_role_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -226,7 +226,7 @@
self.assertEqual(role_creds.tenant_id, '1234')
self.assertEqual(role_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_all_cred_cleanup(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -266,7 +266,7 @@
self.assertIn('12345', args)
self.assertIn('123456', args)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_alt_creds(self, MockRestClient):
cfg.CONF.set_default('neutron', False, 'service_available')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -281,7 +281,7 @@
self.assertEqual(alt_creds.tenant_id, '1234')
self.assertEqual(alt_creds.user_id, '1234')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_no_network_creation_with_config_set(self, MockRestClient):
cfg.CONF.set_default('create_isolated_networks', False, group='auth')
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
@@ -310,7 +310,7 @@
self.assertIsNone(subnet)
self.assertIsNone(router)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_creation(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -335,7 +335,7 @@
self.assertEqual(router['id'], '1234')
self.assertEqual(router['name'], 'fake_router')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_cleanup(self, MockRestClient):
def side_effect(**args):
return {"security_groups": [{"tenant_id": args['tenant_id'],
@@ -406,7 +406,7 @@
return_values = (fake_http.fake_httplib({}, status=204), {})
remove_secgroup_mock = self.patch(
- 'tempest_lib.services.network.security_groups_client.'
+ 'tempest.lib.services.network.security_groups_client.'
'SecurityGroupsClient.delete', return_value=return_values)
creds.clear_creds()
# Verify default security group delete
@@ -450,7 +450,7 @@
self.assertIn('12345', args)
self.assertIn('123456', args)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_alt_creation(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -475,7 +475,7 @@
self.assertEqual(router['id'], '1234')
self.assertEqual(router['name'], 'fake_alt_router')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_network_admin_creation(self, MockRestClient):
creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
self._mock_assign_user_role()
@@ -500,7 +500,7 @@
self.assertEqual(router['id'], '1234')
self.assertEqual(router['name'], 'fake_admin_router')
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_no_network_resources(self, MockRestClient):
net_dict = {
'network': False,
@@ -536,7 +536,7 @@
self.assertIsNone(subnet)
self.assertIsNone(router)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_router_without_network(self, MockRestClient):
net_dict = {
'network': False,
@@ -554,7 +554,7 @@
self.assertRaises(exceptions.InvalidConfiguration,
creds.get_primary_creds)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_subnet_without_network(self, MockRestClient):
net_dict = {
'network': False,
@@ -572,7 +572,7 @@
self.assertRaises(exceptions.InvalidConfiguration,
creds.get_primary_creds)
- @mock.patch('tempest_lib.common.rest_client.RestClient')
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
def test_dhcp_without_subnet(self, MockRestClient):
net_dict = {
'network': False,
diff --git a/tempest/tests/common/test_preprov_creds.py b/tempest/tests/common/test_preprov_creds.py
index fd7df16..7188e5f 100644
--- a/tempest/tests/common/test_preprov_creds.py
+++ b/tempest/tests/common/test_preprov_creds.py
@@ -21,13 +21,13 @@
from oslotest import mockpatch
import shutil
import six
-from tempest_lib import auth
-from tempest_lib import exceptions as lib_exc
-from tempest_lib.services.identity.v2 import token_client
from tempest.common import cred_provider
from tempest.common import preprov_creds
from tempest import config
+from tempest.lib import auth
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.identity.v2 import token_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests import fake_http
@@ -319,7 +319,7 @@
return_value=test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
- with mock.patch('tempest_lib.services.compute.networks_client.'
+ with mock.patch('tempest.lib.services.compute.networks_client.'
'NetworksClient.list_networks',
return_value={'networks': [{'name': 'network-2',
'id': 'fake-id',
diff --git a/tempest/tests/common/test_service_clients.py b/tempest/tests/common/test_service_clients.py
index 5a00a2b..7e9ff9d 100644
--- a/tempest/tests/common/test_service_clients.py
+++ b/tempest/tests/common/test_service_clients.py
@@ -78,7 +78,7 @@
class TestServiceClient(base.TestCase):
- @mock.patch('tempest_lib.common.rest_client.RestClient.__init__')
+ @mock.patch('tempest.lib.common.rest_client.RestClient.__init__')
def test_service_client_creations_with_specified_args(self, mock_init):
test_clients = [
baremetal_client.BaremetalClient,
diff --git a/tempest/tests/lib/__init__.py b/tempest/tests/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/__init__.py
diff --git a/tempest/tests/lib/base.py b/tempest/tests/lib/base.py
new file mode 100644
index 0000000..fe9268e
--- /dev/null
+++ b/tempest/tests/lib/base.py
@@ -0,0 +1,44 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# 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 mock
+from oslotest import base
+from oslotest import moxstubout
+
+
+class TestCase(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestCase, self).setUp()
+ mox_fixture = self.useFixture(moxstubout.MoxStubout())
+ self.mox = mox_fixture.mox
+ self.stubs = mox_fixture.stubs
+
+ def patch(self, target, **kwargs):
+ """Returns a started `mock.patch` object for the supplied target.
+
+ The caller may then call the returned patcher to create a mock object.
+
+ The caller does not need to call stop() on the returned
+ patcher object, as this method automatically adds a cleanup
+ to the test class to stop the patcher.
+
+ :param target: String module.class or module.object expression to patch
+ :param **kwargs: Passed as-is to `mock.patch`. See mock documentation
+ for details.
+ """
+ p = mock.patch(target, **kwargs)
+ m = p.start()
+ self.addCleanup(p.stop)
+ return m
diff --git a/tempest/tests/lib/cli/__init__.py b/tempest/tests/lib/cli/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/cli/__init__.py
diff --git a/tempest/tests/lib/cli/test_command_failed.py b/tempest/tests/lib/cli/test_command_failed.py
new file mode 100644
index 0000000..8ce34c2
--- /dev/null
+++ b/tempest/tests/lib/cli/test_command_failed.py
@@ -0,0 +1,30 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+
+
+class TestOutputParser(base.TestCase):
+
+ def test_command_failed_exception(self):
+ returncode = 1
+ cmd = "foo"
+ stdout = "output"
+ stderr = "error"
+ try:
+ raise exceptions.CommandFailed(returncode, cmd, stdout, stderr)
+ except exceptions.CommandFailed as e:
+ self.assertIn(str(returncode), str(e))
+ self.assertIn(cmd, str(e))
+ self.assertIn(stdout, str(e))
+ self.assertIn(stderr, str(e))
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
new file mode 100644
index 0000000..b5f7145
--- /dev/null
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -0,0 +1,37 @@
+#
+# 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.cli import base as cli_base
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+
+
+class TestExecute(base.TestCase):
+ def test_execute_success(self):
+ result = cli_base.execute("/bin/ls", action="tempest",
+ flags="-l -a")
+ self.assertIsInstance(result, str)
+ self.assertIn("__init__.py", result)
+
+ def test_execute_failure(self):
+ result = cli_base.execute("/bin/ls", action="tempest.lib",
+ flags="--foobar", merge_stderr=True,
+ fail_ok=True)
+ self.assertIsInstance(result, str)
+ self.assertIn("--foobar", result)
+
+ def test_execute_failure_raise_exception(self):
+ self.assertRaises(exceptions.CommandFailed, cli_base.execute,
+ "/bin/ls", action="tempest", flags="--foobar",
+ merge_stderr=True)
diff --git a/tempest/tests/lib/cli/test_output_parser.py b/tempest/tests/lib/cli/test_output_parser.py
new file mode 100644
index 0000000..a2c1b2d
--- /dev/null
+++ b/tempest/tests/lib/cli/test_output_parser.py
@@ -0,0 +1,177 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.lib.cli import output_parser
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+
+
+class TestOutputParser(base.TestCase):
+ OUTPUT_LINES = """
++----+------+---------+
+| ID | Name | Status |
++----+------+---------+
+| 11 | foo | BUILD |
+| 21 | bar | ERROR |
+| 31 | bee | None |
++----+------+---------+
+"""
+ OUTPUT_LINES2 = """
++----+-------+---------+
+| ID | Name2 | Status2 |
++----+-------+---------+
+| 41 | aaa | SSSSS |
+| 51 | bbb | TTTTT |
+| 61 | ccc | AAAAA |
++----+-------+---------+
+"""
+
+ EXPECTED_TABLE = {'headers': ['ID', 'Name', 'Status'],
+ 'values': [['11', 'foo', 'BUILD'],
+ ['21', 'bar', 'ERROR'],
+ ['31', 'bee', 'None']]}
+ EXPECTED_TABLE2 = {'headers': ['ID', 'Name2', 'Status2'],
+ 'values': [['41', 'aaa', 'SSSSS'],
+ ['51', 'bbb', 'TTTTT'],
+ ['61', 'ccc', 'AAAAA']]}
+
+ def test_table_with_normal_values(self):
+ actual = output_parser.table(self.OUTPUT_LINES)
+ self.assertIsInstance(actual, dict)
+ self.assertEqual(self.EXPECTED_TABLE, actual)
+
+ def test_table_with_list(self):
+ output_lines = self.OUTPUT_LINES.split('\n')
+ actual = output_parser.table(output_lines)
+ self.assertIsInstance(actual, dict)
+ self.assertEqual(self.EXPECTED_TABLE, actual)
+
+ def test_table_with_invalid_line(self):
+ output_lines = self.OUTPUT_LINES + "aaaa"
+ actual = output_parser.table(output_lines)
+ self.assertIsInstance(actual, dict)
+ self.assertEqual(self.EXPECTED_TABLE, actual)
+
+ def test_tables_with_normal_values(self):
+ output_lines = ('test' + self.OUTPUT_LINES +
+ 'test2' + self.OUTPUT_LINES2)
+ expected = [{'headers': self.EXPECTED_TABLE['headers'],
+ 'label': 'test',
+ 'values': self.EXPECTED_TABLE['values']},
+ {'headers': self.EXPECTED_TABLE2['headers'],
+ 'label': 'test2',
+ 'values': self.EXPECTED_TABLE2['values']}]
+ actual = output_parser.tables(output_lines)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_tables_with_invalid_values(self):
+ output_lines = ('test' + self.OUTPUT_LINES +
+ 'test2' + self.OUTPUT_LINES2 + '\n')
+ expected = [{'headers': self.EXPECTED_TABLE['headers'],
+ 'label': 'test',
+ 'values': self.EXPECTED_TABLE['values']},
+ {'headers': self.EXPECTED_TABLE2['headers'],
+ 'label': 'test2',
+ 'values': self.EXPECTED_TABLE2['values']}]
+ actual = output_parser.tables(output_lines)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_tables_with_invalid_line(self):
+ output_lines = ('test' + self.OUTPUT_LINES +
+ 'test2' + self.OUTPUT_LINES2 +
+ '+----+-------+---------+')
+ expected = [{'headers': self.EXPECTED_TABLE['headers'],
+ 'label': 'test',
+ 'values': self.EXPECTED_TABLE['values']},
+ {'headers': self.EXPECTED_TABLE2['headers'],
+ 'label': 'test2',
+ 'values': self.EXPECTED_TABLE2['values']}]
+
+ actual = output_parser.tables(output_lines)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ LISTING_OUTPUT = """
++----+
+| ID |
++----+
+| 11 |
+| 21 |
+| 31 |
++----+
+"""
+
+ def test_listing(self):
+ expected = [{'ID': '11'}, {'ID': '21'}, {'ID': '31'}]
+ actual = output_parser.listing(self.LISTING_OUTPUT)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_details_multiple_with_invalid_line(self):
+ self.assertRaises(exceptions.InvalidStructure,
+ output_parser.details_multiple,
+ self.OUTPUT_LINES)
+
+ DETAILS_LINES1 = """First Table
++----------+--------+
+| Property | Value |
++----------+--------+
+| foo | BUILD |
+| bar | ERROR |
+| bee | None |
++----------+--------+
+"""
+ DETAILS_LINES2 = """Second Table
++----------+--------+
+| Property | Value |
++----------+--------+
+| aaa | VVVVV |
+| bbb | WWWWW |
+| ccc | XXXXX |
++----------+--------+
+"""
+
+ def test_details_with_normal_line_label_false(self):
+ expected = {'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
+ actual = output_parser.details(self.DETAILS_LINES1)
+ self.assertEqual(expected, actual)
+
+ def test_details_with_normal_line_label_true(self):
+ expected = {'__label': 'First Table',
+ 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
+ actual = output_parser.details(self.DETAILS_LINES1, with_label=True)
+ self.assertEqual(expected, actual)
+
+ def test_details_multiple_with_normal_line_label_false(self):
+ expected = [{'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
+ {'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
+ actual = output_parser.details_multiple(self.DETAILS_LINES1 +
+ self.DETAILS_LINES2)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
+
+ def test_details_multiple_with_normal_line_label_true(self):
+ expected = [{'__label': 'First Table',
+ 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
+ {'__label': 'Second Table',
+ 'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
+ actual = output_parser.details_multiple(self.DETAILS_LINES1 +
+ self.DETAILS_LINES2,
+ with_label=True)
+ self.assertIsInstance(actual, list)
+ self.assertEqual(expected, actual)
diff --git a/tempest/tests/lib/common/__init__.py b/tempest/tests/lib/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/common/__init__.py
diff --git a/tempest/tests/lib/common/utils/__init__.py b/tempest/tests/lib/common/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/common/utils/__init__.py
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
new file mode 100644
index 0000000..07502d0
--- /dev/null
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -0,0 +1,162 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import netaddr
+
+from tempest.lib.common.utils import data_utils
+from tempest.tests.lib import base
+
+
+class TestDataUtils(base.TestCase):
+
+ def test_rand_uuid(self):
+ actual = data_utils.rand_uuid()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]"
+ "{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
+ actual2 = data_utils.rand_uuid()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_uuid_hex(self):
+ actual = data_utils.rand_uuid_hex()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^[0-9a-f]{32}$")
+
+ actual2 = data_utils.rand_uuid_hex()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_name(self):
+ actual = data_utils.rand_name()
+ self.assertIsInstance(actual, str)
+ actual2 = data_utils.rand_name()
+ self.assertNotEqual(actual, actual2)
+
+ actual = data_utils.rand_name('foo')
+ self.assertTrue(actual.startswith('foo'))
+ actual2 = data_utils.rand_name('foo')
+ self.assertTrue(actual.startswith('foo'))
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_name_with_prefix(self):
+ actual = data_utils.rand_name(prefix='prefix-str')
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^prefix-str-")
+ actual2 = data_utils.rand_name(prefix='prefix-str')
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_password(self):
+ actual = data_utils.rand_password()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "[A-Za-z0-9~!@#$%^&*_=+]{15,}")
+ actual2 = data_utils.rand_password()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_password_with_len(self):
+ actual = data_utils.rand_password(8)
+ self.assertIsInstance(actual, str)
+ self.assertEqual(len(actual), 8)
+ self.assertRegexpMatches(actual, "[A-Za-z0-9~!@#$%^&*_=+]{8}")
+ actual2 = data_utils.rand_password(8)
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_password_with_len_2(self):
+ actual = data_utils.rand_password(2)
+ self.assertIsInstance(actual, str)
+ self.assertEqual(len(actual), 3)
+ self.assertRegexpMatches(actual, "[A-Za-z0-9~!@#$%^&*_=+]{3}")
+ actual2 = data_utils.rand_password(2)
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_url(self):
+ actual = data_utils.rand_url()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^https://url-[0-9]*\.com$")
+ actual2 = data_utils.rand_url()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_int(self):
+ actual = data_utils.rand_int_id()
+ self.assertIsInstance(actual, int)
+
+ actual2 = data_utils.rand_int_id()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_mac_address(self):
+ actual = data_utils.rand_mac_address()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^([0-9a-f][0-9a-f]:){5}"
+ "[0-9a-f][0-9a-f]$")
+
+ actual2 = data_utils.rand_mac_address()
+ self.assertNotEqual(actual, actual2)
+
+ def test_parse_image_id(self):
+ actual = data_utils.parse_image_id("/foo/bar/deadbeaf")
+ self.assertEqual("deadbeaf", actual)
+
+ def test_arbitrary_string(self):
+ actual = data_utils.arbitrary_string()
+ self.assertEqual(actual, "test")
+ actual = data_utils.arbitrary_string(size=30, base_text="abc")
+ self.assertEqual(actual, "abc" * int(30 / len("abc")))
+ actual = data_utils.arbitrary_string(size=5, base_text="deadbeaf")
+ self.assertEqual(actual, "deadb")
+
+ def test_random_bytes(self):
+ actual = data_utils.random_bytes() # default size=1024
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^[\x00-\xFF]{1024}")
+ actual2 = data_utils.random_bytes()
+ self.assertNotEqual(actual, actual2)
+
+ actual = data_utils.random_bytes(size=2048)
+ self.assertRegexpMatches(actual, "^[\x00-\xFF]{2048}")
+
+ def test_get_ipv6_addr_by_EUI64(self):
+ actual = data_utils.get_ipv6_addr_by_EUI64('2001:db8::',
+ '00:16:3e:33:44:55')
+ self.assertIsInstance(actual, netaddr.IPAddress)
+ self.assertEqual(actual,
+ netaddr.IPAddress('2001:db8::216:3eff:fe33:4455'))
+
+ def test_get_ipv6_addr_by_EUI64_with_IPv4_prefix(self):
+ ipv4_prefix = '10.0.8'
+ mac = '00:16:3e:33:44:55'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ ipv4_prefix, mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_cidr_type(self):
+ bad_cidr = 123
+ mac = '00:16:3e:33:44:55'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ bad_cidr, mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_cidr_value(self):
+ bad_cidr = 'bb'
+ mac = '00:16:3e:33:44:55'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ bad_cidr, mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_mac_value(self):
+ cidr = '2001:db8::'
+ bad_mac = '00:16:3e:33:44:5Z'
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ cidr, bad_mac)
+
+ def test_get_ipv6_addr_by_EUI64_bad_mac_type(self):
+ cidr = '2001:db8::'
+ bad_mac = 99999999999999999999
+ self.assertRaises(TypeError, data_utils.get_ipv6_addr_by_EUI64,
+ cidr, bad_mac)
diff --git a/tempest/tests/lib/common/utils/test_misc.py b/tempest/tests/lib/common/utils/test_misc.py
new file mode 100644
index 0000000..e23d7fb
--- /dev/null
+++ b/tempest/tests/lib/common/utils/test_misc.py
@@ -0,0 +1,88 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.lib.common.utils import misc
+from tempest.tests.lib import base
+
+
+@misc.singleton
+class TestFoo(object):
+
+ count = 0
+
+ def increment(self):
+ self.count += 1
+ return self.count
+
+
+@misc.singleton
+class TestBar(object):
+
+ count = 0
+
+ def increment(self):
+ self.count += 1
+ return self.count
+
+
+class TestMisc(base.TestCase):
+
+ def test_singleton(self):
+ test = TestFoo()
+ self.assertEqual(0, test.count)
+ self.assertEqual(1, test.increment())
+ test2 = TestFoo()
+ self.assertEqual(1, test.count)
+ self.assertEqual(1, test2.count)
+ self.assertEqual(test, test2)
+ test3 = TestBar()
+ self.assertNotEqual(test, test3)
+
+ def test_find_test_caller_test_case(self):
+ # Calling it from here should give us the method we're in.
+ self.assertEqual('TestMisc:test_find_test_caller_test_case',
+ misc.find_test_caller())
+
+ def test_find_test_caller_setup_self(self):
+ def setUp(self):
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:setUp', setUp(self))
+
+ def test_find_test_caller_setup_no_self(self):
+ def setUp():
+ return misc.find_test_caller()
+ self.assertEqual(':setUp', setUp())
+
+ def test_find_test_caller_setupclass_cls(self):
+ def setUpClass(cls): # noqa
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:setUpClass', setUpClass(self.__class__))
+
+ def test_find_test_caller_teardown_self(self):
+ def tearDown(self):
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:tearDown', tearDown(self))
+
+ def test_find_test_caller_teardown_no_self(self):
+ def tearDown():
+ return misc.find_test_caller()
+ self.assertEqual(':tearDown', tearDown())
+
+ def test_find_test_caller_teardown_class(self):
+ def tearDownClass(cls): # noqa
+ return misc.find_test_caller()
+ self.assertEqual('TestMisc:tearDownClass',
+ tearDownClass(self.__class__))
diff --git a/tempest/tests/lib/fake_auth_provider.py b/tempest/tests/lib/fake_auth_provider.py
new file mode 100644
index 0000000..7f00fb8
--- /dev/null
+++ b/tempest/tests/lib/fake_auth_provider.py
@@ -0,0 +1,34 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+class FakeAuthProvider(object):
+
+ def __init__(self, creds_dict=None):
+ creds_dict = creds_dict or {}
+ self.credentials = FakeCredentials(creds_dict)
+
+ def auth_request(self, method, url, headers=None, body=None, filters=None):
+ return url, headers, body
+
+ def base_url(self, filters, auth_data=None):
+ return "https://example.com"
+
+
+class FakeCredentials(object):
+
+ def __init__(self, creds_dict):
+ for key in creds_dict.keys():
+ setattr(self, key, creds_dict[key])
diff --git a/tempest/tests/lib/fake_credentials.py b/tempest/tests/lib/fake_credentials.py
new file mode 100644
index 0000000..fb81bd6
--- /dev/null
+++ b/tempest/tests/lib/fake_credentials.py
@@ -0,0 +1,59 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib import auth
+
+
+class FakeCredentials(auth.Credentials):
+
+ def is_valid(self):
+ return True
+
+
+class FakeKeystoneV2Credentials(auth.KeystoneV2Credentials):
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ tenant_name='fake_tenant_name'
+ )
+ super(FakeKeystoneV2Credentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3Credentials(auth.KeystoneV3Credentials):
+ """Fake credentials suitable for the Keystone Identity V3 API"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name',
+ project_name='fake_tenant_name',
+ project_domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3Credentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3DomainCredentials(auth.KeystoneV3Credentials):
+ """Fake credentials for the Keystone Identity V3 API, with no scope"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3DomainCredentials, self).__init__(**creds)
diff --git a/tempest/tests/lib/fake_http.py b/tempest/tests/lib/fake_http.py
new file mode 100644
index 0000000..eda202d
--- /dev/null
+++ b/tempest/tests/lib/fake_http.py
@@ -0,0 +1,74 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+import httplib2
+
+
+class fake_httplib2(object):
+
+ def __init__(self, return_type=None, *args, **kwargs):
+ self.return_type = return_type
+
+ def request(self, uri, method="GET", body=None, headers=None,
+ redirections=5, connection_type=None):
+ if not self.return_type:
+ fake_headers = httplib2.Response(headers)
+ return_obj = {
+ 'uri': uri,
+ 'method': method,
+ 'body': body,
+ 'headers': headers
+ }
+ return (fake_headers, return_obj)
+ elif isinstance(self.return_type, int):
+ body = body or "fake_body"
+ header_info = {
+ 'content-type': 'text/plain',
+ 'status': str(self.return_type),
+ 'content-length': len(body)
+ }
+ resp_header = httplib2.Response(header_info)
+ return (resp_header, body)
+ else:
+ msg = "unsupported return type %s" % self.return_type
+ raise TypeError(msg)
+
+
+class fake_httplib(object):
+ def __init__(self, headers, body=None,
+ version=1.0, status=200, reason="Ok"):
+ """Fake httplib implementation
+
+ :param headers: dict representing HTTP response headers
+ :param body: file-like object
+ :param version: HTTP Version
+ :param status: Response status code
+ :param reason: Status code related message.
+ """
+ self.body = body
+ self.status = status
+ self.reason = reason
+ self.version = version
+ self.headers = headers
+
+ def getheaders(self):
+ return copy.deepcopy(self.headers).items()
+
+ def getheader(self, key, default):
+ return self.headers.get(key, default)
+
+ def read(self, amt):
+ return self.body.read(amt)
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
new file mode 100644
index 0000000..bac2676
--- /dev/null
+++ b/tempest/tests/lib/fake_identity.py
@@ -0,0 +1,164 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+import httplib2
+
+FAKE_AUTH_URL = 'http://fake_uri.com/auth'
+
+TOKEN = "fake_token"
+ALT_TOKEN = "alt_fake_token"
+
+# Fake Identity v2 constants
+COMPUTE_ENDPOINTS_V2 = {
+ "endpoints": [
+ {
+ "adminURL": "http://fake_url/v2/first_endpoint/admin",
+ "region": "NoMatchRegion",
+ "internalURL": "http://fake_url/v2/first_endpoint/internal",
+ "publicURL": "http://fake_url/v2/first_endpoint/public"
+ },
+ {
+ "adminURL": "http://fake_url/v2/second_endpoint/admin",
+ "region": "FakeRegion",
+ "internalURL": "http://fake_url/v2/second_endpoint/internal",
+ "publicURL": "http://fake_url/v2/second_endpoint/public"
+ },
+ ],
+ "type": "compute",
+ "name": "nova"
+}
+
+CATALOG_V2 = [COMPUTE_ENDPOINTS_V2, ]
+
+ALT_IDENTITY_V2_RESPONSE = {
+ "access": {
+ "token": {
+ "expires": "2020-01-01T00:00:10Z",
+ "id": ALT_TOKEN,
+ "tenant": {
+ "id": "fake_alt_tenant_id"
+ },
+ },
+ "user": {
+ "id": "fake_alt_user_id",
+ },
+ "serviceCatalog": CATALOG_V2,
+ },
+}
+
+IDENTITY_V2_RESPONSE = {
+ "access": {
+ "token": {
+ "expires": "2020-01-01T00:00:10Z",
+ "id": TOKEN,
+ "tenant": {
+ "id": "fake_tenant_id"
+ },
+ },
+ "user": {
+ "id": "fake_user_id",
+ },
+ "serviceCatalog": CATALOG_V2,
+ },
+}
+
+# Fake Identity V3 constants
+COMPUTE_ENDPOINTS_V3 = {
+ "endpoints": [
+ {
+ "id": "first_compute_fake_service",
+ "interface": "public",
+ "region": "NoMatchRegion",
+ "url": "http://fake_url/v3/first_endpoint/api"
+ },
+ {
+ "id": "second_fake_service",
+ "interface": "public",
+ "region": "FakeRegion",
+ "url": "http://fake_url/v3/second_endpoint/api"
+ },
+ {
+ "id": "third_fake_service",
+ "interface": "admin",
+ "region": "MiddleEarthRegion",
+ "url": "http://fake_url/v3/third_endpoint/api"
+ }
+
+ ],
+ "type": "compute",
+ "id": "fake_compute_endpoint"
+}
+
+CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ]
+
+IDENTITY_V3_RESPONSE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "project": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "fake"
+ },
+ "id": "project_id",
+ "name": "project_name"
+ },
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ "catalog": CATALOG_V3
+ }
+}
+
+ALT_IDENTITY_V3 = IDENTITY_V3_RESPONSE
+
+
+def _fake_v3_response(self, uri, method="GET", body=None, headers=None,
+ redirections=5, connection_type=None):
+ fake_headers = {
+ "status": "201",
+ "x-subject-token": TOKEN
+ }
+ return (httplib2.Response(fake_headers),
+ json.dumps(IDENTITY_V3_RESPONSE))
+
+
+def _fake_v2_response(self, uri, method="GET", body=None, headers=None,
+ redirections=5, connection_type=None):
+ return (httplib2.Response({"status": "200"}),
+ json.dumps(IDENTITY_V2_RESPONSE))
+
+
+def _fake_auth_failure_response():
+ # the response body isn't really used in this case, but lets send it anyway
+ # to have a safe check in some future change on the rest client.
+ body = {
+ "unauthorized": {
+ "message": "Unauthorized",
+ "code": "401"
+ }
+ }
+ return httplib2.Response({"status": "401"}), json.dumps(body)
diff --git a/tempest/tests/lib/services/__init__.py b/tempest/tests/lib/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/__init__.py
diff --git a/tempest/tests/lib/services/compute/__init__.py b/tempest/tests/lib/services/compute/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/compute/__init__.py
diff --git a/tempest/tests/lib/services/compute/base.py b/tempest/tests/lib/services/compute/base.py
new file mode 100644
index 0000000..5602044
--- /dev/null
+++ b/tempest/tests/lib/services/compute/base.py
@@ -0,0 +1,45 @@
+# Copyright 2015 Deutsche Telekom AG. 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 httplib2
+from oslo_serialization import jsonutils as json
+from oslotest import mockpatch
+
+from tempest.tests.lib import base
+
+
+class BaseComputeServiceTest(base.TestCase):
+ def create_response(self, body, to_utf=False, status=200, headers=None):
+ json_body = {}
+ if body:
+ json_body = json.dumps(body)
+ if to_utf:
+ json_body = json_body.encode('utf-8')
+ resp_dict = {'status': status}
+ if headers:
+ resp_dict.update(headers)
+ response = (httplib2.Response(resp_dict), json_body)
+ return response
+
+ def check_service_client_function(self, function, function2mock,
+ body, to_utf=False, status=200,
+ headers=None, **kwargs):
+ mocked_response = self.create_response(body, to_utf, status, headers)
+ self.useFixture(mockpatch.Patch(
+ function2mock, return_value=mocked_response))
+ if kwargs:
+ resp = function(**kwargs)
+ else:
+ resp = function()
+ self.assertEqual(body, resp)
diff --git a/tempest/tests/lib/services/compute/test_agents_client.py b/tempest/tests/lib/services/compute/test_agents_client.py
new file mode 100644
index 0000000..3c5043d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_agents_client.py
@@ -0,0 +1,103 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import agents_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestAgentsClient(base.BaseComputeServiceTest):
+ FAKE_CREATE_AGENT = {
+ "agent": {
+ "url": "http://foo.com",
+ "hypervisor": "kvm",
+ "md5hash": "md5",
+ "version": "2",
+ "architecture": "x86_64",
+ "os": "linux",
+ "agent_id": 1
+ }
+ }
+
+ FAKE_UPDATE_AGENT = {
+ "agent": {
+ "url": "http://foo.com",
+ "md5hash": "md5",
+ "version": "2",
+ "agent_id": 1
+ }
+ }
+
+ def setUp(self):
+ super(TestAgentsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = agents_client.AgentsClient(fake_auth,
+ 'compute', 'regionOne')
+
+ def _test_list_agents(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_agents,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"agents": []},
+ bytes_body)
+ self.check_service_client_function(
+ self.client.list_agents,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"agents": []},
+ bytes_body,
+ hypervisor="kvm")
+
+ def _test_create_agent(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_agent,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_AGENT,
+ bytes_body,
+ url="http://foo.com", hypervisor="kvm", md5hash="md5",
+ version="2", architecture="x86_64", os="linux")
+
+ def _test_delete_agent(self):
+ self.check_service_client_function(
+ self.client.delete_agent,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, agent_id="1")
+
+ def _test_update_agent(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_agent,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_AGENT,
+ bytes_body,
+ agent_id="1", url="http://foo.com", md5hash="md5", version="2")
+
+ def test_list_agents_with_str_body(self):
+ self._test_list_agents()
+
+ def test_list_agents_with_bytes_body(self):
+ self._test_list_agents(bytes_body=True)
+
+ def test_create_agent_with_str_body(self):
+ self._test_create_agent()
+
+ def test_create_agent_with_bytes_body(self):
+ self._test_create_agent(bytes_body=True)
+
+ def test_delete_agent(self):
+ self._test_delete_agent()
+
+ def test_update_agent_with_str_body(self):
+ self._test_update_agent()
+
+ def test_update_agent_with_bytes_body(self):
+ self._test_update_agent(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_aggregates_client.py b/tempest/tests/lib/services/compute/test_aggregates_client.py
new file mode 100644
index 0000000..a63380e
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_aggregates_client.py
@@ -0,0 +1,192 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import aggregates_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestAggregatesClient(base.BaseComputeServiceTest):
+ FAKE_SHOW_AGGREGATE = {
+ "aggregate":
+ {
+ "name": "hoge",
+ "availability_zone": None,
+ "deleted": False,
+ "created_at":
+ "2015-07-16T03:07:32.000000",
+ "updated_at": None,
+ "hosts": [],
+ "deleted_at": None,
+ "id": 1,
+ "metadata": {}
+ }
+ }
+
+ FAKE_CREATE_AGGREGATE = {
+ "aggregate":
+ {
+ "name": u'\xf4',
+ "availability_zone": None,
+ "deleted": False,
+ "created_at": "2015-07-21T04:11:18.000000",
+ "updated_at": None,
+ "deleted_at": None,
+ "id": 1
+ }
+ }
+
+ FAKE_UPDATE_AGGREGATE = {
+ "aggregate":
+ {
+ "name": u'\xe9',
+ "availability_zone": None,
+ "deleted": False,
+ "created_at": "2015-07-16T03:07:32.000000",
+ "updated_at": "2015-07-23T05:16:29.000000",
+ "hosts": [],
+ "deleted_at": None,
+ "id": 1,
+ "metadata": {}
+ }
+ }
+
+ FAKE_AGGREGATE = {
+ "availability_zone": "nova",
+ "created_at": "2013-08-18T12:17:56.297823",
+ "deleted": False,
+ "deleted_at": None,
+ "hosts": [
+ "21549b2f665945baaa7101926a00143c"
+ ],
+ "id": 1,
+ "metadata": {
+ "availability_zone": "nova"
+ },
+ "name": u'\xe9',
+ "updated_at": None
+ }
+
+ FAKE_ADD_HOST = {'aggregate': FAKE_AGGREGATE}
+ FAKE_REMOVE_HOST = {'aggregate': FAKE_AGGREGATE}
+ FAKE_SET_METADATA = {'aggregate': FAKE_AGGREGATE}
+
+ def setUp(self):
+ super(TestAggregatesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = aggregates_client.AggregatesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_aggregates(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_aggregates,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"aggregates": []},
+ bytes_body)
+
+ def test_list_aggregates_with_str_body(self):
+ self._test_list_aggregates()
+
+ def test_list_aggregates_with_bytes_body(self):
+ self._test_list_aggregates(bytes_body=True)
+
+ def _test_show_aggregate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_AGGREGATE,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_show_aggregate_with_str_body(self):
+ self._test_show_aggregate()
+
+ def test_show_aggregate_with_bytes_body(self):
+ self._test_show_aggregate(bytes_body=True)
+
+ def _test_create_aggregate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_AGGREGATE,
+ bytes_body,
+ name='hoge')
+
+ def test_create_aggregate_with_str_body(self):
+ self._test_create_aggregate()
+
+ def test_create_aggregate_with_bytes_body(self):
+ self._test_create_aggregate(bytes_body=True)
+
+ def test_delete_aggregate(self):
+ self.check_service_client_function(
+ self.client.delete_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, aggregate_id="1")
+
+ def _test_update_aggregate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_aggregate,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_AGGREGATE,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_update_aggregate_with_str_body(self):
+ self._test_update_aggregate()
+
+ def test_update_aggregate_with_bytes_body(self):
+ self._test_update_aggregate(bytes_body=True)
+
+ def _test_add_host(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_host,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_ADD_HOST,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_add_host_with_str_body(self):
+ self._test_add_host()
+
+ def test_add_host_with_bytes_body(self):
+ self._test_add_host(bytes_body=True)
+
+ def _test_remove_host(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.remove_host,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_REMOVE_HOST,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_remove_host_with_str_body(self):
+ self._test_remove_host()
+
+ def test_remove_host_with_bytes_body(self):
+ self._test_remove_host(bytes_body=True)
+
+ def _test_set_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_metadata,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SET_METADATA,
+ bytes_body,
+ aggregate_id=1)
+
+ def test_set_metadata_with_str_body(self):
+ self._test_set_metadata()
+
+ def test_set_metadata_with_bytes_body(self):
+ self._test_set_metadata(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_availability_zone_client.py b/tempest/tests/lib/services/compute/test_availability_zone_client.py
new file mode 100644
index 0000000..d16cf0a
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_availability_zone_client.py
@@ -0,0 +1,51 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import availability_zone_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestAvailabilityZoneClient(base.BaseComputeServiceTest):
+
+ FAKE_AVAILABIRITY_ZONE_INFO = {
+ "availabilityZoneInfo":
+ [
+ {
+ "zoneState": {
+ "available": True
+ },
+ "hosts": None,
+ "zoneName": u'\xf4'
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestAvailabilityZoneClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = availability_zone_client.AvailabilityZoneClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_availability_zones_with_str_body(self):
+ self.check_service_client_function(
+ self.client.list_availability_zones,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_AVAILABIRITY_ZONE_INFO)
+
+ def test_list_availability_zones_with_bytes_body(self):
+ self.check_service_client_function(
+ self.client.list_availability_zones,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_AVAILABIRITY_ZONE_INFO, to_utf=True)
diff --git a/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py b/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
new file mode 100644
index 0000000..a867c06
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py
@@ -0,0 +1,74 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import baremetal_nodes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestBareMetalNodesClient(base.BaseComputeServiceTest):
+
+ FAKE_NODE_INFO = {'cpus': '8',
+ 'disk_gb': '64',
+ 'host': '10.0.2.15',
+ 'id': 'Identifier',
+ 'instance_uuid': "null",
+ 'interfaces': [
+ {
+ "address": "20::01",
+ "datapath_id": "null",
+ "id": 1,
+ "port_no": None
+ }
+ ],
+ 'memory_mb': '8192',
+ 'task_state': None}
+
+ def setUp(self):
+ super(TestBareMetalNodesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.baremetal_nodes_client = (baremetal_nodes_client.
+ BaremetalNodesClient
+ (fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_bareMetal_nodes(self, operation='list', bytes_body=False):
+ if operation != 'list':
+ expected = {"node": self.FAKE_NODE_INFO}
+ function = self.baremetal_nodes_client.show_baremetal_node
+ else:
+ node_info = copy.deepcopy(self.FAKE_NODE_INFO)
+ del node_info['instance_uuid']
+ expected = {"nodes": [node_info]}
+ function = self.baremetal_nodes_client.list_baremetal_nodes
+
+ self.check_service_client_function(
+ function,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body, 200,
+ baremetal_node_id='Identifier')
+
+ def test_list_bareMetal_nodes_with_str_body(self):
+ self._test_bareMetal_nodes()
+
+ def test_list_bareMetal_nodes_with_bytes_body(self):
+ self._test_bareMetal_nodes(bytes_body=True)
+
+ def test_show_bareMetal_node_with_str_body(self):
+ self._test_bareMetal_nodes('show')
+
+ def test_show_bareMetal_node_with_bytes_body(self):
+ self._test_bareMetal_nodes('show', True)
diff --git a/tempest/tests/lib/services/compute/test_certificates_client.py b/tempest/tests/lib/services/compute/test_certificates_client.py
new file mode 100644
index 0000000..e8123bb
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_certificates_client.py
@@ -0,0 +1,64 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import certificates_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestCertificatesClient(base.BaseComputeServiceTest):
+
+ FAKE_CERTIFICATE = {
+ "certificate": {
+ "data": "-----BEGIN----MIICyzCCAjSgAwI----END CERTIFICATE-----\n",
+ "private_key": None
+ }
+ }
+
+ def setUp(self):
+ super(TestCertificatesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = certificates_client.CertificatesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_certificate(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_certificate,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CERTIFICATE,
+ bytes_body,
+ certificate_id="fake-id")
+
+ def test_show_certificate_with_str_body(self):
+ self._test_show_certificate()
+
+ def test_show_certificate_with_bytes_body(self):
+ self._test_show_certificate(bytes_body=True)
+
+ def _test_create_certificate(self, bytes_body=False):
+ cert = copy.deepcopy(self.FAKE_CERTIFICATE)
+ cert['certificate']['private_key'] = "my_private_key"
+ self.check_service_client_function(
+ self.client.create_certificate,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ cert,
+ bytes_body)
+
+ def test_create_certificate_with_str_body(self):
+ self._test_create_certificate()
+
+ def test_create_certificate_with_bytes_body(self):
+ self._test_create_certificate(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_extensions_client.py b/tempest/tests/lib/services/compute/test_extensions_client.py
new file mode 100644
index 0000000..7415988
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_extensions_client.py
@@ -0,0 +1,65 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import extensions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestExtensionsClient(base.BaseComputeServiceTest):
+
+ FAKE_SHOW_EXTENSION = {
+ "extension": {
+ "updated": "2011-06-09T00:00:00Z",
+ "name": "Multinic",
+ "links": [],
+ "namespace":
+ "http://docs.openstack.org/compute/ext/multinic/api/v1.1",
+ "alias": "NMN",
+ "description": u'\u2740(*\xb4\u25e1`*)\u2740'
+ }
+ }
+
+ def setUp(self):
+ super(TestExtensionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = extensions_client.ExtensionsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_extensions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_extensions,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"extensions": []},
+ bytes_body)
+
+ def test_list_extensions_with_str_body(self):
+ self._test_list_extensions()
+
+ def test_list_extensions_with_bytes_body(self):
+ self._test_list_extensions(bytes_body=True)
+
+ def _test_show_extension(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_extension,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_EXTENSION,
+ bytes_body,
+ extension_alias="NMN")
+
+ def test_show_extension_with_str_body(self):
+ self._test_show_extension()
+
+ def test_show_extension_with_bytes_body(self):
+ self._test_show_extension(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_fixedIPs_client.py b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
new file mode 100644
index 0000000..6999f24
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_fixedIPs_client.py
@@ -0,0 +1,58 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import fixed_ips_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFixedIPsClient(base.BaseComputeServiceTest):
+ FIXED_IP_INFO = {"fixed_ip": {"address": "10.0.0.1",
+ "cidr": "10.11.12.0/24",
+ "host": "localhost",
+ "hostname": "OpenStack"}}
+
+ def setUp(self):
+ super(TestFixedIPsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.fixedIPsClient = (fixed_ips_client.
+ FixedIPsClient
+ (fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_show_fixed_ip(self, bytes_body=False):
+ self.check_service_client_function(
+ self.fixedIPsClient.show_fixed_ip,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FIXED_IP_INFO, bytes_body,
+ status=200, fixed_ip='Identifier')
+
+ def test_show_fixed_ip_with_str_body(self):
+ self._test_show_fixed_ip()
+
+ def test_show_fixed_ip_with_bytes_body(self):
+ self._test_show_fixed_ip(True)
+
+ def _test_reserve_fixed_ip(self, bytes_body=False):
+ self.check_service_client_function(
+ self.fixedIPsClient.reserve_fixed_ip,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, bytes_body,
+ status=202, fixed_ip='Identifier')
+
+ def test_reserve_fixed_ip_with_str_body(self):
+ self._test_reserve_fixed_ip()
+
+ def test_reserve_fixed_ip_with_bytes_body(self):
+ self._test_reserve_fixed_ip(True)
diff --git a/tempest/tests/lib/services/compute/test_flavors_client.py b/tempest/tests/lib/services/compute/test_flavors_client.py
new file mode 100644
index 0000000..795aff7
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_flavors_client.py
@@ -0,0 +1,255 @@
+# Copyright 2015 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import httplib2
+
+from oslo_serialization import jsonutils as json
+from oslotest import mockpatch
+
+from tempest.lib.services.compute import flavors_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFlavorsClient(base.BaseComputeServiceTest):
+
+ FAKE_FLAVOR = {
+ "disk": 1,
+ "id": "1",
+ "links": [{
+ "href": "http://openstack.example.com/v2/openstack/flavors/1",
+ "rel": "self"}, {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"}],
+ "name": "m1.tiny",
+ "ram": 512,
+ "swap": 1,
+ "vcpus": 1
+ }
+
+ EXTRA_SPECS = {"extra_specs": {
+ "key1": "value1",
+ "key2": "value2"}
+ }
+
+ FAKE_FLAVOR_ACCESS = {
+ "flavor_id": "10",
+ "tenant_id": "1a951d988e264818afe520e78697dcbf"
+ }
+
+ def setUp(self):
+ super(TestFlavorsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = flavors_client.FlavorsClient(fake_auth,
+ 'compute', 'regionOne')
+
+ def _test_list_flavors(self, bytes_body=False):
+ flavor = copy.deepcopy(TestFlavorsClient.FAKE_FLAVOR)
+ # Remove extra attributes
+ for attribute in ('disk', 'vcpus', 'ram', 'swap'):
+ del flavor[attribute]
+ expected = {'flavors': [flavor]}
+ self.check_service_client_function(
+ self.client.list_flavors,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body)
+
+ def test_list_flavors_str_body(self):
+ self._test_list_flavors(bytes_body=False)
+
+ def test_list_flavors_byte_body(self):
+ self._test_list_flavors(bytes_body=True)
+
+ def _test_show_flavor(self, bytes_body=False):
+ expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR}
+ self.check_service_client_function(
+ self.client.show_flavor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ flavor_id='fake-id')
+
+ def test_show_flavor_str_body(self):
+ self._test_show_flavor(bytes_body=False)
+
+ def test_show_flavor_byte_body(self):
+ self._test_show_flavor(bytes_body=True)
+
+ def _test_create_flavor(self, bytes_body=False):
+ expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR}
+ request = copy.deepcopy(TestFlavorsClient.FAKE_FLAVOR)
+ # The 'links' parameter should not be passed in
+ del request['links']
+ self.check_service_client_function(
+ self.client.create_flavor,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ **request)
+
+ def test_create_flavor_str_body(self):
+ self._test_create_flavor(bytes_body=False)
+
+ def test_create_flavor__byte_body(self):
+ self._test_create_flavor(bytes_body=True)
+
+ def test_delete_flavor(self):
+ self.check_service_client_function(
+ self.client.delete_flavor,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, flavor_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+ def _test_is_resource_deleted(self, flavor_id, is_deleted=True,
+ bytes_body=False):
+ body = json.dumps({'flavors': [TestFlavorsClient.FAKE_FLAVOR]})
+ if bytes_body:
+ body = body.encode('utf-8')
+ response = (httplib2.Response({'status': 200}), body)
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.rest_client.RestClient.get',
+ return_value=response))
+ self.assertEqual(is_deleted,
+ self.client.is_resource_deleted(flavor_id))
+
+ def test_is_resource_deleted_true_str_body(self):
+ self._test_is_resource_deleted('2', bytes_body=False)
+
+ def test_is_resource_deleted_true_byte_body(self):
+ self._test_is_resource_deleted('2', bytes_body=True)
+
+ def test_is_resource_deleted_false_str_body(self):
+ self._test_is_resource_deleted('1', is_deleted=False, bytes_body=False)
+
+ def test_is_resource_deleted_false_byte_body(self):
+ self._test_is_resource_deleted('1', is_deleted=False, bytes_body=True)
+
+ def _test_set_flavor_extra_spec(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ TestFlavorsClient.EXTRA_SPECS,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ **TestFlavorsClient.EXTRA_SPECS)
+
+ def test_set_flavor_extra_spec_str_body(self):
+ self._test_set_flavor_extra_spec(bytes_body=False)
+
+ def test_set_flavor_extra_spec_byte_body(self):
+ self._test_set_flavor_extra_spec(bytes_body=True)
+
+ def _test_list_flavor_extra_specs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_flavor_extra_specs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ TestFlavorsClient.EXTRA_SPECS,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6')
+
+ def test_list_flavor_extra_specs_str_body(self):
+ self._test_list_flavor_extra_specs(bytes_body=False)
+
+ def test_list_flavor_extra_specs__byte_body(self):
+ self._test_list_flavor_extra_specs(bytes_body=True)
+
+ def _test_show_flavor_extra_spec(self, bytes_body=False):
+ expected = {"key": "value"}
+ self.check_service_client_function(
+ self.client.show_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ key='key')
+
+ def test_show_flavor_extra_spec_str_body(self):
+ self._test_show_flavor_extra_spec(bytes_body=False)
+
+ def test_show_flavor_extra_spec__byte_body(self):
+ self._test_show_flavor_extra_spec(bytes_body=True)
+
+ def _test_update_flavor_extra_spec(self, bytes_body=False):
+ expected = {"key1": "value"}
+ self.check_service_client_function(
+ self.client.update_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ key='key1', **expected)
+
+ def test_update_flavor_extra_spec_str_body(self):
+ self._test_update_flavor_extra_spec(bytes_body=False)
+
+ def test_update_flavor_extra_spec_byte_body(self):
+ self._test_update_flavor_extra_spec(bytes_body=True)
+
+ def test_unset_flavor_extra_spec(self):
+ self.check_service_client_function(
+ self.client.unset_flavor_extra_spec,
+ 'tempest.lib.common.rest_client.RestClient.delete', {},
+ flavor_id='c782b7a9-33cd-45f0-b795-7f87f456408b', key='key')
+
+ def _test_list_flavor_access(self, bytes_body=False):
+ expected = {'flavor_access': [TestFlavorsClient.FAKE_FLAVOR_ACCESS]}
+ self.check_service_client_function(
+ self.client.list_flavor_access,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6')
+
+ def test_list_flavor_access_str_body(self):
+ self._test_list_flavor_access(bytes_body=False)
+
+ def test_list_flavor_access_byte_body(self):
+ self._test_list_flavor_access(bytes_body=True)
+
+ def _test_add_flavor_access(self, bytes_body=False):
+ expected = {
+ "flavor_access": [TestFlavorsClient.FAKE_FLAVOR_ACCESS]
+ }
+ self.check_service_client_function(
+ self.client.add_flavor_access,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ tenant_id='1a951d988e264818afe520e78697dcbf')
+
+ def test_add_flavor_access_str_body(self):
+ self._test_add_flavor_access(bytes_body=False)
+
+ def test_add_flavor_access_byte_body(self):
+ self._test_add_flavor_access(bytes_body=True)
+
+ def _test_remove_flavor_access(self, bytes_body=False):
+ expected = {
+ "flavor_access": [TestFlavorsClient.FAKE_FLAVOR_ACCESS]
+ }
+ self.check_service_client_function(
+ self.client.remove_flavor_access,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ flavor_id='10',
+ tenant_id='a6edd4d66ad04245b5d2d8716ecc91e3')
+
+ def test_remove_flavor_access_str_body(self):
+ self._test_remove_flavor_access(bytes_body=False)
+
+ def test_remove_flavor_access_byte_body(self):
+ self._test_remove_flavor_access(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
new file mode 100644
index 0000000..f30719e
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py
@@ -0,0 +1,46 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import floating_ip_pools_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFloatingIPPoolsClient(base.BaseComputeServiceTest):
+
+ FAKE_FLOATING_IP_POOLS = {
+ "floating_ip_pools":
+ [
+ {"name": u'\u3042'},
+ {"name": u'\u3044'}
+ ]
+ }
+
+ def setUp(self):
+ super(TestFloatingIPPoolsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = floating_ip_pools_client.FloatingIPPoolsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_floating_ip_pools_with_str_body(self):
+ self.check_service_client_function(
+ self.client.list_floating_ip_pools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_FLOATING_IP_POOLS)
+
+ def test_list_floating_ip_pools_with_bytes_body(self):
+ self.check_service_client_function(
+ self.client.list_floating_ip_pools,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_FLOATING_IP_POOLS, to_utf=True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py b/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
new file mode 100644
index 0000000..c16c985
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py
@@ -0,0 +1,88 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.tests.lib import fake_auth_provider
+
+from tempest.lib.services.compute import floating_ips_bulk_client
+from tempest.tests.lib.services.compute import base
+
+
+class TestFloatingIPsBulkClient(base.BaseComputeServiceTest):
+
+ FAKE_FIP_BULK_LIST = {"floating_ip_info": [{
+ "address": "10.10.10.1",
+ "instance_uuid": None,
+ "fixed_ip": None,
+ "interface": "eth0",
+ "pool": "nova",
+ "project_id": None
+ },
+ {
+ "address": "10.10.10.2",
+ "instance_uuid": None,
+ "fixed_ip": None,
+ "interface": "eth0",
+ "pool": "nova",
+ "project_id": None
+ }]}
+
+ def setUp(self):
+ super(TestFloatingIPsBulkClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = floating_ips_bulk_client.FloatingIPsBulkClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_floating_ips_bulk(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_floating_ips_bulk,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_FIP_BULK_LIST,
+ to_utf=bytes_body)
+
+ def _test_create_floating_ips_bulk(self, bytes_body=False):
+ fake_fip_create_data = {"floating_ips_bulk_create": {
+ "ip_range": "192.168.1.0/24", "pool": "nova", "interface": "eth0"}}
+ self.check_service_client_function(
+ self.client.create_floating_ips_bulk,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ fake_fip_create_data,
+ to_utf=bytes_body,
+ ip_range="192.168.1.0/24", pool="nova", interface="eth0")
+
+ def _test_delete_floating_ips_bulk(self, bytes_body=False):
+ fake_fip_delete_data = {"floating_ips_bulk_delete": "192.168.1.0/24"}
+ self.check_service_client_function(
+ self.client.delete_floating_ips_bulk,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_fip_delete_data,
+ to_utf=bytes_body,
+ ip_range="192.168.1.0/24")
+
+ def test_list_floating_ips_bulk_with_str_body(self):
+ self._test_list_floating_ips_bulk()
+
+ def test_list_floating_ips_bulk_with_bytes_body(self):
+ self._test_list_floating_ips_bulk(True)
+
+ def test_create_floating_ips_bulk_with_str_body(self):
+ self._test_create_floating_ips_bulk()
+
+ def test_create_floating_ips_bulk_with_bytes_body(self):
+ self._test_create_floating_ips_bulk(True)
+
+ def test_delete_floating_ips_bulk_with_str_body(self):
+ self._test_delete_floating_ips_bulk()
+
+ def test_delete_floating_ips_bulk_with_bytes_body(self):
+ self._test_delete_floating_ips_bulk(True)
diff --git a/tempest/tests/lib/services/compute/test_floating_ips_client.py b/tempest/tests/lib/services/compute/test_floating_ips_client.py
new file mode 100644
index 0000000..3844ba8
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_floating_ips_client.py
@@ -0,0 +1,113 @@
+# Copyright 2015 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# 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 import exceptions as lib_exc
+from tempest.lib.services.compute import floating_ips_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestFloatingIpsClient(base.BaseComputeServiceTest):
+
+ floating_ip = {"fixed_ip": None,
+ "id": "46d61064-13ba-4bf0-9557-69de824c3d6f",
+ "instance_id": "a1daa443-a6bb-463e-aea2-104b7d912eb8",
+ "ip": "10.10.10.1",
+ "pool": "nova"}
+
+ def setUp(self):
+ super(TestFloatingIpsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = floating_ips_client.FloatingIPsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_floating_ips(self, bytes_body=False):
+ expected = {'floating_ips': [TestFloatingIpsClient.floating_ip]}
+ self.check_service_client_function(
+ self.client.list_floating_ips,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body)
+
+ def test_list_floating_ips_str_body(self):
+ self._test_list_floating_ips(bytes_body=False)
+
+ def test_list_floating_ips_byte_body(self):
+ self._test_list_floating_ips(bytes_body=True)
+
+ def _test_show_floating_ip(self, bytes_body=False):
+ expected = {"floating_ip": TestFloatingIpsClient.floating_ip}
+ self.check_service_client_function(
+ self.client.show_floating_ip,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body,
+ floating_ip_id='a1daa443-a6bb-463e-aea2-104b7d912eb8')
+
+ def test_show_floating_ip_str_body(self):
+ self._test_show_floating_ip(bytes_body=False)
+
+ def test_show_floating_ip_byte_body(self):
+ self._test_show_floating_ip(bytes_body=True)
+
+ def _test_create_floating_ip(self, bytes_body=False):
+ expected = {"floating_ip": TestFloatingIpsClient.floating_ip}
+ self.check_service_client_function(
+ self.client.create_floating_ip,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ expected,
+ bytes_body,
+ pool_name='nova')
+
+ def test_create_floating_ip_str_body(self):
+ self._test_create_floating_ip(bytes_body=False)
+
+ def test_create_floating_ip_byte_body(self):
+ self._test_create_floating_ip(bytes_body=True)
+
+ def test_delete_floating_ip(self):
+ self.check_service_client_function(
+ self.client.delete_floating_ip,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, floating_ip_id='fake-id')
+
+ def test_associate_floating_ip_to_server(self):
+ self.check_service_client_function(
+ self.client.associate_floating_ip_to_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, status=202, floating_ip='10.10.10.1',
+ server_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+ def test_disassociate_floating_ip_from_server(self):
+ self.check_service_client_function(
+ self.client.disassociate_floating_ip_from_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {}, status=202, floating_ip='10.10.10.1',
+ server_id='c782b7a9-33cd-45f0-b795-7f87f456408b')
+
+ def test_is_resource_deleted_true(self):
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.services.compute.floating_ips_client.'
+ 'FloatingIPsClient.show_floating_ip',
+ side_effect=lib_exc.NotFound()))
+ self.assertTrue(self.client.is_resource_deleted('fake-id'))
+
+ def test_is_resource_deleted_false(self):
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.services.compute.floating_ips_client.'
+ 'FloatingIPsClient.show_floating_ip',
+ return_value={"floating_ip": TestFloatingIpsClient.floating_ip}))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_hosts_client.py b/tempest/tests/lib/services/compute/test_hosts_client.py
new file mode 100644
index 0000000..d9ff513
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_hosts_client.py
@@ -0,0 +1,147 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import hosts_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestHostsClient(base.BaseComputeServiceTest):
+ FAKE_HOST_DATA = {
+ "host": {
+ "resource": {
+ "cpu": 1,
+ "disk_gb": 1028,
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "memory_mb": 8192,
+ "project": "(total)"
+ }
+ },
+ "hosts": {
+ "host_name": "c1a7de0ac9d94e4baceae031d05caae3",
+ "service": "conductor",
+ "zone": "internal"
+ },
+ "enable_hosts": {
+ "host": "65c5d5b7e3bd44308e67fc50f362aee6",
+ "maintenance_mode": "off_maintenance",
+ "status": "enabled"
+ }
+ }
+
+ FAKE_CONTROL_DATA = {
+ "shutdown": {
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "power_action": "shutdown"
+ },
+ "startup": {
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "power_action": "startup"
+ },
+ "reboot": {
+ "host": "c1a7de0ac9d94e4baceae031d05caae3",
+ "power_action": "reboot"
+ }}
+
+ HOST_DATA = {'host': [FAKE_HOST_DATA['host']]}
+ HOSTS_DATA = {'hosts': [FAKE_HOST_DATA['hosts']]}
+ ENABLE_HOST_DATA = FAKE_HOST_DATA['enable_hosts']
+ HOST_ID = "c1a7de0ac9d94e4baceae031d05caae3"
+ TEST_HOST_DATA = {
+ "status": "enable",
+ "maintenance_mode": "disable"
+ }
+
+ def setUp(self):
+ super(TestHostsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = hosts_client.HostsClient(fake_auth, 'compute',
+ 'regionOne')
+ self.params = {'hostname': self.HOST_ID}
+ self.func2mock = {
+ 'get': 'tempest.lib.common.rest_client.RestClient.get',
+ 'put': 'tempest.lib.common.rest_client.RestClient.put'}
+
+ def _test_host_data(self, test_type='list', bytes_body=False):
+ expected_resp = self.HOST_DATA
+ if test_type != 'list':
+ function_call = self.client.show_host
+ else:
+ expected_resp = self.HOSTS_DATA
+ function_call = self.client.list_hosts
+ self.params = {'host_name': self.HOST_ID}
+
+ self.check_service_client_function(
+ function_call, self.func2mock['get'],
+ expected_resp, bytes_body,
+ 200, **self.params)
+
+ def _test_update_hosts(self, bytes_body=False):
+ expected_resp = self.ENABLE_HOST_DATA
+ self.check_service_client_function(
+ self.client.update_host, self.func2mock['put'],
+ expected_resp, bytes_body,
+ 200, **self.params)
+
+ def _test_control_host(self, control_op='reboot', bytes_body=False):
+ if control_op == 'start':
+ expected_resp = self.FAKE_CONTROL_DATA['startup']
+ function_call = self.client.startup_host
+ elif control_op == 'stop':
+ expected_resp = self.FAKE_CONTROL_DATA['shutdown']
+ function_call = self.client.shutdown_host
+ else:
+ expected_resp = self.FAKE_CONTROL_DATA['reboot']
+ function_call = self.client.reboot_host
+
+ self.check_service_client_function(
+ function_call, self.func2mock['get'],
+ expected_resp, bytes_body,
+ 200, **self.params)
+
+ def test_show_host_with_str_body(self):
+ self._test_host_data('show')
+
+ def test_show_host_with_bytes_body(self):
+ self._test_host_data('show', True)
+
+ def test_list_host_with_str_body(self):
+ self._test_host_data()
+
+ def test_list_host_with_bytes_body(self):
+ self._test_host_data(bytes_body=True)
+
+ def test_start_host_with_str_body(self):
+ self._test_control_host('start')
+
+ def test_start_host_with_bytes_body(self):
+ self._test_control_host('start', True)
+
+ def test_stop_host_with_str_body(self):
+ self._test_control_host('stop')
+
+ def test_stop_host_with_bytes_body(self):
+ self._test_control_host('stop', True)
+
+ def test_reboot_host_with_str_body(self):
+ self._test_control_host('reboot')
+
+ def test_reboot_host_with_bytes_body(self):
+ self._test_control_host('reboot', True)
+
+ def test_update_host_with_str_body(self):
+ self._test_update_hosts()
+
+ def test_update_host_with_bytes_body(self):
+ self._test_update_hosts(True)
diff --git a/tempest/tests/lib/services/compute/test_hypervisor_client.py b/tempest/tests/lib/services/compute/test_hypervisor_client.py
new file mode 100644
index 0000000..fab34da
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_hypervisor_client.py
@@ -0,0 +1,167 @@
+# Copyright 2015 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# 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.compute import hypervisor_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestHypervisorClient(base.BaseComputeServiceTest):
+
+ hypervisor_id = "1"
+ hypervisor_name = "hyper.hostname.com"
+
+ def setUp(self):
+ super(TestHypervisorClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = hypervisor_client.HypervisorClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_hypervisor_str_body(self):
+ self._test_list_hypervisor(bytes_body=False)
+
+ def test_list_hypervisor_byte_body(self):
+ self._test_list_hypervisor(bytes_body=True)
+
+ def _test_list_hypervisor(self, bytes_body=False):
+ expected = {"hypervisors": [{
+ "id": 1,
+ "hypervisor_hostname": "hypervisor1.hostname.com"},
+ {
+ "id": 2,
+ "hypervisor_hostname": "hypervisor2.hostname.com"}]}
+ self.check_service_client_function(
+ self.client.list_hypervisors,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body)
+
+ def test_show_hypervisor_str_body(self):
+ self._test_show_hypervisor(bytes_body=False)
+
+ def test_show_hypervisor_byte_body(self):
+ self._test_show_hypervisor(bytes_body=True)
+
+ def _test_show_hypervisor(self, bytes_body=False):
+ expected = {"hypervisor": {
+ "cpu_info": "?",
+ "current_workload": 0,
+ "disk_available_least": 1,
+ "host_ip": "10.10.10.10",
+ "free_disk_gb": 1028,
+ "free_ram_mb": 7680,
+ "hypervisor_hostname": "fake-mini",
+ "hypervisor_type": "fake",
+ "hypervisor_version": 1,
+ "id": 1,
+ "local_gb": 1028,
+ "local_gb_used": 0,
+ "memory_mb": 8192,
+ "memory_mb_used": 512,
+ "running_vms": 0,
+ "service": {
+ "host": "fake_host",
+ "id": 2},
+ "vcpus": 1,
+ "vcpus_used": 0}}
+ self.check_service_client_function(
+ self.client.show_hypervisor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_id=self.hypervisor_id)
+
+ def test_list_servers_on_hypervisor_str_body(self):
+ self._test_list_servers_on_hypervisor(bytes_body=False)
+
+ def test_list_servers_on_hypervisor_byte_body(self):
+ self._test_list_servers_on_hypervisor(bytes_body=True)
+
+ def _test_list_servers_on_hypervisor(self, bytes_body=False):
+ expected = {"hypervisors": [{
+ "id": 1,
+ "hypervisor_hostname": "hyper.hostname.com",
+ "servers": [{
+ "uuid": "e1ae8fc4-b72d-4c2f-a427-30dd420b6277",
+ "name": "instance-00000001"},
+ {
+ "uuid": "e1ae8fc4-b72d-4c2f-a427-30dd42066666",
+ "name": "instance-00000002"}
+ ]}
+ ]}
+ self.check_service_client_function(
+ self.client.list_servers_on_hypervisor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_name=self.hypervisor_name)
+
+ def test_show_hypervisor_statistics_str_body(self):
+ self._test_show_hypervisor_statistics(bytes_body=False)
+
+ def test_show_hypervisor_statistics_byte_body(self):
+ self._test_show_hypervisor_statistics(bytes_body=True)
+
+ def _test_show_hypervisor_statistics(self, bytes_body=False):
+ expected = {
+ "hypervisor_statistics": {
+ "count": 1,
+ "current_workload": 0,
+ "disk_available_least": 0,
+ "free_disk_gb": 1028,
+ "free_ram_mb": 7680,
+ "local_gb": 1028,
+ "local_gb_used": 0,
+ "memory_mb": 8192,
+ "memory_mb_used": 512,
+ "running_vms": 0,
+ "vcpus": 1,
+ "vcpus_used": 0}}
+ self.check_service_client_function(
+ self.client.show_hypervisor_statistics,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body)
+
+ def test_show_hypervisor_uptime_str_body(self):
+ self._test_show_hypervisor_uptime(bytes_body=False)
+
+ def test_show_hypervisor_uptime_byte_body(self):
+ self._test_show_hypervisor_uptime(bytes_body=True)
+
+ def _test_show_hypervisor_uptime(self, bytes_body=False):
+ expected = {
+ "hypervisor": {
+ "hypervisor_hostname": "fake-mini",
+ "id": 1,
+ "uptime": (" 08:32:11 up 93 days, 18:25, 12 users, "
+ " load average: 0.20, 0.12, 0.14")
+ }}
+ self.check_service_client_function(
+ self.client.show_hypervisor_uptime,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_id=self.hypervisor_id)
+
+ def test_search_hypervisor_str_body(self):
+ self._test_search_hypervisor(bytes_body=False)
+
+ def test_search_hypervisor_byte_body(self):
+ self._test_search_hypervisor(bytes_body=True)
+
+ def _test_search_hypervisor(self, bytes_body=False):
+ expected = {"hypervisors": [{
+ "id": 2,
+ "hypervisor_hostname": "hyper.hostname.com"}]}
+ self.check_service_client_function(
+ self.client.search_hypervisor,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ hypervisor_name=self.hypervisor_name)
diff --git a/tempest/tests/lib/services/compute/test_images_client.py b/tempest/tests/lib/services/compute/test_images_client.py
new file mode 100644
index 0000000..28757c3
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_images_client.py
@@ -0,0 +1,265 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import images_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestImagesClient(base.BaseComputeServiceTest):
+ # Data Dictionaries used for testing #
+ FAKE_IMAGE_METADATA = {
+ "list":
+ {"metadata": {
+ "auto_disk_config": "True",
+ "Label": "Changed"
+ }},
+ "set_item":
+ {"meta": {
+ "auto_disk_config": "True"
+ }},
+ "show_item":
+ {"meta": {
+ "kernel_id": "nokernel",
+ }},
+ "update":
+ {"metadata": {
+ "kernel_id": "False",
+ "Label": "UpdatedImage"
+ }},
+ "set":
+ {"metadata": {
+ "Label": "Changed",
+ "auto_disk_config": "True"
+ }},
+ "delete_item": {}
+ }
+
+ FAKE_IMAGE_DATA = {
+ "list":
+ {"images": [
+ {"id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {"href": "http://openstack.example.com/v2/openstack" +
+ "/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "self"
+ }
+ ],
+ "name": "fakeimage7"
+ }]},
+ "show": {"image": {
+ "created": "2011-01-01T01:02:03Z",
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack" +
+ "/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "self"
+ },
+ ],
+ "metadata": {
+ "architecture": "x86_64",
+ "auto_disk_config": "True",
+ "kernel_id": "nokernel",
+ "ramdisk_id": "nokernel"
+ },
+ "minDisk": 0,
+ "minRam": 0,
+ "name": "fakeimage7",
+ "progress": 100,
+ "status": "ACTIVE",
+ "updated": "2011-01-01T01:02:03Z"}},
+ "create": {},
+ "delete": {}
+ }
+ func2mock = {
+ 'get': 'tempest.lib.common.rest_client.RestClient.get',
+ 'post': 'tempest.lib.common.rest_client.RestClient.post',
+ 'put': 'tempest.lib.common.rest_client.RestClient.put',
+ 'delete': 'tempest.lib.common.rest_client.RestClient.delete'}
+ # Variable definition
+ FAKE_IMAGE_ID = FAKE_IMAGE_DATA['show']['image']['id']
+ FAKE_SERVER_ID = "80a599e0-31e7-49b7-b260-868f441e343f"
+ FAKE_CREATE_INFO = {'location': 'None'}
+ FAKE_METADATA = FAKE_IMAGE_METADATA['show_item']['meta']
+
+ def setUp(self):
+ super(TestImagesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = images_client.ImagesClient(fake_auth,
+ "compute", "regionOne")
+
+ def _test_image_operation(self, operation="delete", bytes_body=False):
+ response_code = 200
+ mock_operation = self.func2mock['get']
+ expected_op = self.FAKE_IMAGE_DATA[operation]
+ params = {"image_id": self.FAKE_IMAGE_ID}
+ headers = None
+ if operation == 'list':
+ function = self.client.list_images
+ elif operation == 'show':
+ function = self.client.show_image
+ elif operation == 'create':
+ function = self.client.create_image
+ mock_operation = self.func2mock['post']
+ params = {"server_id": self.FAKE_SERVER_ID}
+ response_code = 202
+ headers = {
+ 'connection': 'keep-alive',
+ 'content-length': '0',
+ 'content-type': 'application/json',
+ 'status': '202',
+ 'x-compute-request-id': 'req-fake',
+ 'vary': 'accept-encoding',
+ 'x-openstack-nova-api-version': 'v2.1',
+ 'date': '13 Oct 2015 05:55:36 GMT',
+ 'location': 'http://fake.com/images/fake'
+ }
+ else:
+ function = self.client.delete_image
+ mock_operation = self.func2mock['delete']
+ response_code = 204
+
+ self.check_service_client_function(
+ function, mock_operation, expected_op,
+ bytes_body, response_code, headers, **params)
+
+ def _test_image_metadata(self, operation="set_item", bytes_body=False):
+ response_code = 200
+ expected_op = self.FAKE_IMAGE_METADATA[operation]
+ if operation == 'list':
+ function = self.client.list_image_metadata
+ mock_operation = self.func2mock['get']
+ params = {"image_id": self.FAKE_IMAGE_ID}
+
+ elif operation == 'set':
+ function = self.client.set_image_metadata
+ mock_operation = self.func2mock['put']
+ params = {"image_id": "_dummy_data",
+ "meta": self.FAKE_METADATA}
+
+ elif operation == 'update':
+ function = self.client.update_image_metadata
+ mock_operation = self.func2mock['post']
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "meta": self.FAKE_METADATA}
+
+ elif operation == 'show_item':
+ mock_operation = self.func2mock['get']
+ function = self.client.show_image_metadata_item
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "key": "123"}
+
+ elif operation == 'delete_item':
+ function = self.client.delete_image_metadata_item
+ mock_operation = self.func2mock['delete']
+ response_code = 204
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "key": "123"}
+
+ else:
+ function = self.client.set_image_metadata_item
+ mock_operation = self.func2mock['put']
+ params = {"image_id": self.FAKE_IMAGE_ID,
+ "key": "123",
+ "meta": self.FAKE_METADATA}
+
+ self.check_service_client_function(
+ function, mock_operation, expected_op,
+ bytes_body, response_code, **params)
+
+ def _test_resource_deleted(self, bytes_body=False):
+ params = {"id": self.FAKE_IMAGE_ID}
+ expected_op = self.FAKE_IMAGE_DATA['show']['image']
+ self.useFixture(mockpatch.Patch('tempest.lib.services.compute'
+ '.images_client.ImagesClient.show_image',
+ side_effect=lib_exc.NotFound))
+ self.assertEqual(True, self.client.is_resource_deleted(**params))
+ tempdata = copy.deepcopy(self.FAKE_IMAGE_DATA['show'])
+ tempdata['image']['id'] = None
+ self.useFixture(mockpatch.Patch('tempest.lib.services.compute'
+ '.images_client.ImagesClient.show_image',
+ return_value=expected_op))
+ self.assertEqual(False, self.client.is_resource_deleted(**params))
+
+ def test_list_images_with_str_body(self):
+ self._test_image_operation('list')
+
+ def test_list_images_with_bytes_body(self):
+ self._test_image_operation('list', True)
+
+ def test_show_image_with_str_body(self):
+ self._test_image_operation('show')
+
+ def test_show_image_with_bytes_body(self):
+ self._test_image_operation('show', True)
+
+ def test_create_image_with_str_body(self):
+ self._test_image_operation('create')
+
+ def test_create_image_with_bytes_body(self):
+ self._test_image_operation('create', True)
+
+ def test_delete_image_with_str_body(self):
+ self._test_image_operation('delete')
+
+ def test_delete_image_with_bytes_body(self):
+ self._test_image_operation('delete', True)
+
+ def test_list_image_metadata_with_str_body(self):
+ self._test_image_metadata('list')
+
+ def test_list_image_metadata_with_bytes_body(self):
+ self._test_image_metadata('list', True)
+
+ def test_set_image_metadata_with_str_body(self):
+ self._test_image_metadata('set')
+
+ def test_set_image_metadata_with_bytes_body(self):
+ self._test_image_metadata('set', True)
+
+ def test_update_image_metadata_with_str_body(self):
+ self._test_image_metadata('update')
+
+ def test_update_image_metadata_with_bytes_body(self):
+ self._test_image_metadata('update', True)
+
+ def test_set_image_metadata_item_with_str_body(self):
+ self._test_image_metadata()
+
+ def test_set_image_metadata_item_with_bytes_body(self):
+ self._test_image_metadata(bytes_body=True)
+
+ def test_show_image_metadata_item_with_str_body(self):
+ self._test_image_metadata('show_item')
+
+ def test_show_image_metadata_item_with_bytes_body(self):
+ self._test_image_metadata('show_item', True)
+
+ def test_delete_image_metadata_item_with_str_body(self):
+ self._test_image_metadata('delete_item')
+
+ def test_delete_image_metadata_item_with_bytes_body(self):
+ self._test_image_metadata('delete_item', True)
+
+ def test_resource_delete_with_str_body(self):
+ self._test_resource_deleted()
+
+ def test_resource_delete_with_bytes_body(self):
+ self._test_resource_deleted(True)
diff --git a/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py b/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py
new file mode 100644
index 0000000..e8c22f1
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py
@@ -0,0 +1,73 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.lib.services.compute import instance_usage_audit_log_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestInstanceUsagesAuditLogClient(base.BaseComputeServiceTest):
+
+ FAKE_AUDIT_LOG = {
+ "hosts_not_run": [
+ "f4eb7cfd155f4574967f8b55a7faed75"
+ ],
+ "log": {},
+ "num_hosts": 1,
+ "num_hosts_done": 0,
+ "num_hosts_not_run": 1,
+ "num_hosts_running": 0,
+ "overall_status": "0 of 1 hosts done. 0 errors.",
+ "period_beginning": "2012-12-01 00:00:00",
+ "period_ending": "2013-01-01 00:00:00",
+ "total_errors": 0,
+ "total_instances": 0
+ }
+
+ def setUp(self):
+ super(TestInstanceUsagesAuditLogClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = (instance_usage_audit_log_client.
+ InstanceUsagesAuditLogClient(fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_list_instance_usage_audit_logs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_instance_usage_audit_logs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"instance_usage_audit_logs": self.FAKE_AUDIT_LOG},
+ bytes_body)
+
+ def test_list_instance_usage_audit_logs_with_str_body(self):
+ self._test_list_instance_usage_audit_logs()
+
+ def test_list_instance_usage_audit_logs_with_bytes_body(self):
+ self._test_list_instance_usage_audit_logs(bytes_body=True)
+
+ def _test_show_instance_usage_audit_log(self, bytes_body=False):
+ before_time = datetime.datetime(2012, 12, 1, 0, 0)
+ self.check_service_client_function(
+ self.client.show_instance_usage_audit_log,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"instance_usage_audit_log": self.FAKE_AUDIT_LOG},
+ bytes_body,
+ time_before=before_time)
+
+ def test_show_instance_usage_audit_log_with_str_body(self):
+ self._test_show_instance_usage_audit_log()
+
+ def test_show_network_with_bytes_body_with_bytes_body(self):
+ self._test_show_instance_usage_audit_log(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_interfaces_client.py b/tempest/tests/lib/services/compute/test_interfaces_client.py
new file mode 100644
index 0000000..de8e268
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_interfaces_client.py
@@ -0,0 +1,98 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import interfaces_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestInterfacesClient(base.BaseComputeServiceTest):
+ # Data Values to be used for testing #
+ FAKE_INTERFACE_DATA = {
+ "fixed_ips": [{
+ "ip_address": "192.168.1.1",
+ "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef"
+ }],
+ "mac_addr": "fa:16:3e:4c:2c:30",
+ "net_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6",
+ "port_id": "ce531f90-199f-48c0-816c-13e38010b442",
+ "port_state": "ACTIVE"}
+
+ FAKE_SHOW_DATA = {
+ "interfaceAttachment": FAKE_INTERFACE_DATA}
+ FAKE_LIST_DATA = {
+ "interfaceAttachments": [FAKE_INTERFACE_DATA]}
+
+ FAKE_SERVER_ID = "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5"
+ FAKE_PORT_ID = FAKE_SHOW_DATA['interfaceAttachment']['port_id']
+ func2mock = {
+ 'delete': 'tempest.lib.common.rest_client.RestClient.delete',
+ 'get': 'tempest.lib.common.rest_client.RestClient.get',
+ 'post': 'tempest.lib.common.rest_client.RestClient.post'}
+
+ def setUp(self):
+ super(TestInterfacesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = interfaces_client.InterfacesClient(fake_auth,
+ "compute",
+ "regionOne")
+
+ def _test_interface_operation(self, operation="create", bytes_body=False):
+ response_code = 200
+ expected_op = self.FAKE_SHOW_DATA
+ mock_operation = self.func2mock['get']
+ params = {'server_id': self.FAKE_SERVER_ID,
+ 'port_id': self.FAKE_PORT_ID}
+ if operation == 'list':
+ expected_op = self.FAKE_LIST_DATA
+ function = self.client.list_interfaces
+ params = {'server_id': self.FAKE_SERVER_ID}
+ elif operation == 'show':
+ function = self.client.show_interface
+ elif operation == 'delete':
+ expected_op = {}
+ mock_operation = self.func2mock['delete']
+ function = self.client.delete_interface
+ response_code = 202
+ else:
+ function = self.client.create_interface
+ mock_operation = self.func2mock['post']
+
+ self.check_service_client_function(
+ function, mock_operation, expected_op,
+ bytes_body, response_code, **params)
+
+ def test_list_interfaces_with_str_body(self):
+ self._test_interface_operation('list')
+
+ def test_list_interfaces_with_bytes_body(self):
+ self._test_interface_operation('list', True)
+
+ def test_show_interface_with_str_body(self):
+ self._test_interface_operation('show')
+
+ def test_show_interface_with_bytes_body(self):
+ self._test_interface_operation('show', True)
+
+ def test_delete_interface_with_str_body(self):
+ self._test_interface_operation('delete')
+
+ def test_delete_interface_with_bytes_body(self):
+ self._test_interface_operation('delete', True)
+
+ def test_create_interface_with_str_body(self):
+ self._test_interface_operation()
+
+ def test_create_interface_with_bytes_body(self):
+ self._test_interface_operation(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_keypairs_client.py b/tempest/tests/lib/services/compute/test_keypairs_client.py
new file mode 100644
index 0000000..7c595ca
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_keypairs_client.py
@@ -0,0 +1,94 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import keypairs_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestKeyPairsClient(base.BaseComputeServiceTest):
+
+ FAKE_KEYPAIR = {"keypair": {
+ "public_key": "ssh-rsa foo Generated-by-Nova",
+ "name": u'\u2740(*\xb4\u25e1`*)\u2740',
+ "user_id": "525d55f98980415ba98e634972fa4a10",
+ "fingerprint": "76:24:66:49:d7:ca:6e:5c:77:ea:8e:bb:9c:15:5f:98"
+ }}
+
+ def setUp(self):
+ super(TestKeyPairsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = keypairs_client.KeyPairsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_keypairs(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_keypairs,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"keypairs": []},
+ bytes_body)
+
+ def test_list_keypairs_with_str_body(self):
+ self._test_list_keypairs()
+
+ def test_list_keypairs_with_bytes_body(self):
+ self._test_list_keypairs(bytes_body=True)
+
+ def _test_show_keypair(self, bytes_body=False):
+ fake_keypair = copy.deepcopy(self.FAKE_KEYPAIR)
+ fake_keypair["keypair"].update({
+ "deleted": False,
+ "created_at": "2015-07-22T04:53:52.000000",
+ "updated_at": None,
+ "deleted_at": None,
+ "id": 1
+ })
+
+ self.check_service_client_function(
+ self.client.show_keypair,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_keypair,
+ bytes_body,
+ keypair_name="test")
+
+ def test_show_keypair_with_str_body(self):
+ self._test_show_keypair()
+
+ def test_show_keypair_with_bytes_body(self):
+ self._test_show_keypair(bytes_body=True)
+
+ def _test_create_keypair(self, bytes_body=False):
+ fake_keypair = copy.deepcopy(self.FAKE_KEYPAIR)
+ fake_keypair["keypair"].update({"private_key": "foo"})
+
+ self.check_service_client_function(
+ self.client.create_keypair,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ fake_keypair,
+ bytes_body,
+ name="test")
+
+ def test_create_keypair_with_str_body(self):
+ self._test_create_keypair()
+
+ def test_create_keypair_with_bytes_body(self):
+ self._test_create_keypair(bytes_body=True)
+
+ def test_delete_keypair(self):
+ self.check_service_client_function(
+ self.client.delete_keypair,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, keypair_name='test')
diff --git a/tempest/tests/lib/services/compute/test_limits_client.py b/tempest/tests/lib/services/compute/test_limits_client.py
new file mode 100644
index 0000000..d3f0aee
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_limits_client.py
@@ -0,0 +1,66 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import limits_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestLimitsClient(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestLimitsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = limits_client.LimitsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_limits(self, bytes_body=False):
+ expected = {
+ "limits": {
+ "rate": [],
+ "absolute": {
+ "maxServerMeta": 128,
+ "maxPersonality": 5,
+ "totalServerGroupsUsed": 0,
+ "maxImageMeta": 128,
+ "maxPersonalitySize": 10240,
+ "maxServerGroups": 10,
+ "maxSecurityGroupRules": 20,
+ "maxTotalKeypairs": 100,
+ "totalCoresUsed": 0,
+ "totalRAMUsed": 0,
+ "totalInstancesUsed": 0,
+ "maxSecurityGroups": 10,
+ "totalFloatingIpsUsed": 0,
+ "maxTotalCores": 20,
+ "totalSecurityGroupsUsed": 0,
+ "maxTotalFloatingIps": 10,
+ "maxTotalInstances": 10,
+ "maxTotalRAMSize": 51200,
+ "maxServerGroupMembers": 10
+ }
+ }
+ }
+
+ self.check_service_client_function(
+ self.client.show_limits,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected,
+ bytes_body)
+
+ def test_show_limits_with_str_body(self):
+ self._test_show_limits()
+
+ def test_show_limits_with_bytes_body(self):
+ self._test_show_limits(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_migrations_client.py b/tempest/tests/lib/services/compute/test_migrations_client.py
new file mode 100644
index 0000000..5b1578d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_migrations_client.py
@@ -0,0 +1,52 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import migrations_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestMigrationsClient(base.BaseComputeServiceTest):
+ FAKE_MIGRATION_INFO = {"migrations": [{
+ "created_at": "2012-10-29T13:42:02",
+ "dest_compute": "compute2",
+ "dest_host": "1.2.3.4",
+ "dest_node": "node2",
+ "id": 1234,
+ "instance_uuid": "e9e4fdd7-f956-44ff-bfeb-d654a96ab3a2",
+ "new_instance_type_id": 2,
+ "old_instance_type_id": 1,
+ "source_compute": "compute1",
+ "source_node": "node1",
+ "status": "finished",
+ "updated_at": "2012-10-29T13:42:02"}]}
+
+ def setUp(self):
+ super(TestMigrationsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.mg_client_obj = migrations_client.MigrationsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_migrations(self, bytes_body=False):
+ self.check_service_client_function(
+ self.mg_client_obj.list_migrations,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_MIGRATION_INFO,
+ bytes_body)
+
+ def test_list_migration_with_str_body(self):
+ self._test_list_migrations()
+
+ def test_list_migration_with_bytes_body(self):
+ self._test_list_migrations(True)
diff --git a/tempest/tests/lib/services/compute/test_networks_client.py b/tempest/tests/lib/services/compute/test_networks_client.py
new file mode 100644
index 0000000..4f5c8b9
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_networks_client.py
@@ -0,0 +1,94 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestNetworksClient(base.BaseComputeServiceTest):
+
+ FAKE_NETWORK = {
+ "bridge": None,
+ "vpn_public_port": None,
+ "dhcp_start": None,
+ "bridge_interface": None,
+ "share_address": None,
+ "updated_at": None,
+ "id": "34d5ae1e-5659-49cf-af80-73bccd7d7ad3",
+ "cidr_v6": None,
+ "deleted_at": None,
+ "gateway": None,
+ "rxtx_base": None,
+ "label": u'30d7',
+ "priority": None,
+ "project_id": None,
+ "vpn_private_address": None,
+ "deleted": None,
+ "vlan": None,
+ "broadcast": None,
+ "netmask": None,
+ "injected": None,
+ "cidr": None,
+ "vpn_public_address": None,
+ "multi_host": None,
+ "enable_dhcp": None,
+ "dns2": None,
+ "created_at": None,
+ "host": None,
+ "mtu": None,
+ "gateway_v6": None,
+ "netmask_v6": None,
+ "dhcp_server": None,
+ "dns1": None
+ }
+
+ network_id = "34d5ae1e-5659-49cf-af80-73bccd7d7ad3"
+
+ FAKE_NETWORKS = [FAKE_NETWORK]
+
+ def setUp(self):
+ super(TestNetworksClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = networks_client.NetworksClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_networks(self, bytes_body=False):
+ fake_list = {"networks": self.FAKE_NETWORKS}
+ self.check_service_client_function(
+ self.client.list_networks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_list,
+ bytes_body)
+
+ def test_list_networks_with_str_body(self):
+ self._test_list_networks()
+
+ def test_list_networks_with_bytes_body(self):
+ self._test_list_networks(bytes_body=True)
+
+ def _test_show_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_network,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"network": self.FAKE_NETWORK},
+ bytes_body,
+ network_id=self.network_id
+ )
+
+ def test_show_network_with_str_body(self):
+ self._test_show_network()
+
+ def test_show_network_with_bytes_body(self):
+ self._test_show_network(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_quota_classes_client.py b/tempest/tests/lib/services/compute/test_quota_classes_client.py
new file mode 100644
index 0000000..4b67576
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_quota_classes_client.py
@@ -0,0 +1,71 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import quota_classes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestQuotaClassesClient(base.BaseComputeServiceTest):
+
+ FAKE_QUOTA_CLASS_SET = {
+ "injected_file_content_bytes": 10240,
+ "metadata_items": 128,
+ "server_group_members": 10,
+ "server_groups": 10,
+ "ram": 51200,
+ "floating_ips": 10,
+ "key_pairs": 100,
+ "id": u'\u2740(*\xb4\u25e1`*)\u2740',
+ "instances": 10,
+ "security_group_rules": 20,
+ "security_groups": 10,
+ "injected_files": 5,
+ "cores": 20,
+ "fixed_ips": -1,
+ "injected_file_path_bytes": 255,
+ }
+
+ def setUp(self):
+ super(TestQuotaClassesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = quota_classes_client.QuotaClassesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_quota_class_set(self, bytes_body=False):
+ fake_body = {'quota_class_set': self.FAKE_QUOTA_CLASS_SET}
+ self.check_service_client_function(
+ self.client.show_quota_class_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_body,
+ bytes_body,
+ quota_class_id="test")
+
+ def test_show_quota_class_set_with_str_body(self):
+ self._test_show_quota_class_set()
+
+ def test_show_quota_class_set_with_bytes_body(self):
+ self._test_show_quota_class_set(bytes_body=True)
+
+ def test_update_quota_class_set(self):
+ fake_quota_class_set = copy.deepcopy(self.FAKE_QUOTA_CLASS_SET)
+ fake_quota_class_set.pop("id")
+ fake_body = {'quota_class_set': fake_quota_class_set}
+ self.check_service_client_function(
+ self.client.update_quota_class_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_body,
+ quota_class_id="test")
diff --git a/tempest/tests/lib/services/compute/test_quotas_client.py b/tempest/tests/lib/services/compute/test_quotas_client.py
new file mode 100644
index 0000000..9f5d1f6
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_quotas_client.py
@@ -0,0 +1,130 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import quotas_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestQuotasClient(base.BaseComputeServiceTest):
+
+ FAKE_QUOTA_SET = {
+ "quota_set": {
+ "injected_file_content_bytes": 10240,
+ "metadata_items": 128,
+ "server_group_members": 10,
+ "server_groups": 10,
+ "ram": 51200,
+ "floating_ips": 10,
+ "key_pairs": 100,
+ "id": "8421f7be61064f50b680465c07f334af",
+ "instances": 10,
+ "security_group_rules": 20,
+ "injected_files": 5,
+ "cores": 20,
+ "fixed_ips": -1,
+ "injected_file_path_bytes": 255,
+ "security_groups": 10}
+ }
+
+ project_id = "8421f7be61064f50b680465c07f334af"
+ fake_user_id = "65f09168cbb04eb593f3138b63b67b67"
+
+ def setUp(self):
+ super(TestQuotasClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = quotas_client.QuotasClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_show_quota_set(self, bytes_body=False, user_id=None):
+ if user_id:
+ self.check_service_client_function(
+ self.client.show_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_QUOTA_SET,
+ to_utf=bytes_body,
+ tenant_id=self.project_id,
+ user_id=user_id)
+ else:
+ self.check_service_client_function(
+ self.client.show_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_QUOTA_SET,
+ to_utf=bytes_body,
+ tenant_id=self.project_id)
+
+ def test_show_quota_set_with_str_body(self):
+ self._test_show_quota_set()
+
+ def test_show_quota_set_with_bytes_body(self):
+ self._test_show_quota_set(bytes_body=True)
+
+ def test_show_quota_set_for_user_with_str_body(self):
+ self._test_show_quota_set(user_id=self.fake_user_id)
+
+ def test_show_quota_set_for_user_with_bytes_body(self):
+ self._test_show_quota_set(bytes_body=True, user_id=self.fake_user_id)
+
+ def _test_show_default_quota_set(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_default_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_QUOTA_SET,
+ to_utf=bytes_body,
+ tenant_id=self.project_id)
+
+ def test_show_default_quota_set_with_str_body(self):
+ self._test_show_default_quota_set()
+
+ def test_show_default_quota_set_with_bytes_body(self):
+ self._test_show_default_quota_set(bytes_body=True)
+
+ def _test_update_quota_set(self, bytes_body=False, user_id=None):
+ fake_quota_set = copy.deepcopy(self.FAKE_QUOTA_SET)
+ fake_quota_set['quota_set'].pop("id")
+ if user_id:
+ self.check_service_client_function(
+ self.client.update_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_quota_set,
+ to_utf=bytes_body,
+ tenant_id=self.project_id,
+ user_id=user_id)
+ else:
+ self.check_service_client_function(
+ self.client.update_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_quota_set,
+ to_utf=bytes_body,
+ tenant_id=self.project_id)
+
+ def test_update_quota_set_with_str_body(self):
+ self._test_update_quota_set()
+
+ def test_update_quota_set_with_bytes_body(self):
+ self._test_update_quota_set(bytes_body=True)
+
+ def test_update_quota_set_for_user_with_str_body(self):
+ self._test_update_quota_set(user_id=self.fake_user_id)
+
+ def test_update_quota_set_for_user_with_bytes_body(self):
+ self._test_update_quota_set(bytes_body=True, user_id=self.fake_user_id)
+
+ def test_delete_quota_set(self):
+ self.check_service_client_function(
+ self.client.delete_quota_set,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, tenant_id=self.project_id)
diff --git a/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py b/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py
new file mode 100644
index 0000000..581f7b1
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py
@@ -0,0 +1,88 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import security_group_default_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSecurityGroupDefaultRulesClient(base.BaseComputeServiceTest):
+ FAKE_RULE = {
+ "from_port": 80,
+ "id": 1,
+ "ip_protocol": "TCP",
+ "ip_range": {
+ "cidr": "10.10.10.0/24"
+ },
+ "to_port": 80
+ }
+
+ def setUp(self):
+ super(TestSecurityGroupDefaultRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = (security_group_default_rules_client.
+ SecurityGroupDefaultRulesClient(fake_auth, 'compute',
+ 'regionOne'))
+
+ def _test_list_security_group_default_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_security_group_default_rules,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_group_default_rules": [self.FAKE_RULE]},
+ to_utf=bytes_body)
+
+ def test_list_security_group_default_rules_with_str_body(self):
+ self._test_list_security_group_default_rules()
+
+ def test_list_security_group_default_rules_with_bytes_body(self):
+ self._test_list_security_group_default_rules(bytes_body=True)
+
+ def _test_show_security_group_default_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_security_group_default_rule,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_group_default_rule": self.FAKE_RULE},
+ to_utf=bytes_body,
+ security_group_default_rule_id=1)
+
+ def test_show_security_group_default_rule_with_str_body(self):
+ self._test_show_security_group_default_rule()
+
+ def test_show_security_group_default_rule_with_bytes_body(self):
+ self._test_show_security_group_default_rule(bytes_body=True)
+
+ def _test_create_security_default_group_rule(self, bytes_body=False):
+ request_body = {
+ "to_port": 80,
+ "from_port": 80,
+ "ip_protocol": "TCP",
+ "cidr": "10.10.10.0/24"
+ }
+ self.check_service_client_function(
+ self.client.create_security_default_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"security_group_default_rule": self.FAKE_RULE},
+ to_utf=bytes_body, **request_body)
+
+ def test_create_security_default_group_rule_with_str_body(self):
+ self._test_create_security_default_group_rule()
+
+ def test_create_security_default_group_rule_with_bytes_body(self):
+ self._test_create_security_default_group_rule(bytes_body=True)
+
+ def test_delete_security_group_default_rule(self):
+ self.check_service_client_function(
+ self.client.delete_security_group_default_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=204, security_group_default_rule_id=1)
diff --git a/tempest/tests/lib/services/compute/test_security_group_rules_client.py b/tempest/tests/lib/services/compute/test_security_group_rules_client.py
new file mode 100644
index 0000000..9a7c04d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_security_group_rules_client.py
@@ -0,0 +1,66 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import security_group_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSecurityGroupRulesClient(base.BaseComputeServiceTest):
+
+ FAKE_SECURITY_GROUP_RULE = {
+ "security_group_rule": {
+ "id": "2d021cf1-ce4b-4292-994f-7a785d62a144",
+ "ip_range": {
+ "cidr": "0.0.0.0/0"
+ },
+ "parent_group_id": "48700ff3-30b8-4e63-845f-a79c9633e9fb",
+ "to_port": 443,
+ "ip_protocol": "tcp",
+ "group": {},
+ "from_port": 443
+ }
+ }
+
+ def setUp(self):
+ super(TestSecurityGroupRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = security_group_rules_client.SecurityGroupRulesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_security_group_rule(self, bytes_body=False):
+ req_body = {
+ "from_port": "443",
+ "ip_protocol": "tcp",
+ "to_port": "443",
+ "cidr": "0.0.0.0/0",
+ "parent_group_id": "48700ff3-30b8-4e63-845f-a79c9633e9fb"
+ }
+ self.check_service_client_function(
+ self.client.create_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SECURITY_GROUP_RULE,
+ to_utf=bytes_body, **req_body)
+
+ def test_create_security_group_rule_with_str_body(self):
+ self._test_create_security_group_rule()
+
+ def test_create_security_group_rule_with_bytes_body(self):
+ self._test_create_security_group_rule(bytes_body=True)
+
+ def test_delete_security_group_rule(self):
+ self.check_service_client_function(
+ self.client.delete_security_group_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, group_rule_id='group-id')
diff --git a/tempest/tests/lib/services/compute/test_security_groups_client.py b/tempest/tests/lib/services/compute/test_security_groups_client.py
new file mode 100644
index 0000000..6a11c29
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_security_groups_client.py
@@ -0,0 +1,113 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import security_groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSecurityGroupsClient(base.BaseComputeServiceTest):
+
+ FAKE_SECURITY_GROUP_INFO = [{
+ "description": "default",
+ "id": "3fb26eb3-581b-4420-9963-b0879a026506",
+ "name": "default",
+ "rules": [],
+ "tenant_id": "openstack"
+ }]
+
+ def setUp(self):
+ super(TestSecurityGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = security_groups_client.SecurityGroupsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_security_groups(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_security_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_groups": self.FAKE_SECURITY_GROUP_INFO},
+ to_utf=bytes_body)
+
+ def test_list_security_groups_with_str_body(self):
+ self._test_list_security_groups()
+
+ def test_list_security_groups_with_bytes_body(self):
+ self._test_list_security_groups(bytes_body=True)
+
+ def _test_show_security_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_security_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"security_group": self.FAKE_SECURITY_GROUP_INFO[0]},
+ to_utf=bytes_body,
+ security_group_id='fake-id')
+
+ def test_show_security_group_with_str_body(self):
+ self._test_show_security_group()
+
+ def test_show_security_group_with_bytes_body(self):
+ self._test_show_security_group(bytes_body=True)
+
+ def _test_create_security_group(self, bytes_body=False):
+ post_body = {"name": "test", "description": "test_group"}
+ self.check_service_client_function(
+ self.client.create_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"security_group": self.FAKE_SECURITY_GROUP_INFO[0]},
+ to_utf=bytes_body,
+ kwargs=post_body)
+
+ def test_create_security_group_with_str_body(self):
+ self._test_create_security_group()
+
+ def test_create_security_group_with_bytes_body(self):
+ self._test_create_security_group(bytes_body=True)
+
+ def _test_update_security_group(self, bytes_body=False):
+ req_body = {"name": "test", "description": "test_group"}
+ self.check_service_client_function(
+ self.client.update_security_group,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {"security_group": self.FAKE_SECURITY_GROUP_INFO[0]},
+ to_utf=bytes_body,
+ security_group_id='fake-id',
+ kwargs=req_body)
+
+ def test_update_security_group_with_str_body(self):
+ self._test_update_security_group()
+
+ def test_update_security_group_with_bytes_body(self):
+ self._test_update_security_group(bytes_body=True)
+
+ def test_delete_security_group(self):
+ self.check_service_client_function(
+ self.client.delete_security_group,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, security_group_id='fake-id')
+
+ def test_is_resource_deleted_true(self):
+ mod = ('tempest.lib.services.compute.security_groups_client.'
+ 'SecurityGroupsClient.show_security_group')
+ self.useFixture(mockpatch.Patch(mod, side_effect=lib_exc.NotFound))
+ self.assertTrue(self.client.is_resource_deleted('fake-id'))
+
+ def test_is_resource_deleted_false(self):
+ mod = ('tempest.lib.services.compute.security_groups_client.'
+ 'SecurityGroupsClient.show_security_group')
+ self.useFixture(mockpatch.Patch(mod, return_value='success'))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_server_groups_client.py b/tempest/tests/lib/services/compute/test_server_groups_client.py
new file mode 100644
index 0000000..f1f2906
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_server_groups_client.py
@@ -0,0 +1,84 @@
+# Copyright 2015 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# 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 httplib2
+
+from oslotest import mockpatch
+from tempest.tests.lib import fake_auth_provider
+
+from tempest.lib.services.compute import server_groups_client
+from tempest.tests.lib.services.compute import base
+
+
+class TestServerGroupsClient(base.BaseComputeServiceTest):
+
+ server_group = {
+ "id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
+ "name": "test",
+ "policies": ["anti-affinity"],
+ "members": [],
+ "metadata": {}}
+
+ def setUp(self):
+ super(TestServerGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = server_groups_client.ServerGroupsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_server_group(self, bytes_body=False):
+ expected = {"server_group": TestServerGroupsClient.server_group}
+ self.check_service_client_function(
+ self.client.create_server_group,
+ 'tempest.lib.common.rest_client.RestClient.post', expected,
+ bytes_body, name='fake-group', policies=['affinity'])
+
+ def test_create_server_group_str_body(self):
+ self._test_create_server_group(bytes_body=False)
+
+ def test_create_server_group_byte_body(self):
+ self._test_create_server_group(bytes_body=True)
+
+ def test_delete_server_group(self):
+ response = (httplib2.Response({'status': 204}), None)
+ self.useFixture(mockpatch.Patch(
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ return_value=response))
+ self.client.delete_server_group('fake-group')
+
+ def _test_list_server_groups(self, bytes_body=False):
+ expected = {"server_groups": [TestServerGroupsClient.server_group]}
+ self.check_service_client_function(
+ self.client.list_server_groups,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body)
+
+ def test_list_server_groups_str_body(self):
+ self._test_list_server_groups(bytes_body=False)
+
+ def test_list_server_groups_byte_body(self):
+ self._test_list_server_groups(bytes_body=True)
+
+ def _test_show_server_group(self, bytes_body=False):
+ expected = {"server_group": TestServerGroupsClient.server_group}
+ self.check_service_client_function(
+ self.client.show_server_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ expected, bytes_body,
+ server_group_id='5bbcc3c4-1da2-4437-a48a-66f15b1b13f9')
+
+ def test_show_server_group_str_body(self):
+ self._test_show_server_group(bytes_body=False)
+
+ def test_show_server_group_byte_body(self):
+ self._test_show_server_group(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
new file mode 100644
index 0000000..0078497
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -0,0 +1,1011 @@
+# Copyright 2015 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import servers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestServersClient(base.BaseComputeServiceTest):
+
+ FAKE_SERVERS = {'servers': [{
+ "id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "links": [
+ {
+ "href": "http://os.co/v2/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "self"
+ },
+ {
+ "href": "http://os.co/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "bookmark"
+ }
+ ],
+ "name": u"new\u1234-server-test"}]
+ }
+
+ FAKE_SERVER_DIAGNOSTICS = {
+ "cpu0_time": 17300000000,
+ "memory": 524288,
+ "vda_errors": -1,
+ "vda_read": 262144,
+ "vda_read_req": 112,
+ "vda_write": 5778432,
+ "vda_write_req": 488,
+ "vnet1_rx": 2070139,
+ "vnet1_rx_drop": 0,
+ "vnet1_rx_errors": 0,
+ "vnet1_rx_packets": 26701,
+ "vnet1_tx": 140208,
+ "vnet1_tx_drop": 0,
+ "vnet1_tx_errors": 0,
+ "vnet1_tx_packets": 662
+ }
+
+ FAKE_SERVER_GET = {'server': {
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]
+ },
+ "created": "2012-08-20T21:11:09Z",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://os.com/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "65201c14a29663e06d0748e561207d998b343e1d164bfa0aafa9c45d",
+ "id": "893c7791-f1df-4c3d-8383-3caae9656c62",
+ "image": {
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://imgs/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://v2/srvs/893c7791-f1df-4c3d-8383-3caae9656c62",
+ "rel": "self"
+ },
+ {
+ "href": "http://srvs/893c7791-f1df-4c3d-8383-3caae9656c62",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ u"My Server N\u1234me": u"Apa\u1234che1"
+ },
+ "name": u"new\u1234-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "2012-08-20T21:11:09Z",
+ "user_id": "fake"}
+ }
+
+ FAKE_SERVER_POST = {"server": {
+ "id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "adminPass": "fake-admin-pass",
+ "security_groups": [
+ 'fake-security-group-1',
+ 'fake-security-group-2'
+ ],
+ "links": [
+ {
+ "href": "http://os.co/v2/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "self"
+ },
+ {
+ "href": "http://os.co/616fb98f-46ca-475e-917e-2563e5a8cd19",
+ "rel": "bookmark"
+ }
+ ],
+ "OS-DCF:diskConfig": "fake-disk-config"}
+ }
+
+ FAKE_ADDRESS = {"addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]}
+ }
+
+ FAKE_COMMON_VOLUME = {
+ "id": "a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb",
+ "device": "fake-device",
+ "volumeId": "a6b0875b-46ca-475e-917e-0c3aa62e5fdb",
+ "serverId": "616fb98f-46ca-475e-917e-2563e5a8cd19"
+ }
+
+ FAKE_VIRTUAL_INTERFACES = {
+ "id": "a6b0875b-46ca-475e-917e-0c3aa62e5fdb",
+ "mac_address": "00:25:90:5b:f8:c3",
+ "OS-EXT-VIF-NET:net_id": "fake-os-net-id"
+ }
+
+ FAKE_INSTANCE_ACTIONS = {
+ "action": "fake-action",
+ "request_id": "16fb98f-46ca-475e-917e-2563e5a8cd19",
+ "user_id": "16fb98f-46ca-475e-917e-2563e5a8cd12",
+ "project_id": "16fb98f-46ca-475e-917e-2563e5a8cd34",
+ "start_time": "09MAR2015 11:15",
+ "message": "fake-msg",
+ "instance_uuid": "16fb98f-46ca-475e-917e-2563e5a8cd12"
+ }
+
+ FAKE_VNC_CONSOLE = {
+ "type": "fake-type",
+ "url": "http://os.co/v2/616fb98f-46ca-475e-917e-2563e5a8cd19"
+ }
+
+ FAKE_INSTANCE_ACTION_EVENTS = {
+ "event": "fake-event",
+ "start_time": "09MAR2015 11:15",
+ "finish_time": "09MAR2015 11:15",
+ "result": "fake-result",
+ "traceback": "fake-trace-back"
+ }
+
+ FAKE_INSTANCE_WITH_EVENTS = copy.deepcopy(FAKE_INSTANCE_ACTIONS)
+ FAKE_INSTANCE_WITH_EVENTS['events'] = [FAKE_INSTANCE_ACTION_EVENTS]
+
+ FAKE_REBUILD_SERVER = copy.deepcopy(FAKE_SERVER_GET)
+ FAKE_REBUILD_SERVER['server']['adminPass'] = 'fake-admin-pass'
+
+ server_id = FAKE_SERVER_GET['server']['id']
+ network_id = 'a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb'
+
+ def setUp(self):
+ super(TestServersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = servers_client.ServersClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_servers_with_str_body(self):
+ self._test_list_servers()
+
+ def test_list_servers_with_bytes_body(self):
+ self._test_list_servers(bytes_body=True)
+
+ def _test_list_servers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_servers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVERS,
+ bytes_body)
+
+ def test_show_server_with_str_body(self):
+ self._test_show_server()
+
+ def test_show_server_with_bytes_body(self):
+ self._test_show_server(bytes_body=True)
+
+ def _test_show_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_server,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVER_GET,
+ bytes_body,
+ server_id=self.server_id
+ )
+
+ def test_delete_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_server,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ server_id=self.server_id
+ )
+
+ def test_create_server_with_str_body(self):
+ self._test_create_server()
+
+ def test_create_server_with_bytes_body(self):
+ self._test_create_server(True)
+
+ def _test_create_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_SERVER_POST,
+ bytes_body,
+ status=202,
+ name='fake-name',
+ imageRef='fake-image-ref',
+ flavorRef='fake-flavor-ref'
+ )
+
+ def test_list_addresses_with_str_body(self):
+ self._test_list_addresses()
+
+ def test_list_addresses_with_bytes_body(self):
+ self._test_list_addresses(True)
+
+ def _test_list_addresses(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_addresses,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ADDRESS,
+ bytes_body,
+ server_id=self.server_id
+ )
+
+ def test_list_addresses_by_network_with_str_body(self):
+ self._test_list_addresses_by_network()
+
+ def test_list_addresses_by_network_with_bytes_body(self):
+ self._test_list_addresses_by_network(True)
+
+ def _test_list_addresses_by_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_addresses_by_network,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ADDRESS['addresses'],
+ server_id=self.server_id,
+ network_id=self.network_id
+ )
+
+ def test_action_with_str_body(self):
+ self._test_action()
+
+ def test_action_with_bytes_body(self):
+ self._test_action(True)
+
+ def _test_action(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.action,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ server_id=self.server_id,
+ action_name='fake-action-name',
+ schema={'status_code': 200}
+ )
+
+ def test_create_backup_with_str_body(self):
+ self._test_create_backup()
+
+ def test_create_backup_with_bytes_body(self):
+ self._test_create_backup(True)
+
+ def _test_create_backup(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_backup,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ backup_type='fake-backup',
+ rotation='fake-rotation',
+ name='fake-name'
+ )
+
+ def test_change_password_with_str_body(self):
+ self._test_change_password()
+
+ def test_change_password_with_bytes_body(self):
+ self._test_change_password(True)
+
+ def _test_change_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.change_password,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ adminPass='fake-admin-pass'
+ )
+
+ def test_show_password_with_str_body(self):
+ self._test_show_password()
+
+ def test_show_password_with_bytes_body(self):
+ self._test_show_password(True)
+
+ def _test_show_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_password,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'password': 'fake-password'},
+ server_id=self.server_id
+ )
+
+ def test_delete_password_with_str_body(self):
+ self._test_delete_password()
+
+ def test_delete_password_with_bytes_body(self):
+ self._test_delete_password(True)
+
+ def _test_delete_password(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_password,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ server_id=self.server_id
+ )
+
+ def test_reboot_server_with_str_body(self):
+ self._test_reboot_server()
+
+ def test_reboot_server_with_bytes_body(self):
+ self._test_reboot_server(True)
+
+ def _test_reboot_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.reboot_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ type='fake-reboot-type'
+ )
+
+ def test_rebuild_server_with_str_body(self):
+ self._test_rebuild_server()
+
+ def test_rebuild_server_with_bytes_body(self):
+ self._test_rebuild_server(True)
+
+ def _test_rebuild_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.rebuild_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_REBUILD_SERVER,
+ status=202,
+ server_id=self.server_id,
+ image_ref='fake-image-ref'
+ )
+
+ def test_resize_server_with_str_body(self):
+ self._test_resize_server()
+
+ def test_resize_server_with_bytes_body(self):
+ self._test_resize_server(True)
+
+ def _test_resize_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.resize_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ flavor_ref='fake-flavor-ref'
+ )
+
+ def test_confirm_resize_server_with_str_body(self):
+ self._test_confirm_resize_server()
+
+ def test_confirm_resize_server_with_bytes_body(self):
+ self._test_confirm_resize_server(True)
+
+ def _test_confirm_resize_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.confirm_resize_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=204,
+ server_id=self.server_id
+ )
+
+ def test_revert_resize_server_with_str_body(self):
+ self._test_revert_resize()
+
+ def test_revert_resize_server_with_bytes_body(self):
+ self._test_revert_resize(True)
+
+ def _test_revert_resize(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.revert_resize_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_list_server_metadata_with_str_body(self):
+ self._test_list_server_metadata()
+
+ def test_list_server_metadata_with_bytes_body(self):
+ self._test_list_server_metadata()
+
+ def _test_list_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_server_metadata,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'metadata': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id
+ )
+
+ def test_set_server_metadata_with_str_body(self):
+ self._test_set_server_metadata()
+
+ def test_set_server_metadata_with_bytes_body(self):
+ self._test_set_server_metadata(True)
+
+ def _test_set_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_server_metadata,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {'metadata': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ meta='fake-meta'
+ )
+
+ def test_update_server_metadata_with_str_body(self):
+ self._test_update_server_metadata()
+
+ def test_update_server_metadata_with_bytes_body(self):
+ self._test_update_server_metadata(True)
+
+ def _test_update_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_server_metadata,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'metadata': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ meta='fake-meta'
+ )
+
+ def test_show_server_metadata_item_with_str_body(self):
+ self._test_show_server_metadata()
+
+ def test_show_server_metadata_item_with_bytes_body(self):
+ self._test_show_server_metadata(True)
+
+ def _test_show_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_server_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'meta': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ key='fake-key'
+ )
+
+ def test_set_server_metadata_item_with_str_body(self):
+ self._test_set_server_metadata_item()
+
+ def test_set_server_metadata_item_with_bytes_body(self):
+ self._test_set_server_metadata_item(True)
+
+ def _test_set_server_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.set_server_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {'meta': {'fake-key': 'fake-meta-data'}},
+ server_id=self.server_id,
+ key='fake-key',
+ meta='fake-meta'
+ )
+
+ def test_delete_server_metadata_item_with_str_body(self):
+ self._test_delete_server_metadata()
+
+ def test_delete_server_metadata_item_with_bytes_body(self):
+ self._test_delete_server_metadata(True)
+
+ def _test_delete_server_metadata(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_server_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=204,
+ server_id=self.server_id,
+ key='fake-key'
+ )
+
+ def test_stop_server_with_str_body(self):
+ self._test_stop_server()
+
+ def test_stop_server_with_bytes_body(self):
+ self._test_stop_server(True)
+
+ def _test_stop_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.stop_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_start_server_with_str_body(self):
+ self._test_start_server()
+
+ def test_start_server_with_bytes_body(self):
+ self._test_start_server(True)
+
+ def _test_start_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.start_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_attach_volume_with_str_body(self):
+ self._test_attach_volume_server()
+
+ def test_attach_volume_with_bytes_body(self):
+ self._test_attach_volume_server(True)
+
+ def _test_attach_volume_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.attach_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'volumeAttachment': self.FAKE_COMMON_VOLUME},
+ server_id=self.server_id
+ )
+
+ def test_update_attached_volume(self):
+ self.check_service_client_function(
+ self.client.update_attached_volume,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ status=202,
+ server_id=self.server_id,
+ attachment_id='fake-attachment-id',
+ volumeId='fake-volume-id'
+ )
+
+ def test_detach_volume_with_str_body(self):
+ self._test_detach_volume_server()
+
+ def test_detach_volume_with_bytes_body(self):
+ self._test_detach_volume_server(True)
+
+ def _test_detach_volume_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.detach_volume,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ status=202,
+ server_id=self.server_id,
+ volume_id=self.FAKE_COMMON_VOLUME['volumeId']
+ )
+
+ def test_show_volume_attachment_with_str_body(self):
+ self._test_show_volume_attachment()
+
+ def test_show_volume_attachment_with_bytes_body(self):
+ self._test_show_volume_attachment(True)
+
+ def _test_show_volume_attachment(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_attachment,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'volumeAttachment': self.FAKE_COMMON_VOLUME},
+ server_id=self.server_id,
+ volume_id=self.FAKE_COMMON_VOLUME['volumeId']
+ )
+
+ def test_list_volume_attachments_with_str_body(self):
+ self._test_list_volume_attachments()
+
+ def test_list_volume_attachments_with_bytes_body(self):
+ self._test_list_volume_attachments(True)
+
+ def _test_list_volume_attachments(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_volume_attachments,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'volumeAttachments': [self.FAKE_COMMON_VOLUME]},
+ server_id=self.server_id
+ )
+
+ def test_add_security_group_with_str_body(self):
+ self._test_add_security_group()
+
+ def test_add_security_group_with_bytes_body(self):
+ self._test_add_security_group(True)
+
+ def _test_add_security_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ name='fake-name'
+ )
+
+ def test_remove_security_group_with_str_body(self):
+ self._test_remove_security_group()
+
+ def test_remove_security_group_with_bytes_body(self):
+ self._test_remove_security_group(True)
+
+ def _test_remove_security_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.remove_security_group,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ name='fake-name'
+ )
+
+ def test_live_migrate_server_with_str_body(self):
+ self._test_live_migrate_server()
+
+ def test_live_migrate_server_with_bytes_body(self):
+ self._test_live_migrate_server(True)
+
+ def _test_live_migrate_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.live_migrate_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_migrate_server_with_str_body(self):
+ self._test_migrate_server()
+
+ def test_migrate_server_with_bytes_body(self):
+ self._test_migrate_server(True)
+
+ def _test_migrate_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.migrate_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_lock_server_with_str_body(self):
+ self._test_lock_server()
+
+ def test_lock_server_with_bytes_body(self):
+ self._test_lock_server(True)
+
+ def _test_lock_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.lock_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_unlock_server_with_str_body(self):
+ self._test_unlock_server()
+
+ def test_unlock_server_with_bytes_body(self):
+ self._test_unlock_server(True)
+
+ def _test_unlock_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.unlock_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_suspend_server_with_str_body(self):
+ self._test_suspend_server()
+
+ def test_suspend_server_with_bytes_body(self):
+ self._test_suspend_server(True)
+
+ def _test_suspend_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.suspend_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_resume_server_with_str_body(self):
+ self._test_resume_server()
+
+ def test_resume_server_with_bytes_body(self):
+ self._test_resume_server(True)
+
+ def _test_resume_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.resume_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_pause_server_with_str_body(self):
+ self._test_pause_server()
+
+ def test_pause_server_with_bytes_body(self):
+ self._test_pause_server(True)
+
+ def _test_pause_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.pause_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_unpause_server_with_str_body(self):
+ self._test_unpause_server()
+
+ def test_unpause_server_with_bytes_body(self):
+ self._test_unpause_server(True)
+
+ def _test_unpause_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.unpause_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_reset_state_with_str_body(self):
+ self._test_reset_state()
+
+ def test_reset_state_with_bytes_body(self):
+ self._test_reset_state(True)
+
+ def _test_reset_state(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.reset_state,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id,
+ state='fake-state'
+ )
+
+ def test_shelve_server_with_str_body(self):
+ self._test_shelve_server()
+
+ def test_shelve_server_with_bytes_body(self):
+ self._test_shelve_server(True)
+
+ def _test_shelve_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.shelve_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_unshelve_server_with_str_body(self):
+ self._test_unshelve_server()
+
+ def test_unshelve_server_with_bytes_body(self):
+ self._test_unshelve_server(True)
+
+ def _test_unshelve_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.unshelve_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_shelve_offload_server_with_str_body(self):
+ self._test_shelve_offload_server()
+
+ def test_shelve_offload_server_with_bytes_body(self):
+ self._test_shelve_offload_server(True)
+
+ def _test_shelve_offload_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.shelve_offload_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_get_console_output_with_str_body(self):
+ self._test_get_console_output()
+
+ def test_get_console_output_with_bytes_body(self):
+ self._test_get_console_output(True)
+
+ def _test_get_console_output(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_console_output,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'output': 'fake-output'},
+ server_id=self.server_id,
+ length='fake-length'
+ )
+
+ def test_list_virtual_interfaces_with_str_body(self):
+ self._test_list_virtual_interfaces()
+
+ def test_list_virtual_interfaces_with_bytes_body(self):
+ self._test_list_virtual_interfaces(True)
+
+ def _test_list_virtual_interfaces(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_virtual_interfaces,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'virtual_interfaces': [self.FAKE_VIRTUAL_INTERFACES]},
+ server_id=self.server_id
+ )
+
+ def test_rescue_server_with_str_body(self):
+ self._test_rescue_server()
+
+ def test_rescue_server_with_bytes_body(self):
+ self._test_rescue_server(True)
+
+ def _test_rescue_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.rescue_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'adminPass': 'fake-admin-pass'},
+ server_id=self.server_id
+ )
+
+ def test_unrescue_server_with_str_body(self):
+ self._test_unrescue_server()
+
+ def test_unrescue_server_with_bytes_body(self):
+ self._test_unrescue_server(True)
+
+ def _test_unrescue_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.unrescue_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_show_server_diagnostics_with_str_body(self):
+ self._test_show_server_diagnostics()
+
+ def test_show_server_diagnostics_with_bytes_body(self):
+ self._test_show_server_diagnostics(True)
+
+ def _test_show_server_diagnostics(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_server_diagnostics,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVER_DIAGNOSTICS,
+ status=200,
+ server_id=self.server_id
+ )
+
+ def test_list_instance_actions_with_str_body(self):
+ self._test_list_instance_actions()
+
+ def test_list_instance_actions_with_bytes_body(self):
+ self._test_list_instance_actions(True)
+
+ def _test_list_instance_actions(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_instance_actions,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'instanceActions': [self.FAKE_INSTANCE_ACTIONS]},
+ server_id=self.server_id
+ )
+
+ def test_show_instance_action_with_str_body(self):
+ self._test_show_instance_action()
+
+ def test_show_instance_action_with_bytes_body(self):
+ self._test_show_instance_action(True)
+
+ def _test_show_instance_action(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_instance_action,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {'instanceAction': self.FAKE_INSTANCE_WITH_EVENTS},
+ server_id=self.server_id,
+ request_id='fake-request-id'
+ )
+
+ def test_force_delete_server_with_str_body(self):
+ self._test_force_delete_server()
+
+ def test_force_delete_server_with_bytes_body(self):
+ self._test_force_delete_server(True)
+
+ def _test_force_delete_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.force_delete_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_restore_soft_deleted_server_with_str_body(self):
+ self._test_restore_soft_deleted_server()
+
+ def test_restore_soft_deleted_server_with_bytes_body(self):
+ self._test_restore_soft_deleted_server(True)
+
+ def _test_restore_soft_deleted_server(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.restore_soft_deleted_server,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_reset_network_with_str_body(self):
+ self._test_reset_network()
+
+ def test_reset_network_with_bytes_body(self):
+ self._test_reset_network(True)
+
+ def _test_reset_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.reset_network,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_inject_network_info_with_str_body(self):
+ self._test_inject_network_info()
+
+ def test_inject_network_info_with_bytes_body(self):
+ self._test_inject_network_info(True)
+
+ def _test_inject_network_info(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.inject_network_info,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ server_id=self.server_id
+ )
+
+ def test_get_vnc_console_with_str_body(self):
+ self._test_get_vnc_console()
+
+ def test_get_vnc_console_with_bytes_body(self):
+ self._test_get_vnc_console(True)
+
+ def _test_get_vnc_console(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.get_vnc_console,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {'console': self.FAKE_VNC_CONSOLE},
+ server_id=self.server_id,
+ type='fake-console-type'
+ )
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
new file mode 100644
index 0000000..7add187
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -0,0 +1,94 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.services.compute import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestServicesClient(base.BaseComputeServiceTest):
+
+ FAKE_SERVICES = {
+ "services":
+ [{
+ "status": "enabled",
+ "binary": "nova-conductor",
+ "zone": "internal",
+ "state": "up",
+ "updated_at": "2015-08-19T06:50:55.000000",
+ "host": "controller",
+ "disabled_reason": None,
+ "id": 1
+ }]
+ }
+
+ FAKE_SERVICE = {
+ "service":
+ {
+ "status": "enabled",
+ "binary": "nova-conductor",
+ "host": "controller"
+ }
+ }
+
+ def setUp(self):
+ super(TestServicesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = services_client.ServicesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_list_services_with_str_body(self):
+ self.check_service_client_function(
+ self.client.list_services,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICES)
+
+ def test_list_services_with_bytes_body(self):
+ self.check_service_client_function(
+ self.client.list_services,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SERVICES, to_utf=True)
+
+ def _test_enable_service(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.enable_service,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_SERVICE,
+ bytes_body,
+ host_name="nova-conductor", binary="controller")
+
+ def test_enable_service_with_str_body(self):
+ self._test_enable_service()
+
+ def test_enable_service_with_bytes_body(self):
+ self._test_enable_service(bytes_body=True)
+
+ def _test_disable_service(self, bytes_body=False):
+ fake_service = copy.deepcopy(self.FAKE_SERVICE)
+ fake_service["service"]["status"] = "disable"
+
+ self.check_service_client_function(
+ self.client.disable_service,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_service,
+ bytes_body,
+ host_name="nova-conductor", binary="controller")
+
+ def test_disable_service_with_str_body(self):
+ self._test_disable_service()
+
+ def test_disable_service_with_bytes_body(self):
+ self._test_disable_service(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_snapshots_client.py b/tempest/tests/lib/services/compute/test_snapshots_client.py
new file mode 100644
index 0000000..b1d8ade
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_snapshots_client.py
@@ -0,0 +1,103 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import snapshots_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestSnapshotsClient(base.BaseComputeServiceTest):
+
+ FAKE_SNAPSHOT = {
+ "createdAt": "2015-10-02T16:27:54.724209",
+ "displayDescription": u"Another \u1234.",
+ "displayName": u"v\u1234-001",
+ "id": "100",
+ "size": 100,
+ "status": "available",
+ "volumeId": "12"
+ }
+
+ FAKE_SNAPSHOTS = {"snapshots": [FAKE_SNAPSHOT]}
+
+ def setUp(self):
+ super(TestSnapshotsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = snapshots_client.SnapshotsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_snapshot(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"snapshot": self.FAKE_SNAPSHOT},
+ to_utf=bytes_body, status=200,
+ volume_id=self.FAKE_SNAPSHOT["volumeId"])
+
+ def test_create_snapshot_with_str_body(self):
+ self._test_create_snapshot()
+
+ def test_create_shapshot_with_bytes_body(self):
+ self._test_create_snapshot(bytes_body=True)
+
+ def _test_show_snapshot(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"snapshot": self.FAKE_SNAPSHOT},
+ to_utf=bytes_body, snapshot_id=self.FAKE_SNAPSHOT["id"])
+
+ def test_show_snapshot_with_str_body(self):
+ self._test_show_snapshot()
+
+ def test_show_snapshot_with_bytes_body(self):
+ self._test_show_snapshot(bytes_body=True)
+
+ def _test_list_snapshots(self, bytes_body=False, **params):
+ self.check_service_client_function(
+ self.client.list_snapshots,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SNAPSHOTS, to_utf=bytes_body, **params)
+
+ def test_list_snapshots_with_str_body(self):
+ self._test_list_snapshots()
+
+ def test_list_snapshots_with_byte_body(self):
+ self._test_list_snapshots(bytes_body=True)
+
+ def test_list_snapshots_with_params(self):
+ self._test_list_snapshots('fake')
+
+ def test_delete_snapshot(self):
+ self.check_service_client_function(
+ self.client.delete_snapshot,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, snapshot_id=self.FAKE_SNAPSHOT['id'])
+
+ def test_is_resource_deleted_true(self):
+ module = ('tempest.lib.services.compute.snapshots_client.'
+ 'SnapshotsClient.show_snapshot')
+ self.useFixture(mockpatch.Patch(
+ module, side_effect=lib_exc.NotFound))
+ self.assertTrue(self.client.is_resource_deleted('fake-id'))
+
+ def test_is_resource_deleted_false(self):
+ module = ('tempest.lib.services.compute.snapshots_client.'
+ 'SnapshotsClient.show_snapshot')
+ self.useFixture(mockpatch.Patch(
+ module, return_value={}))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/compute/test_tenant_networks_client.py b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
new file mode 100644
index 0000000..cfb68cc
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_tenant_networks_client.py
@@ -0,0 +1,63 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import tenant_networks_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestTenantNetworksClient(base.BaseComputeServiceTest):
+
+ FAKE_NETWORK = {
+ "cidr": "None",
+ "id": "c2329eb4-cc8e-4439-ac4c-932369309e36",
+ "label": u'\u30d7'
+ }
+
+ FAKE_NETWORKS = [FAKE_NETWORK]
+
+ NETWORK_ID = FAKE_NETWORK['id']
+
+ def setUp(self):
+ super(TestTenantNetworksClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tenant_networks_client.TenantNetworksClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_tenant_networks(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tenant_networks,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"networks": self.FAKE_NETWORKS},
+ bytes_body)
+
+ def test_list_tenant_networks_with_str_body(self):
+ self._test_list_tenant_networks()
+
+ def test_list_tenant_networks_with_bytes_body(self):
+ self._test_list_tenant_networks(bytes_body=True)
+
+ def _test_show_tenant_network(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_tenant_network,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"network": self.FAKE_NETWORK},
+ bytes_body,
+ network_id=self.NETWORK_ID)
+
+ def test_show_tenant_network_with_str_body(self):
+ self._test_show_tenant_network()
+
+ def test_show_tenant_network_with_bytes_body(self):
+ self._test_show_tenant_network(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_tenant_usages_client.py b/tempest/tests/lib/services/compute/test_tenant_usages_client.py
new file mode 100644
index 0000000..88d093d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_tenant_usages_client.py
@@ -0,0 +1,79 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.compute import tenant_usages_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestTenantUsagesClient(base.BaseComputeServiceTest):
+
+ FAKE_SERVER_USAGES = [{
+ "ended_at": None,
+ "flavor": "m1.tiny",
+ "hours": 1.0,
+ "instance_id": "1f1deceb-17b5-4c04-84c7-e0d4499c8fe0",
+ "local_gb": 1,
+ "memory_mb": 512,
+ "name": "new-server-test",
+ "started_at": "2012-10-08T20:10:44.541277",
+ "state": "active",
+ "tenant_id": "openstack",
+ "uptime": 3600,
+ "vcpus": 1
+ }]
+
+ FAKE_TENANT_USAGES = [{
+ "server_usages": FAKE_SERVER_USAGES,
+ "start": "2012-10-08T21:10:44.587336",
+ "stop": "2012-10-08T22:10:44.587336",
+ "tenant_id": "openstack",
+ "total_hours": 1,
+ "total_local_gb_usage": 1,
+ "total_memory_mb_usage": 512,
+ "total_vcpus_usage": 1
+ }]
+
+ def setUp(self):
+ super(TestTenantUsagesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tenant_usages_client.TenantUsagesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_tenant_usages(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tenant_usages,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"tenant_usages": self.FAKE_TENANT_USAGES},
+ to_utf=bytes_body)
+
+ def test_list_tenant_usages_with_str_body(self):
+ self._test_list_tenant_usages()
+
+ def test_list_tenant_usages_with_bytes_body(self):
+ self._test_list_tenant_usages(bytes_body=True)
+
+ def _test_show_tenant_usage(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_tenant_usage,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"tenant_usage": self.FAKE_TENANT_USAGES[0]},
+ to_utf=bytes_body,
+ tenant_id='openstack')
+
+ def test_show_tenant_usage_with_str_body(self):
+ self._test_show_tenant_usage()
+
+ def test_show_tenant_usage_with_bytes_body(self):
+ self._test_show_tenant_usage(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_versions_client.py b/tempest/tests/lib/services/compute/test_versions_client.py
new file mode 100644
index 0000000..5ac2f2d
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_versions_client.py
@@ -0,0 +1,96 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+from oslotest import mockpatch
+
+from tempest.lib.services.compute import versions_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestVersionsClient(base.BaseComputeServiceTest):
+
+ FAKE_INIT_VERSION = {
+ "version": {
+ "id": "v2.1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2.1/",
+ "rel": "self"
+ },
+ {
+ "href": "http://docs.openstack.org/",
+ "rel": "describedby",
+ "type": "text/html"
+ }
+ ],
+ "status": "CURRENT",
+ "updated": "2013-07-23T11:33:21Z",
+ "version": "2.1",
+ "min_version": "2.1"
+ }
+ }
+
+ FAKE_VERSIONS_INFO = {
+ "versions": [FAKE_INIT_VERSION["version"]]
+ }
+
+ FAKE_VERSION_INFO = copy.deepcopy(FAKE_INIT_VERSION)
+
+ FAKE_VERSION_INFO["version"]["media-types"] = [
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.compute+json;version=2.1"
+ }
+ ]
+
+ def setUp(self):
+ super(TestVersionsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.versions_client = (
+ versions_client.VersionsClient
+ (fake_auth, 'compute', 'regionOne'))
+
+ def _test_versions_client(self, bytes_body=False):
+ self.check_service_client_function(
+ self.versions_client.list_versions,
+ 'tempest.lib.common.rest_client.RestClient.raw_request',
+ self.FAKE_VERSIONS_INFO,
+ bytes_body,
+ 200)
+
+ def _test_get_version_by_url(self, bytes_body=False):
+ self.useFixture(mockpatch.Patch(
+ "tempest.lib.common.rest_client.RestClient.token",
+ return_value="Dummy Token"))
+ params = {"version_url": self.versions_client._get_base_version_url()}
+ self.check_service_client_function(
+ self.versions_client.get_version_by_url,
+ 'tempest.lib.common.rest_client.RestClient.raw_request',
+ self.FAKE_VERSION_INFO,
+ bytes_body,
+ 200, **params)
+
+ def test_list_versions_client_with_str_body(self):
+ self._test_versions_client()
+
+ def test_list_versions_client_with_bytes_body(self):
+ self._test_versions_client(bytes_body=True)
+
+ def test_get_version_by_url_with_str_body(self):
+ self._test_get_version_by_url()
+
+ def test_get_version_by_url_with_bytes_body(self):
+ self._test_get_version_by_url(bytes_body=True)
diff --git a/tempest/tests/lib/services/compute/test_volumes_client.py b/tempest/tests/lib/services/compute/test_volumes_client.py
new file mode 100644
index 0000000..d16f5b0
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_volumes_client.py
@@ -0,0 +1,114 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from oslotest import mockpatch
+
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.compute import volumes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services.compute import base
+
+
+class TestVolumesClient(base.BaseComputeServiceTest):
+
+ FAKE_VOLUME = {
+ "id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
+ "displayName": u"v\u12345ol-001",
+ "displayDescription": u"Another \u1234volume.",
+ "size": 30,
+ "status": "Active",
+ "volumeType": "289da7f8-6440-407c-9fb4-7db01ec49164",
+ "metadata": {
+ "contents": "junk"
+ },
+ "availabilityZone": "us-east1",
+ "snapshotId": None,
+ "attachments": [],
+ "createdAt": "2012-02-14T20:53:07Z"
+ }
+
+ FAKE_VOLUMES = {"volumes": [FAKE_VOLUME]}
+
+ def setUp(self):
+ super(TestVolumesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = volumes_client.VolumesClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_list_volumes(self, bytes_body=False, **params):
+ self.check_service_client_function(
+ self.client.list_volumes,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUMES, to_utf=bytes_body, **params)
+
+ def test_list_volumes_with_str_body(self):
+ self._test_list_volumes()
+
+ def test_list_volumes_with_byte_body(self):
+ self._test_list_volumes(bytes_body=True)
+
+ def test_list_volumes_with_params(self):
+ self._test_list_volumes(name='fake')
+
+ def _test_show_volume(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"volume": self.FAKE_VOLUME},
+ to_utf=bytes_body, volume_id=self.FAKE_VOLUME['id'])
+
+ def test_show_volume_with_str_body(self):
+ self._test_show_volume()
+
+ def test_show_volume_with_bytes_body(self):
+ self._test_show_volume(bytes_body=True)
+
+ def _test_create_volume(self, bytes_body=False):
+ post_body = copy.deepcopy(self.FAKE_VOLUME)
+ del post_body['id']
+ del post_body['createdAt']
+ del post_body['status']
+ self.check_service_client_function(
+ self.client.create_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {"volume": self.FAKE_VOLUME},
+ to_utf=bytes_body, status=200, **post_body)
+
+ def test_create_volume_with_str_body(self):
+ self._test_create_volume()
+
+ def test_create_volume_with_bytes_body(self):
+ self._test_create_volume(bytes_body=True)
+
+ def test_delete_volume(self):
+ self.check_service_client_function(
+ self.client.delete_volume,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, status=202, volume_id=self.FAKE_VOLUME['id'])
+
+ def test_is_resource_deleted_true(self):
+ module = ('tempest.lib.services.compute.volumes_client.'
+ 'VolumesClient.show_volume')
+ self.useFixture(mockpatch.Patch(
+ module, side_effect=lib_exc.NotFound))
+ self.assertTrue(self.client.is_resource_deleted('fake-id'))
+
+ def test_is_resource_deleted_false(self):
+ module = ('tempest.lib.services.compute.volumes_client.'
+ 'VolumesClient.show_volume')
+ self.useFixture(mockpatch.Patch(
+ module, return_value={}))
+ self.assertFalse(self.client.is_resource_deleted('fake-id'))
diff --git a/tempest/tests/lib/services/identity/__init__.py b/tempest/tests/lib/services/identity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/identity/__init__.py
diff --git a/tempest/tests/lib/services/identity/v2/__init__.py b/tempest/tests/lib/services/identity/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/__init__.py
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
new file mode 100644
index 0000000..dd3533a
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -0,0 +1,92 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+import httplib2
+from oslotest import mockpatch
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_http
+
+
+class TestTokenClientV2(base.TestCase):
+
+ def setUp(self):
+ super(TestTokenClientV2, self).setUp()
+ self.fake_200_http = fake_http.fake_httplib2(return_type=200)
+
+ def test_init_without_authurl(self):
+ self.assertRaises(exceptions.IdentityError,
+ token_client.TokenClient, None)
+
+ def test_auth(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ post_mock = self.useFixture(mockpatch.PatchObject(
+ token_client_v2, 'post', return_value=self.fake_200_http.request(
+ 'fake_url', body={'access': {'token': 'fake_token'}})))
+ resp = token_client_v2.auth('fake_user', 'fake_pass')
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'passwordCredentials': {
+ 'username': 'fake_user',
+ 'password': 'fake_pass',
+ },
+ }
+ }, sort_keys=True)
+ post_mock.mock.assert_called_once_with('fake_url/tokens',
+ body=req_dict)
+
+ def test_auth_with_tenant(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ post_mock = self.useFixture(mockpatch.PatchObject(
+ token_client_v2, 'post', return_value=self.fake_200_http.request(
+ 'fake_url', body={'access': {'token': 'fake_token'}})))
+ resp = token_client_v2.auth('fake_user', 'fake_pass', 'fake_tenant')
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'tenantName': 'fake_tenant',
+ 'passwordCredentials': {
+ 'username': 'fake_user',
+ 'password': 'fake_pass',
+ },
+ }
+ }, sort_keys=True)
+ post_mock.mock.assert_called_once_with('fake_url/tokens',
+ body=req_dict)
+
+ def test_request_with_str_body(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ self.useFixture(mockpatch.PatchObject(
+ token_client_v2, 'raw_request', return_value=(
+ httplib2.Response({'status': '200'}),
+ str('{"access": {"token": "fake_token"}}'))))
+ resp, body = token_client_v2.request('GET', 'fake_uri')
+ self.assertIsInstance(resp, httplib2.Response)
+ self.assertIsInstance(body, dict)
+
+ def test_request_with_bytes_body(self):
+ token_client_v2 = token_client.TokenClient('fake_url')
+ self.useFixture(mockpatch.PatchObject(
+ token_client_v2, 'raw_request', return_value=(
+ httplib2.Response({'status': '200'}),
+ bytes(b'{"access": {"token": "fake_token"}}'))))
+ resp, body = token_client_v2.request('GET', 'fake_uri')
+ self.assertIsInstance(resp, httplib2.Response)
+ self.assertIsInstance(body, dict)
diff --git a/tempest/tests/lib/services/identity/v3/__init__.py b/tempest/tests/lib/services/identity/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/__init__.py
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
new file mode 100644
index 0000000..bb4dae3
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -0,0 +1,145 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+import httplib2
+from oslotest import mockpatch
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v3 import token_client
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_http
+
+
+class TestTokenClientV2(base.TestCase):
+
+ def setUp(self):
+ super(TestTokenClientV2, self).setUp()
+ self.fake_201_http = fake_http.fake_httplib2(return_type=201)
+
+ def test_init_without_authurl(self):
+ self.assertRaises(exceptions.IdentityError,
+ token_client.V3TokenClient, None)
+
+ def test_auth(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ post_mock = self.useFixture(mockpatch.PatchObject(
+ token_client_v3, 'post', return_value=self.fake_201_http.request(
+ 'fake_url', body={'access': {'token': 'fake_token'}})))
+ resp = token_client_v3.auth(username='fake_user', password='fake_pass')
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': 'fake_user',
+ 'password': 'fake_pass',
+ }
+ }
+ },
+ }
+ }, sort_keys=True)
+ post_mock.mock.assert_called_once_with('fake_url/auth/tokens',
+ body=req_dict)
+
+ def test_auth_with_project_id_and_domain_id(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ post_mock = self.useFixture(mockpatch.PatchObject(
+ token_client_v3, 'post', return_value=self.fake_201_http.request(
+ 'fake_url', body={'access': {'token': 'fake_token'}})))
+ resp = token_client_v3.auth(
+ username='fake_user', password='fake_pass',
+ project_id='fcac2a055a294e4c82d0a9c21c620eb4',
+ user_domain_id='14f4a9a99973404d8c20ba1d2af163ff',
+ project_domain_id='291f63ae9ac54ee292ca09e5f72d9676')
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': 'fake_user',
+ 'password': 'fake_pass',
+ 'domain': {
+ 'id': '14f4a9a99973404d8c20ba1d2af163ff'
+ }
+ }
+ }
+ },
+ 'scope': {
+ 'project': {
+ 'id': 'fcac2a055a294e4c82d0a9c21c620eb4',
+ 'domain': {
+ 'id': '291f63ae9ac54ee292ca09e5f72d9676'
+ }
+ }
+ }
+ }
+ }, sort_keys=True)
+ post_mock.mock.assert_called_once_with('fake_url/auth/tokens',
+ body=req_dict)
+
+ def test_auth_with_tenant(self):
+ token_client_v2 = token_client.V3TokenClient('fake_url')
+ post_mock = self.useFixture(mockpatch.PatchObject(
+ token_client_v2, 'post', return_value=self.fake_201_http.request(
+ 'fake_url', body={'access': {'token': 'fake_token'}})))
+ resp = token_client_v2.auth(username='fake_user', password='fake_pass',
+ project_name='fake_tenant')
+ self.assertIsInstance(resp, rest_client.ResponseBody)
+ req_dict = json.dumps({
+ 'auth': {
+ 'identity': {
+ 'methods': ['password'],
+ 'password': {
+ 'user': {
+ 'name': 'fake_user',
+ 'password': 'fake_pass',
+ }
+ }},
+ 'scope': {
+ 'project': {
+ 'name': 'fake_tenant'
+ }
+ },
+ }
+ }, sort_keys=True)
+
+ post_mock.mock.assert_called_once_with('fake_url/auth/tokens',
+ body=req_dict)
+
+ def test_request_with_str_body(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ self.useFixture(mockpatch.PatchObject(
+ token_client_v3, 'raw_request', return_value=(
+ httplib2.Response({"status": "200"}),
+ str('{"access": {"token": "fake_token"}}'))))
+ resp, body = token_client_v3.request('GET', 'fake_uri')
+ self.assertIsInstance(resp, httplib2.Response)
+ self.assertIsInstance(body, dict)
+
+ def test_request_with_bytes_body(self):
+ token_client_v3 = token_client.V3TokenClient('fake_url')
+ self.useFixture(mockpatch.PatchObject(
+ token_client_v3, 'raw_request', return_value=(
+ httplib2.Response({"status": "200"}),
+ bytes(b'{"access": {"token": "fake_token"}}'))))
+ resp, body = token_client_v3.request('GET', 'fake_uri')
+ self.assertIsInstance(resp, httplib2.Response)
+ self.assertIsInstance(body, dict)
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
new file mode 100644
index 0000000..f7bc7e4
--- /dev/null
+++ b/tempest/tests/lib/test_auth.py
@@ -0,0 +1,480 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import datetime
+
+from oslotest import mockpatch
+
+from tempest.lib import auth
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client as v2_client
+from tempest.lib.services.identity.v3 import token_client as v3_client
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_credentials
+from tempest.tests.lib import fake_http
+from tempest.tests.lib import fake_identity
+
+
+def fake_get_credentials(fill_in=True, identity_version='v2', **kwargs):
+ return fake_credentials.FakeCredentials()
+
+
+class BaseAuthTestsSetUp(base.TestCase):
+ _auth_provider_class = None
+ credentials = fake_credentials.FakeCredentials()
+
+ def _auth(self, credentials, auth_url, **params):
+ """returns auth method according to keystone"""
+ return self._auth_provider_class(credentials, auth_url, **params)
+
+ def setUp(self):
+ super(BaseAuthTestsSetUp, self).setUp()
+ self.fake_http = fake_http.fake_httplib2(return_type=200)
+ self.stubs.Set(auth, 'get_credentials', fake_get_credentials)
+ self.auth_provider = self._auth(self.credentials,
+ fake_identity.FAKE_AUTH_URL)
+
+
+class TestBaseAuthProvider(BaseAuthTestsSetUp):
+ """Tests for base AuthProvider
+
+ This tests auth.AuthProvider class which is base for the other so we
+ obviously don't test not implemented method or the ones which strongly
+ depends on them.
+ """
+
+ class FakeAuthProviderImpl(auth.AuthProvider):
+ def _decorate_request(self):
+ pass
+
+ def _fill_credentials(self):
+ pass
+
+ def _get_auth(self):
+ pass
+
+ def base_url(self):
+ pass
+
+ def is_expired(self):
+ pass
+
+ _auth_provider_class = FakeAuthProviderImpl
+
+ def _auth(self, credentials, auth_url, **params):
+ """returns auth method according to keystone"""
+ return self._auth_provider_class(credentials, **params)
+
+ def test_check_credentials_bad_type(self):
+ self.assertFalse(self.auth_provider.check_credentials([]))
+
+ def test_auth_data_property_when_cache_exists(self):
+ self.auth_provider.cache = 'foo'
+ self.useFixture(mockpatch.PatchObject(self.auth_provider,
+ 'is_expired',
+ return_value=False))
+ self.assertEqual('foo', getattr(self.auth_provider, 'auth_data'))
+
+ def test_delete_auth_data_property_through_deleter(self):
+ self.auth_provider.cache = 'foo'
+ del self.auth_provider.auth_data
+ self.assertIsNone(self.auth_provider.cache)
+
+ def test_delete_auth_data_property_through_clear_auth(self):
+ self.auth_provider.cache = 'foo'
+ self.auth_provider.clear_auth()
+ self.assertIsNone(self.auth_provider.cache)
+
+ def test_set_and_reset_alt_auth_data(self):
+ self.auth_provider.set_alt_auth_data('foo', 'bar')
+ self.assertEqual(self.auth_provider.alt_part, 'foo')
+ self.assertEqual(self.auth_provider.alt_auth_data, 'bar')
+
+ self.auth_provider.reset_alt_auth_data()
+ self.assertIsNone(self.auth_provider.alt_part)
+ self.assertIsNone(self.auth_provider.alt_auth_data)
+
+ def test_auth_class(self):
+ self.assertRaises(TypeError,
+ auth.AuthProvider,
+ fake_credentials.FakeCredentials)
+
+
+class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp):
+ _endpoints = fake_identity.IDENTITY_V2_RESPONSE['access']['serviceCatalog']
+ _auth_provider_class = auth.KeystoneV2AuthProvider
+ credentials = fake_credentials.FakeKeystoneV2Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV2AuthProvider, self).setUp()
+ self.stubs.Set(v2_client.TokenClient, 'raw_request',
+ fake_identity._fake_v2_response)
+ self.target_url = 'test_api'
+
+ def _get_fake_identity(self):
+ return fake_identity.IDENTITY_V2_RESPONSE['access']
+
+ def _get_fake_alt_identity(self):
+ return fake_identity.ALT_IDENTITY_V2_RESPONSE['access']
+
+ def _get_result_url_from_endpoint(self, ep, endpoint_type='publicURL',
+ replacement=None):
+ if replacement:
+ return ep[endpoint_type].replace('v2', replacement)
+ return ep[endpoint_type]
+
+ def _get_token_from_fake_identity(self):
+ return fake_identity.TOKEN
+
+ def _get_from_fake_identity(self, attr):
+ access = fake_identity.IDENTITY_V2_RESPONSE['access']
+ if attr == 'user_id':
+ return access['user']['id']
+ elif attr == 'tenant_id':
+ return access['token']['tenant']['id']
+
+ def _test_request_helper(self, filters, expected):
+ url, headers, body = self.auth_provider.auth_request('GET',
+ self.target_url,
+ filters=filters)
+
+ self.assertEqual(expected['url'], url)
+ self.assertEqual(expected['token'], headers['X-Auth-Token'])
+ self.assertEqual(expected['body'], body)
+
+ def _auth_data_with_expiry(self, date_as_string):
+ token, access = self.auth_provider.auth_data
+ access['token']['expires'] = date_as_string
+ return token, access
+
+ def test_request(self):
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+
+ url = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1]) + '/' + self.target_url
+
+ expected = {
+ 'body': None,
+ 'url': url,
+ 'token': self._get_token_from_fake_identity(),
+ }
+ self._test_request_helper(filters, expected)
+
+ def test_request_with_alt_auth_cleans_alt(self):
+ """Test alternate auth data for headers
+
+ Assert that when the alt data is provided for headers, after an
+ auth_request the data alt_data is cleaned-up.
+ """
+ self.auth_provider.set_alt_auth_data(
+ 'headers',
+ (fake_identity.ALT_TOKEN, self._get_fake_alt_identity()))
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.auth_provider.auth_request('GET', self.target_url,
+ filters=filters)
+
+ # Assert alt auth data is clear after it
+ self.assertIsNone(self.auth_provider.alt_part)
+ self.assertIsNone(self.auth_provider.alt_auth_data)
+
+ def _test_request_with_identical_alt_auth(self, part):
+ """Test alternate but identical auth data for headers
+
+ Assert that when the alt data is provided, but it's actually
+ identical, an exception is raised.
+ """
+ self.auth_provider.set_alt_auth_data(
+ part,
+ (fake_identity.TOKEN, self._get_fake_identity()))
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+
+ self.assertRaises(exceptions.BadAltAuth,
+ self.auth_provider.auth_request,
+ 'GET', self.target_url, filters=filters)
+
+ def test_request_with_identical_alt_auth_headers(self):
+ self._test_request_with_identical_alt_auth('headers')
+
+ def test_request_with_identical_alt_auth_url(self):
+ self._test_request_with_identical_alt_auth('url')
+
+ def test_request_with_identical_alt_auth_body(self):
+ self._test_request_with_identical_alt_auth('body')
+
+ def test_request_with_alt_part_without_alt_data(self):
+ """Test empty alternate auth data
+
+ Assert that when alt_part is defined, the corresponding original
+ request element is kept the same.
+ """
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.auth_provider.set_alt_auth_data('headers', None)
+
+ url, headers, body = self.auth_provider.auth_request('GET',
+ self.target_url,
+ filters=filters)
+ # The original headers where empty
+ self.assertNotEqual(url, self.target_url)
+ self.assertIsNone(headers)
+ self.assertEqual(body, None)
+
+ def _test_request_with_alt_part_without_alt_data_no_change(self, body):
+ """Test empty alternate auth data with no effect
+
+ Assert that when alt_part is defined, no auth_data is provided,
+ and the the corresponding original request element was not going to
+ be changed anyways, and exception is raised
+ """
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.auth_provider.set_alt_auth_data('body', None)
+
+ self.assertRaises(exceptions.BadAltAuth,
+ self.auth_provider.auth_request,
+ 'GET', self.target_url, filters=filters)
+
+ def test_request_with_alt_part_without_alt_data_no_change_headers(self):
+ self._test_request_with_alt_part_without_alt_data_no_change('headers')
+
+ def test_request_with_alt_part_without_alt_data_no_change_url(self):
+ self._test_request_with_alt_part_without_alt_data_no_change('url')
+
+ def test_request_with_alt_part_without_alt_data_no_change_body(self):
+ self._test_request_with_alt_part_without_alt_data_no_change('body')
+
+ def test_request_with_bad_service(self):
+ filters = {
+ 'service': 'BAD_SERVICE',
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.auth_request, 'GET',
+ self.target_url, filters=filters)
+
+ def test_request_without_service(self):
+ filters = {
+ 'service': None,
+ 'endpoint_type': 'publicURL',
+ 'region': 'fakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.auth_request, 'GET',
+ self.target_url, filters=filters)
+
+ def test_check_credentials_missing_attribute(self):
+ for attr in ['username', 'password']:
+ cred = copy.copy(self.credentials)
+ del cred[attr]
+ self.assertFalse(self.auth_provider.check_credentials(cred))
+
+ def test_fill_credentials(self):
+ self.auth_provider.fill_credentials()
+ creds = self.auth_provider.credentials
+ for attr in ['user_id', 'tenant_id']:
+ self.assertEqual(self._get_from_fake_identity(attr),
+ getattr(creds, attr))
+
+ def _test_base_url_helper(self, expected_url, filters,
+ auth_data=None):
+
+ url = self.auth_provider.base_url(filters, auth_data)
+ self.assertEqual(url, expected_url)
+
+ def test_base_url(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1])
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_to_get_admin_endpoint(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'adminURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1], endpoint_type='adminURL')
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_unknown_region(self):
+ """If the region is unknown, the first endpoint is returned."""
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'AintNoBodyKnowThisRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][0])
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_with_non_existent_service(self):
+ self.filters = {
+ 'service': 'BAD_SERVICE',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self._test_base_url_helper, None, self.filters)
+
+ def test_base_url_without_service(self):
+ self.filters = {
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self._test_base_url_helper, None, self.filters)
+
+ def test_base_url_with_api_version_filter(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v12'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1], replacement='v12')
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_base_url_with_skip_path_filter(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'skip_path': True
+ }
+ expected = 'http://fake_url/'
+ self._test_base_url_helper(expected, self.filters)
+
+ def test_token_not_expired(self):
+ expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+ self._verify_expiry(expiry_data=expiry_data, should_be_expired=False)
+
+ def test_token_expired(self):
+ expiry_data = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
+ self._verify_expiry(expiry_data=expiry_data, should_be_expired=True)
+
+ def test_token_not_expired_to_be_renewed(self):
+ expiry_data = (datetime.datetime.utcnow() +
+ self.auth_provider.token_expiry_threshold / 2)
+ self._verify_expiry(expiry_data=expiry_data, should_be_expired=True)
+
+ def _verify_expiry(self, expiry_data, should_be_expired):
+ for expiry_format in self.auth_provider.EXPIRY_DATE_FORMATS:
+ auth_data = self._auth_data_with_expiry(
+ expiry_data.strftime(expiry_format))
+ self.assertEqual(self.auth_provider.is_expired(auth_data),
+ should_be_expired)
+
+
+class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
+ _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
+ _auth_provider_class = auth.KeystoneV3AuthProvider
+ credentials = fake_credentials.FakeKeystoneV3Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV3AuthProvider, self).setUp()
+ self.stubs.Set(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response)
+
+ def _get_fake_identity(self):
+ return fake_identity.IDENTITY_V3_RESPONSE['token']
+
+ def _get_fake_alt_identity(self):
+ return fake_identity.ALT_IDENTITY_V3['token']
+
+ def _get_result_url_from_endpoint(self, ep, replacement=None):
+ if replacement:
+ return ep['url'].replace('v3', replacement)
+ return ep['url']
+
+ def _auth_data_with_expiry(self, date_as_string):
+ token, access = self.auth_provider.auth_data
+ access['expires_at'] = date_as_string
+ return token, access
+
+ def _get_from_fake_identity(self, attr):
+ token = fake_identity.IDENTITY_V3_RESPONSE['token']
+ if attr == 'user_id':
+ return token['user']['id']
+ elif attr == 'project_id':
+ return token['project']['id']
+ elif attr == 'user_domain_id':
+ return token['user']['domain']['id']
+ elif attr == 'project_domain_id':
+ return token['project']['domain']['id']
+
+ def test_check_credentials_missing_attribute(self):
+ # reset credentials to fresh ones
+ self.credentials.reset()
+ for attr in ['username', 'password', 'user_domain_name',
+ 'project_domain_name']:
+ cred = copy.copy(self.credentials)
+ del cred[attr]
+ self.assertFalse(self.auth_provider.check_credentials(cred),
+ "Credentials should be invalid without %s" % attr)
+
+ def test_check_domain_credentials_missing_attribute(self):
+ # reset credentials to fresh ones
+ self.credentials.reset()
+ domain_creds = fake_credentials.FakeKeystoneV3DomainCredentials()
+ for attr in ['username', 'password', 'user_domain_name']:
+ cred = copy.copy(domain_creds)
+ del cred[attr]
+ self.assertFalse(self.auth_provider.check_credentials(cred),
+ "Credentials should be invalid without %s" % attr)
+
+ def test_fill_credentials(self):
+ self.auth_provider.fill_credentials()
+ creds = self.auth_provider.credentials
+ for attr in ['user_id', 'project_id', 'user_domain_id',
+ 'project_domain_id']:
+ self.assertEqual(self._get_from_fake_identity(attr),
+ getattr(creds, attr))
+
+ # Overwrites v2 test
+ def test_base_url_to_get_admin_endpoint(self):
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'admin',
+ 'region': 'MiddleEarthRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][2])
+ self._test_base_url_helper(expected, self.filters)
diff --git a/tempest/tests/lib/test_base.py b/tempest/tests/lib/test_base.py
new file mode 100644
index 0000000..27cda1a
--- /dev/null
+++ b/tempest/tests/lib/test_base.py
@@ -0,0 +1,64 @@
+# Copyright 2014 Mirantis 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.
+
+import testtools
+
+from tempest.lib import base
+from tempest.lib import exceptions
+
+
+class TestAttr(base.BaseTestCase):
+
+ def test_has_no_attrs(self):
+ self.assertEqual(
+ 'tempest.tests.lib.test_base.TestAttr.test_has_no_attrs',
+ self.id()
+ )
+
+ @testtools.testcase.attr('foo')
+ def test_has_one_attr(self):
+ self.assertEqual(
+ 'tempest.tests.lib.test_base.TestAttr.test_has_one_attr[foo]',
+ self.id()
+ )
+
+ @testtools.testcase.attr('foo')
+ @testtools.testcase.attr('bar')
+ def test_has_two_attrs(self):
+ self.assertEqual(
+ 'tempest.tests.lib.test_base.TestAttr.test_has_two_attrs[bar,foo]',
+ self.id(),
+ )
+
+
+class TestSetUpClass(base.BaseTestCase):
+
+ @classmethod
+ def setUpClass(cls): # noqa
+ """Simulate absence of super() call."""
+
+ def setUp(self):
+ try:
+ # We expect here RuntimeError exception because 'setUpClass'
+ # has not called 'super'.
+ super(TestSetUpClass, self).setUp()
+ except RuntimeError:
+ pass
+ else:
+ raise exceptions.TempestException(
+ "If you see this, then expected exception was not raised.")
+
+ def test_setup_class_raises_runtime_error(self):
+ """No-op test just to call setUp."""
diff --git a/tempest/tests/lib/test_credentials.py b/tempest/tests/lib/test_credentials.py
new file mode 100644
index 0000000..791fbb5
--- /dev/null
+++ b/tempest/tests/lib/test_credentials.py
@@ -0,0 +1,180 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib import auth
+from tempest.lib import exceptions
+from tempest.lib.services.identity.v2 import token_client as v2_client
+from tempest.lib.services.identity.v3 import token_client as v3_client
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_identity
+
+
+class CredentialsTests(base.TestCase):
+ attributes = {}
+ credentials_class = auth.Credentials
+
+ def _get_credentials(self, attributes=None):
+ if attributes is None:
+ attributes = self.attributes
+ return self.credentials_class(**attributes)
+
+ def _check(self, credentials, credentials_class, filled):
+ # Check the right version of credentials has been returned
+ self.assertIsInstance(credentials, credentials_class)
+ # Check the id attributes are filled in
+ attributes = [x for x in credentials.ATTRIBUTES if (
+ '_id' in x and x != 'domain_id')]
+ for attr in attributes:
+ if filled:
+ self.assertIsNotNone(getattr(credentials, attr))
+ else:
+ self.assertIsNone(getattr(credentials, attr))
+
+ def test_create(self):
+ creds = self._get_credentials()
+ self.assertEqual(self.attributes, creds._initial)
+
+ def test_create_invalid_attr(self):
+ self.assertRaises(exceptions.InvalidCredentials,
+ self._get_credentials,
+ attributes=dict(invalid='fake'))
+
+ def test_is_valid(self):
+ creds = self._get_credentials()
+ self.assertRaises(NotImplementedError, creds.is_valid)
+
+
+class KeystoneV2CredentialsTests(CredentialsTests):
+ attributes = {
+ 'username': 'fake_username',
+ 'password': 'fake_password',
+ 'tenant_name': 'fake_tenant_name'
+ }
+
+ identity_response = fake_identity._fake_v2_response
+ credentials_class = auth.KeystoneV2Credentials
+ tokenclient_class = v2_client.TokenClient
+ identity_version = 'v2'
+
+ def setUp(self):
+ super(KeystoneV2CredentialsTests, self).setUp()
+ self.stubs.Set(self.tokenclient_class, 'raw_request',
+ self.identity_response)
+
+ def _verify_credentials(self, credentials_class, creds_dict, filled=True):
+ creds = auth.get_credentials(fake_identity.FAKE_AUTH_URL,
+ fill_in=filled,
+ identity_version=self.identity_version,
+ **creds_dict)
+ self._check(creds, credentials_class, filled)
+
+ def test_get_credentials(self):
+ self._verify_credentials(credentials_class=self.credentials_class,
+ creds_dict=self.attributes)
+
+ def test_get_credentials_not_filled(self):
+ self._verify_credentials(credentials_class=self.credentials_class,
+ creds_dict=self.attributes,
+ filled=False)
+
+ def test_is_valid(self):
+ creds = self._get_credentials()
+ self.assertTrue(creds.is_valid())
+
+ def _test_is_not_valid(self, ignore_key):
+ creds = self._get_credentials()
+ for attr in self.attributes.keys():
+ if attr == ignore_key:
+ continue
+ temp_attr = getattr(creds, attr)
+ delattr(creds, attr)
+ self.assertFalse(creds.is_valid(),
+ "Credentials should be invalid without %s" % attr)
+ setattr(creds, attr, temp_attr)
+
+ def test_is_not_valid(self):
+ # NOTE(mtreinish): A KeystoneV2 credential object is valid without
+ # a tenant_name. So skip that check. See tempest.auth for the valid
+ # credential requirements
+ self._test_is_not_valid('tenant_name')
+
+ def test_reset_all_attributes(self):
+ creds = self._get_credentials()
+ initial_creds = copy.deepcopy(creds)
+ set_attr = creds.__dict__.keys()
+ missing_attr = set(creds.ATTRIBUTES).difference(set_attr)
+ # Set all unset attributes, then reset
+ for attr in missing_attr:
+ setattr(creds, attr, 'fake' + attr)
+ creds.reset()
+ # Check reset credentials are same as initial ones
+ self.assertEqual(creds, initial_creds)
+
+ def test_reset_single_attribute(self):
+ creds = self._get_credentials()
+ initial_creds = copy.deepcopy(creds)
+ set_attr = creds.__dict__.keys()
+ missing_attr = set(creds.ATTRIBUTES).difference(set_attr)
+ # Set one unset attributes, then reset
+ for attr in missing_attr:
+ setattr(creds, attr, 'fake' + attr)
+ creds.reset()
+ # Check reset credentials are same as initial ones
+ self.assertEqual(creds, initial_creds)
+
+
+class KeystoneV3CredentialsTests(KeystoneV2CredentialsTests):
+ attributes = {
+ 'username': 'fake_username',
+ 'password': 'fake_password',
+ 'project_name': 'fake_project_name',
+ 'user_domain_name': 'fake_domain_name'
+ }
+
+ credentials_class = auth.KeystoneV3Credentials
+ identity_response = fake_identity._fake_v3_response
+ tokenclient_class = v3_client.V3TokenClient
+ identity_version = 'v3'
+
+ def test_is_not_valid(self):
+ # NOTE(mtreinish) For a Keystone V3 credential object a project name
+ # is not required to be valid, so we skip that check. See tempest.auth
+ # for the valid credential requirements
+ self._test_is_not_valid('project_name')
+
+ def test_synced_attributes(self):
+ attributes = self.attributes
+ # Create V3 credentials with tenant instead of project, and user_domain
+ for attr in ['project_id', 'user_domain_id']:
+ attributes[attr] = 'fake_' + attr
+ creds = self._get_credentials(attributes)
+ self.assertEqual(creds.project_name, creds.tenant_name)
+ self.assertEqual(creds.project_id, creds.tenant_id)
+ self.assertEqual(creds.user_domain_name, creds.project_domain_name)
+ self.assertEqual(creds.user_domain_id, creds.project_domain_id)
+ # Replace user_domain with project_domain
+ del attributes['user_domain_name']
+ del attributes['user_domain_id']
+ del attributes['project_name']
+ del attributes['project_id']
+ for attr in ['project_domain_name', 'project_domain_id',
+ 'tenant_name', 'tenant_id']:
+ attributes[attr] = 'fake_' + attr
+ self.assertEqual(creds.tenant_name, creds.project_name)
+ self.assertEqual(creds.tenant_id, creds.project_id)
+ self.assertEqual(creds.project_domain_name, creds.user_domain_name)
+ self.assertEqual(creds.project_domain_id, creds.user_domain_id)
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
new file mode 100644
index 0000000..558445d
--- /dev/null
+++ b/tempest/tests/lib/test_decorators.py
@@ -0,0 +1,126 @@
+# Copyright 2013 IBM Corp
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 uuid
+
+import testtools
+
+from tempest.lib import base as test
+from tempest.lib import decorators
+from tempest.tests.lib import base
+
+
+class TestSkipBecauseDecorator(base.TestCase):
+ def _test_skip_because_helper(self, expected_to_skip=True,
+ **decorator_args):
+ class TestFoo(test.BaseTestCase):
+ _interface = 'json'
+
+ @decorators.skip_because(**decorator_args)
+ def test_bar(self):
+ return 0
+
+ t = TestFoo('test_bar')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ else:
+ # assert that test_bar returned 0
+ self.assertEqual(TestFoo('test_bar').test_bar(), 0)
+
+ def test_skip_because_bug(self):
+ self._test_skip_because_helper(bug='12345')
+
+ def test_skip_because_bug_and_condition_true(self):
+ self._test_skip_because_helper(bug='12348', condition=True)
+
+ def test_skip_because_bug_and_condition_false(self):
+ self._test_skip_because_helper(expected_to_skip=False,
+ bug='12349', condition=False)
+
+ def test_skip_because_bug_without_bug_never_skips(self):
+ """Never skip without a bug parameter."""
+ self._test_skip_because_helper(expected_to_skip=False,
+ condition=True)
+ self._test_skip_because_helper(expected_to_skip=False)
+
+ def test_skip_because_invalid_bug_number(self):
+ """Raise ValueError if with an invalid bug number"""
+ self.assertRaises(ValueError, self._test_skip_because_helper,
+ bug='critical_bug')
+
+
+class TestIdempotentIdDecorator(base.TestCase):
+ def _test_helper(self, _id, **decorator_args):
+ @decorators.idempotent_id(_id)
+ def foo():
+ """Docstring"""
+ pass
+
+ return foo
+
+ def _test_helper_without_doc(self, _id, **decorator_args):
+ @decorators.idempotent_id(_id)
+ def foo():
+ pass
+
+ return foo
+
+ def test_positive(self):
+ _id = str(uuid.uuid4())
+ foo = self._test_helper(_id)
+ self.assertIn('id-%s' % _id, getattr(foo, '__testtools_attrs'))
+ self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
+
+ def test_positive_without_doc(self):
+ _id = str(uuid.uuid4())
+ foo = self._test_helper_without_doc(_id)
+ self.assertTrue(foo.__doc__.startswith('Test idempotent id: %s' % _id))
+
+ def test_idempotent_id_not_str(self):
+ _id = 42
+ self.assertRaises(TypeError, self._test_helper, _id)
+
+ def test_idempotent_id_not_valid_uuid(self):
+ _id = '42'
+ self.assertRaises(ValueError, self._test_helper, _id)
+
+
+class TestSkipUnlessAttrDecorator(base.TestCase):
+ def _test_skip_unless_attr(self, attr, expected_to_skip=True):
+ class TestFoo(test.BaseTestCase):
+ expected_attr = not expected_to_skip
+
+ @decorators.skip_unless_attr(attr)
+ def test_foo(self):
+ pass
+
+ t = TestFoo('test_foo')
+ if expected_to_skip:
+ self.assertRaises(testtools.TestCase.skipException,
+ t.test_foo())
+ else:
+ try:
+ t.test_foo()
+ except Exception:
+ raise testtools.TestCase.failureException()
+
+ def test_skip_attr_does_not_exist(self):
+ self._test_skip_unless_attr('unexpected_attr')
+
+ def test_skip_attr_false(self):
+ self._test_skip_unless_attr('expected_attr')
+
+ def test_no_skip_for_attr_exist_and_true(self):
+ self._test_skip_unless_attr('expected_attr', expected_to_skip=False)
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py
new file mode 100644
index 0000000..6aff305
--- /dev/null
+++ b/tempest/tests/lib/test_rest_client.py
@@ -0,0 +1,1065 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import json
+
+import httplib2
+import jsonschema
+from oslotest import mockpatch
+import six
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+
+
+class BaseRestClientTestClass(base.TestCase):
+
+ url = 'fake_endpoint'
+
+ def setUp(self):
+ super(BaseRestClientTestClass, self).setUp()
+ self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
+ self.rest_client = rest_client.RestClient(
+ self.fake_auth_provider, None, None)
+ self.stubs.Set(httplib2.Http, 'request', self.fake_http.request)
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ '_log_request'))
+
+
+class TestRestClientHTTPMethods(BaseRestClientTestClass):
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientHTTPMethods, self).setUp()
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ '_error_checker'))
+
+ def test_post(self):
+ __, return_dict = self.rest_client.post(self.url, {}, {})
+ self.assertEqual('POST', return_dict['method'])
+
+ def test_get(self):
+ __, return_dict = self.rest_client.get(self.url)
+ self.assertEqual('GET', return_dict['method'])
+
+ def test_delete(self):
+ __, return_dict = self.rest_client.delete(self.url)
+ self.assertEqual('DELETE', return_dict['method'])
+
+ def test_patch(self):
+ __, return_dict = self.rest_client.patch(self.url, {}, {})
+ self.assertEqual('PATCH', return_dict['method'])
+
+ def test_put(self):
+ __, return_dict = self.rest_client.put(self.url, {}, {})
+ self.assertEqual('PUT', return_dict['method'])
+
+ def test_head(self):
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ 'response_checker'))
+ __, return_dict = self.rest_client.head(self.url)
+ self.assertEqual('HEAD', return_dict['method'])
+
+ def test_copy(self):
+ __, return_dict = self.rest_client.copy(self.url)
+ self.assertEqual('COPY', return_dict['method'])
+
+
+class TestRestClientNotFoundHandling(BaseRestClientTestClass):
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2(404)
+ super(TestRestClientNotFoundHandling, self).setUp()
+
+ def test_post(self):
+ self.assertRaises(exceptions.NotFound, self.rest_client.post,
+ self.url, {}, {})
+
+
+class TestRestClientHeadersJSON(TestRestClientHTTPMethods):
+ TYPE = "json"
+
+ def _verify_headers(self, resp):
+ self.assertEqual(self.rest_client._get_type(), self.TYPE)
+ resp = dict((k.lower(), v) for k, v in six.iteritems(resp))
+ self.assertEqual(self.header_value, resp['accept'])
+ self.assertEqual(self.header_value, resp['content-type'])
+
+ def setUp(self):
+ super(TestRestClientHeadersJSON, self).setUp()
+ self.rest_client.TYPE = self.TYPE
+ self.header_value = 'application/%s' % self.rest_client._get_type()
+
+ def test_post(self):
+ resp, __ = self.rest_client.post(self.url, {})
+ self._verify_headers(resp)
+
+ def test_get(self):
+ resp, __ = self.rest_client.get(self.url)
+ self._verify_headers(resp)
+
+ def test_delete(self):
+ resp, __ = self.rest_client.delete(self.url)
+ self._verify_headers(resp)
+
+ def test_patch(self):
+ resp, __ = self.rest_client.patch(self.url, {})
+ self._verify_headers(resp)
+
+ def test_put(self):
+ resp, __ = self.rest_client.put(self.url, {})
+ self._verify_headers(resp)
+
+ def test_head(self):
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ 'response_checker'))
+ resp, __ = self.rest_client.head(self.url)
+ self._verify_headers(resp)
+
+ def test_copy(self):
+ resp, __ = self.rest_client.copy(self.url)
+ self._verify_headers(resp)
+
+
+class TestRestClientUpdateHeaders(BaseRestClientTestClass):
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientUpdateHeaders, self).setUp()
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ '_error_checker'))
+ self.headers = {'X-Configuration-Session': 'session_id'}
+
+ def test_post_update_headers(self):
+ __, return_dict = self.rest_client.post(self.url, {},
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_get_update_headers(self):
+ __, return_dict = self.rest_client.get(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_delete_update_headers(self):
+ __, return_dict = self.rest_client.delete(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_patch_update_headers(self):
+ __, return_dict = self.rest_client.patch(self.url, {},
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_put_update_headers(self):
+ __, return_dict = self.rest_client.put(self.url, {},
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_head_update_headers(self):
+ self.useFixture(mockpatch.PatchObject(self.rest_client,
+ 'response_checker'))
+
+ __, return_dict = self.rest_client.head(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+ def test_copy_update_headers(self):
+ __, return_dict = self.rest_client.copy(self.url,
+ extra_headers=True,
+ headers=self.headers)
+
+ self.assertDictContainsSubset(
+ {'X-Configuration-Session': 'session_id',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'},
+ return_dict['headers']
+ )
+
+
+class TestRestClientParseRespJSON(BaseRestClientTestClass):
+ TYPE = "json"
+
+ keys = ["fake_key1", "fake_key2"]
+ values = ["fake_value1", "fake_value2"]
+ item_expected = dict((key, value) for (key, value) in zip(keys, values))
+ list_expected = {"body_list": [
+ {keys[0]: values[0]},
+ {keys[1]: values[1]},
+ ]}
+ dict_expected = {"body_dict": {
+ keys[0]: values[0],
+ keys[1]: values[1],
+ }}
+ null_dict = {}
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientParseRespJSON, self).setUp()
+ self.rest_client.TYPE = self.TYPE
+
+ def test_parse_resp_body_item(self):
+ body = self.rest_client._parse_resp(json.dumps(self.item_expected))
+ self.assertEqual(self.item_expected, body)
+
+ def test_parse_resp_body_list(self):
+ body = self.rest_client._parse_resp(json.dumps(self.list_expected))
+ self.assertEqual(self.list_expected["body_list"], body)
+
+ def test_parse_resp_body_dict(self):
+ body = self.rest_client._parse_resp(json.dumps(self.dict_expected))
+ self.assertEqual(self.dict_expected["body_dict"], body)
+
+ def test_parse_resp_two_top_keys(self):
+ dict_two_keys = self.dict_expected.copy()
+ dict_two_keys.update({"second_key": ""})
+ body = self.rest_client._parse_resp(json.dumps(dict_two_keys))
+ self.assertEqual(dict_two_keys, body)
+
+ def test_parse_resp_one_top_key_without_list_or_dict(self):
+ data = {"one_top_key": "not_list_or_dict_value"}
+ body = self.rest_client._parse_resp(json.dumps(data))
+ self.assertEqual(data, body)
+
+ def test_parse_nullable_dict(self):
+ body = self.rest_client._parse_resp(json.dumps(self.null_dict))
+ self.assertEqual(self.null_dict, body)
+
+
+class TestRestClientErrorCheckerJSON(base.TestCase):
+ c_type = "application/json"
+
+ def set_data(self, r_code, enc=None, r_body=None, absolute_limit=True):
+ if enc is None:
+ enc = self.c_type
+ resp_dict = {'status': r_code, 'content-type': enc}
+ resp_body = {'resp_body': 'fake_resp_body'}
+
+ if absolute_limit is False:
+ resp_dict.update({'retry-after': 120})
+ resp_body.update({'overLimit': {'message': 'fake_message'}})
+ resp = httplib2.Response(resp_dict)
+ data = {
+ "method": "fake_method",
+ "url": "fake_url",
+ "headers": "fake_headers",
+ "body": "fake_body",
+ "resp": resp,
+ "resp_body": json.dumps(resp_body)
+ }
+ if r_body is not None:
+ data.update({"resp_body": r_body})
+ return data
+
+ def setUp(self):
+ super(TestRestClientErrorCheckerJSON, self).setUp()
+ self.rest_client = rest_client.RestClient(
+ fake_auth_provider.FakeAuthProvider(), None, None)
+
+ def test_response_less_than_400(self):
+ self.rest_client._error_checker(**self.set_data("399"))
+
+ def _test_error_checker(self, exception_type, data):
+ e = self.assertRaises(exception_type,
+ self.rest_client._error_checker,
+ **data)
+ self.assertEqual(e.resp, data['resp'])
+ self.assertTrue(hasattr(e, 'resp_body'))
+ return e
+
+ def test_response_400(self):
+ self._test_error_checker(exceptions.BadRequest, self.set_data("400"))
+
+ def test_response_401(self):
+ self._test_error_checker(exceptions.Unauthorized, self.set_data("401"))
+
+ def test_response_403(self):
+ self._test_error_checker(exceptions.Forbidden, self.set_data("403"))
+
+ def test_response_404(self):
+ self._test_error_checker(exceptions.NotFound, self.set_data("404"))
+
+ def test_response_409(self):
+ self._test_error_checker(exceptions.Conflict, self.set_data("409"))
+
+ def test_response_410(self):
+ self._test_error_checker(exceptions.Gone, self.set_data("410"))
+
+ def test_response_413(self):
+ self._test_error_checker(exceptions.OverLimit, self.set_data("413"))
+
+ def test_response_413_without_absolute_limit(self):
+ self._test_error_checker(exceptions.RateLimitExceeded,
+ self.set_data("413", absolute_limit=False))
+
+ def test_response_415(self):
+ self._test_error_checker(exceptions.InvalidContentType,
+ self.set_data("415"))
+
+ def test_response_422(self):
+ self._test_error_checker(exceptions.UnprocessableEntity,
+ self.set_data("422"))
+
+ def test_response_500_with_text(self):
+ # _parse_resp is expected to return 'str'
+ self._test_error_checker(exceptions.ServerFault, self.set_data("500"))
+
+ def test_response_501_with_text(self):
+ self._test_error_checker(exceptions.NotImplemented,
+ self.set_data("501"))
+
+ def test_response_400_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.BadRequest,
+ self.set_data("400", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_401_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Unauthorized,
+ self.set_data("401", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_403_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Forbidden,
+ self.set_data("403", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_404_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.NotFound,
+ self.set_data("404", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_404_with_invalid_dict(self):
+ r_body = '{"foo": "bar"]'
+ e = self._test_error_checker(exceptions.NotFound,
+ self.set_data("404", r_body=r_body))
+
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_410_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Gone,
+ self.set_data("410", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_410_with_invalid_dict(self):
+ r_body = '{"foo": "bar"]'
+ e = self._test_error_checker(exceptions.Gone,
+ self.set_data("410", r_body=r_body))
+
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_409_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.Conflict,
+ self.set_data("409", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_500_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ e = self._test_error_checker(exceptions.ServerFault,
+ self.set_data("500", r_body=r_body))
+
+ if self.c_type == 'application/json':
+ expected = {"err": "fake_resp_body"}
+ else:
+ expected = r_body
+ self.assertEqual(expected, e.resp_body)
+
+ def test_response_501_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ self._test_error_checker(exceptions.NotImplemented,
+ self.set_data("501", r_body=r_body))
+
+ def test_response_bigger_than_400(self):
+ # Any response code, that bigger than 400, and not in
+ # (401, 403, 404, 409, 413, 422, 500, 501)
+ self._test_error_checker(exceptions.UnexpectedResponseCode,
+ self.set_data("402"))
+
+
+class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
+ c_type = "text/plain"
+
+ def test_fake_content_type(self):
+ # This test is required only in one exemplar
+ # Any response code, that bigger than 400, and not in
+ # (401, 403, 404, 409, 413, 422, 500, 501)
+ self._test_error_checker(exceptions.UnexpectedContentType,
+ self.set_data("405", enc="fake_enc"))
+
+ def test_response_413_without_absolute_limit(self):
+ # Skip this test because rest_client cannot get overLimit message
+ # from text body.
+ pass
+
+
+class TestRestClientUtils(BaseRestClientTestClass):
+
+ def _is_resource_deleted(self, resource_id):
+ if not isinstance(self.retry_pass, int):
+ return False
+ if self.retry_count >= self.retry_pass:
+ return True
+ self.retry_count = self.retry_count + 1
+ return False
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientUtils, self).setUp()
+ self.retry_count = 0
+ self.retry_pass = None
+ self.original_deleted_method = self.rest_client.is_resource_deleted
+ self.rest_client.is_resource_deleted = self._is_resource_deleted
+
+ def test_wait_for_resource_deletion(self):
+ self.retry_pass = 2
+ # Ensure timeout long enough for loop execution to hit retry count
+ self.rest_client.build_timeout = 500
+ sleep_mock = self.patch('time.sleep')
+ self.rest_client.wait_for_resource_deletion('1234')
+ self.assertEqual(len(sleep_mock.mock_calls), 2)
+
+ def test_wait_for_resource_deletion_not_deleted(self):
+ self.patch('time.sleep')
+ # Set timeout to be very quick to force exception faster
+ self.rest_client.build_timeout = 1
+ self.assertRaises(exceptions.TimeoutException,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
+
+ def test_wait_for_deletion_with_unimplemented_deleted_method(self):
+ self.rest_client.is_resource_deleted = self.original_deleted_method
+ self.assertRaises(NotImplementedError,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
+
+ def test_get_versions(self):
+ self.rest_client._parse_resp = lambda x: [{'id': 'v1'}, {'id': 'v2'}]
+ actual_resp, actual_versions = self.rest_client.get_versions()
+ self.assertEqual(['v1', 'v2'], list(actual_versions))
+
+ def test__str__(self):
+ def get_token():
+ return "deadbeef"
+
+ self.fake_auth_provider.get_token = get_token
+ self.assertIsNotNone(str(self.rest_client))
+
+
+class TestProperties(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestProperties, self).setUp()
+ creds_dict = {
+ 'username': 'test-user',
+ 'user_id': 'test-user_id',
+ 'tenant_name': 'test-tenant_name',
+ 'tenant_id': 'test-tenant_id',
+ 'password': 'test-password'
+ }
+ self.rest_client = rest_client.RestClient(
+ fake_auth_provider.FakeAuthProvider(creds_dict=creds_dict),
+ None, None)
+
+ def test_properties(self):
+ self.assertEqual('test-user', self.rest_client.user)
+ self.assertEqual('test-user_id', self.rest_client.user_id)
+ self.assertEqual('test-tenant_name', self.rest_client.tenant_name)
+ self.assertEqual('test-tenant_id', self.rest_client.tenant_id)
+ self.assertEqual('test-password', self.rest_client.password)
+
+ self.rest_client.api_version = 'v1'
+ expected = {'api_version': 'v1',
+ 'endpoint_type': 'publicURL',
+ 'region': None,
+ 'service': None,
+ 'skip_path': True}
+ self.rest_client.skip_path()
+ self.assertEqual(expected, self.rest_client.filters)
+
+ self.rest_client.reset_path()
+ self.rest_client.api_version = 'v1'
+ expected = {'api_version': 'v1',
+ 'endpoint_type': 'publicURL',
+ 'region': None,
+ 'service': None}
+ self.assertEqual(expected, self.rest_client.filters)
+
+
+class TestExpectedSuccess(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestExpectedSuccess, self).setUp()
+
+ def test_expected_succes_int_match(self):
+ expected_code = 202
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_int_no_match(self):
+ expected_code = 204
+ read_code = 202
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_expected_succes_list_match(self):
+ expected_code = [202, 204]
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_list_no_match(self):
+ expected_code = [202, 204]
+ read_code = 200
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_int(self):
+ expected_code = 404
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_list(self):
+ expected_code = [404, 202]
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+
+class TestResponseBody(base.TestCase):
+
+ def test_str(self):
+ response = {'status': 200}
+ body = {'key1': 'value1'}
+ actual = rest_client.ResponseBody(response, body)
+ self.assertEqual("response: %s\nBody: %s" % (response, body),
+ str(actual))
+
+
+class TestResponseBodyData(base.TestCase):
+
+ def test_str(self):
+ response = {'status': 200}
+ data = 'data1'
+ actual = rest_client.ResponseBodyData(response, data)
+ self.assertEqual("response: %s\nBody: %s" % (response, data),
+ str(actual))
+
+
+class TestResponseBodyList(base.TestCase):
+
+ def test_str(self):
+ response = {'status': 200}
+ body = ['value1', 'value2', 'value3']
+ actual = rest_client.ResponseBodyList(response, body)
+ self.assertEqual("response: %s\nBody: %s" % (response, body),
+ str(actual))
+
+
+class TestJSONSchemaValidationBase(base.TestCase):
+
+ class Response(dict):
+
+ def __getattr__(self, attr):
+ return self[attr]
+
+ def __setattr__(self, attr, value):
+ self[attr] = value
+
+ def setUp(self):
+ super(TestJSONSchemaValidationBase, self).setUp()
+ self.fake_auth_provider = fake_auth_provider.FakeAuthProvider()
+ self.rest_client = rest_client.RestClient(
+ self.fake_auth_provider, None, None)
+
+ def _test_validate_pass(self, schema, resp_body, status=200):
+ resp = self.Response()
+ resp.status = status
+ self.rest_client.validate_response(schema, resp, resp_body)
+
+ def _test_validate_fail(self, schema, resp_body, status=200,
+ error_msg="HTTP response body is invalid"):
+ resp = self.Response()
+ resp.status = status
+ ex = self.assertRaises(exceptions.InvalidHTTPResponseBody,
+ self.rest_client.validate_response,
+ schema, resp, resp_body)
+ self.assertIn(error_msg, ex._error_string)
+
+
+class TestRestClientJSONSchemaValidation(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'integer',
+ },
+ },
+ 'required': ['foo']
+ }
+ }
+
+ def test_validate_pass_with_http_success_code(self):
+ body = {'foo': 12}
+ self._test_validate_pass(self.schema, body, status=200)
+
+ def test_validate_pass_with_http_redirect_code(self):
+ body = {'foo': 12}
+ schema = copy.deepcopy(self.schema)
+ schema['status_code'] = 300
+ self._test_validate_pass(schema, body, status=300)
+
+ def test_validate_not_http_success_code(self):
+ schema = {
+ 'status_code': [200]
+ }
+ body = {}
+ self._test_validate_pass(schema, body, status=400)
+
+ def test_validate_multiple_allowed_type(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': ['integer', 'string'],
+ },
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12}
+ self._test_validate_pass(schema, body)
+ body = {'foo': '12'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_enable_additional_property_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'additionalProperties': True,
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12, 'foo2': 'foo2value'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_disable_additional_property_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_disable_additional_property_fail(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 12, 'foo2': 'foo2value'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_wrong_status_code(self):
+ schema = {
+ 'status_code': [202]
+ }
+ body = {}
+ resp = self.Response()
+ resp.status = 200
+ ex = self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.validate_response,
+ schema, resp, body)
+ self.assertIn("Unexpected http success status code", ex._error_string)
+
+ def test_validate_wrong_attribute_type(self):
+ body = {'foo': 1.2}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_unexpected_response_body(self):
+ schema = {
+ 'status_code': [200],
+ }
+ body = {'foo': 12}
+ self._test_validate_fail(
+ schema, body,
+ error_msg="HTTP response body should not exist")
+
+ def test_validate_missing_response_body(self):
+ body = {}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_missing_required_attribute(self):
+ body = {'notfoo': 12}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_response_body_not_list(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'list_items': {
+ 'type': 'array',
+ 'items': {'foo': {'type': 'integer'}}
+ }
+ },
+ 'required': ['list_items'],
+ }
+ }
+ body = {'foo': 12}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_response_body_list_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'list_items': {
+ 'type': 'array',
+ 'items': {'foo': {'type': 'integer'}}
+ }
+ },
+ 'required': ['list_items'],
+ }
+ }
+ body = {'list_items': [{'foo': 12}, {'foo': 10}]}
+ self._test_validate_pass(schema, body)
+
+
+class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_header': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'integer'}
+ },
+ 'required': ['foo']
+ }
+ }
+
+ def test_validate_header_schema_pass(self):
+ resp_body = {}
+ resp = self.Response()
+ resp.status = 200
+ resp.foo = 12
+ self.rest_client.validate_response(self.schema, resp, resp_body)
+
+ def test_validate_header_schema_fail(self):
+ resp_body = {}
+ resp = self.Response()
+ resp.status = 200
+ resp.foo = 1.2
+ ex = self.assertRaises(exceptions.InvalidHTTPResponseHeader,
+ self.rest_client.validate_response,
+ self.schema, resp, resp_body)
+ self.assertIn("HTTP response header is invalid", ex._error_string)
+
+
+class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'format': 'email'
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+
+ def test_validate_format_pass(self):
+ body = {'foo': 'example@example.com'}
+ self._test_validate_pass(self.schema, body)
+
+ def test_validate_format_fail(self):
+ body = {'foo': 'wrong_email'}
+ self._test_validate_fail(self.schema, body)
+
+ def test_validate_formats_in_oneOf_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_pass(schema, body)
+ body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_formats_in_oneOf_fail_both_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv4'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_formats_in_oneOf_fail_no_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'oneOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 'wrong_ip_format'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_formats_in_anyOf_pass(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'anyOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_pass(schema, body)
+ body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_formats_in_anyOf_pass_both_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'anyOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv4'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': '10.0.0.0'}
+ self._test_validate_pass(schema, body)
+
+ def test_validate_formats_in_anyOf_fail_no_match(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'anyOf': [
+ {'format': 'ipv4'},
+ {'format': 'ipv6'}
+ ]
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 'wrong_ip_format'}
+ self._test_validate_fail(schema, body)
+
+ def test_validate_formats_pass_for_unknow_format(self):
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {
+ 'type': 'string',
+ 'format': 'UNKNOWN'
+ }
+ },
+ 'required': ['foo']
+ }
+ }
+ body = {'foo': 'example@example.com'}
+ self._test_validate_pass(schema, body)
+
+
+class TestRestClientJSONSchemaValidatorVersion(TestJSONSchemaValidationBase):
+
+ schema = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'foo': {'type': 'string'}
+ }
+ }
+ }
+
+ def test_current_json_schema_validator_version(self):
+ with mockpatch.PatchObject(jsonschema.Draft4Validator,
+ "check_schema") as chk_schema:
+ body = {'foo': 'test'}
+ self._test_validate_pass(self.schema, body)
+ chk_schema.mock.assert_called_once_with(
+ self.schema['response_body'])
diff --git a/tempest/tests/lib/test_ssh.py b/tempest/tests/lib/test_ssh.py
new file mode 100644
index 0000000..7a4fc09
--- /dev/null
+++ b/tempest/tests/lib/test_ssh.py
@@ -0,0 +1,253 @@
+# Copyright 2014 OpenStack Foundation
+#
+# 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 io import StringIO
+import socket
+import time
+
+import mock
+import six
+import testtools
+
+from tempest.lib.common import ssh
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+
+
+class TestSshClient(base.TestCase):
+
+ SELECT_POLLIN = 1
+
+ @mock.patch('paramiko.RSAKey.from_private_key')
+ @mock.patch('six.StringIO')
+ def test_pkey_calls_paramiko_RSAKey(self, cs_mock, rsa_mock):
+ cs_mock.return_value = mock.sentinel.csio
+ pkey = 'mykey'
+ ssh.Client('localhost', 'root', pkey=pkey)
+ rsa_mock.assert_called_once_with(mock.sentinel.csio)
+ cs_mock.assert_called_once_with('mykey')
+ rsa_mock.reset_mock()
+ cs_mock.reset_mock()
+ pkey = mock.sentinel.pkey
+ # Shouldn't call out to load a file from RSAKey, since
+ # a sentinel isn't a basestring...
+ ssh.Client('localhost', 'root', pkey=pkey)
+ self.assertEqual(0, rsa_mock.call_count)
+ self.assertEqual(0, cs_mock.call_count)
+
+ def _set_ssh_connection_mocks(self):
+ client_mock = mock.MagicMock()
+ client_mock.connect.return_value = True
+ return (self.patch('paramiko.SSHClient'),
+ self.patch('paramiko.AutoAddPolicy'),
+ client_mock)
+
+ def test_get_ssh_connection(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+ s_mock = self.patch('time.sleep')
+
+ c_mock.return_value = client_mock
+ aa_mock.return_value = mock.sentinel.aa
+
+ # Test normal case for successful connection on first try
+ client = ssh.Client('localhost', 'root', timeout=2)
+ client._get_ssh_connection(sleep=1)
+
+ aa_mock.assert_called_once_with()
+ client_mock.set_missing_host_key_policy.assert_called_once_with(
+ mock.sentinel.aa)
+ expected_connect = [mock.call(
+ 'localhost',
+ username='root',
+ pkey=None,
+ key_filename=None,
+ look_for_keys=False,
+ timeout=10.0,
+ password=None
+ )]
+ self.assertEqual(expected_connect, client_mock.connect.mock_calls)
+ self.assertEqual(0, s_mock.call_count)
+
+ def test_get_ssh_connection_two_attemps(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+
+ c_mock.return_value = client_mock
+ client_mock.connect.side_effect = [
+ socket.error,
+ mock.MagicMock()
+ ]
+
+ client = ssh.Client('localhost', 'root', timeout=1)
+ start_time = int(time.time())
+ client._get_ssh_connection(sleep=1)
+ end_time = int(time.time())
+ self.assertLess((end_time - start_time), 4)
+ self.assertGreater((end_time - start_time), 1)
+
+ def test_get_ssh_connection_timeout(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+
+ c_mock.return_value = client_mock
+ client_mock.connect.side_effect = [
+ socket.error,
+ socket.error,
+ socket.error,
+ ]
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ start_time = int(time.time())
+ with testtools.ExpectedException(exceptions.SSHTimeout):
+ client._get_ssh_connection()
+ end_time = int(time.time())
+ self.assertLess((end_time - start_time), 5)
+ self.assertGreaterEqual((end_time - start_time), 2)
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_timeout_in_exec_command(self):
+ chan_mock, poll_mock, _ = self._set_mocks_for_select([0, 0, 0], True)
+
+ # Test for a timeout condition immediately raised
+ client = ssh.Client('localhost', 'root', timeout=2)
+ with testtools.ExpectedException(exceptions.TimeoutException):
+ client.exec_command("test")
+
+ chan_mock.fileno.assert_called_once_with()
+ chan_mock.exec_command.assert_called_once_with("test")
+ chan_mock.shutdown_write.assert_called_once_with()
+
+ poll_mock.register.assert_called_once_with(
+ chan_mock, self.SELECT_POLLIN)
+ poll_mock.poll.assert_called_once_with(10)
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_exec_command(self):
+ chan_mock, poll_mock, select_mock = (
+ self._set_mocks_for_select([[1, 0, 0]], True))
+ closed_prop = mock.PropertyMock(return_value=True)
+ type(chan_mock).closed = closed_prop
+
+ chan_mock.recv_exit_status.return_value = 0
+ chan_mock.recv.return_value = b''
+ chan_mock.recv_stderr.return_value = b''
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ client.exec_command("test")
+
+ chan_mock.fileno.assert_called_once_with()
+ chan_mock.exec_command.assert_called_once_with("test")
+ chan_mock.shutdown_write.assert_called_once_with()
+
+ select_mock.assert_called_once_with()
+ poll_mock.register.assert_called_once_with(
+ chan_mock, self.SELECT_POLLIN)
+ poll_mock.poll.assert_called_once_with(10)
+ chan_mock.recv_ready.assert_called_once_with()
+ chan_mock.recv.assert_called_once_with(1024)
+ chan_mock.recv_stderr_ready.assert_called_once_with()
+ chan_mock.recv_stderr.assert_called_once_with(1024)
+ chan_mock.recv_exit_status.assert_called_once_with()
+ closed_prop.assert_called_once_with()
+
+ def _set_mocks_for_select(self, poll_data, ito_value=False):
+ gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
+ '_get_ssh_connection')
+ ito_mock = self.patch('tempest.lib.common.ssh.Client._is_timed_out')
+ csp_mock = self.patch(
+ 'tempest.lib.common.ssh.Client._can_system_poll')
+ csp_mock.return_value = True
+
+ select_mock = self.patch('select.poll', create=True)
+ client_mock = mock.MagicMock()
+ tran_mock = mock.MagicMock()
+ chan_mock = mock.MagicMock()
+ poll_mock = mock.MagicMock()
+
+ select_mock.return_value = poll_mock
+ gsc_mock.return_value = client_mock
+ ito_mock.return_value = ito_value
+ client_mock.get_transport.return_value = tran_mock
+ tran_mock.open_session.return_value = chan_mock
+ if isinstance(poll_data[0], list):
+ poll_mock.poll.side_effect = poll_data
+ else:
+ poll_mock.poll.return_value = poll_data
+
+ return chan_mock, poll_mock, select_mock
+
+ _utf8_string = six.unichr(1071)
+ _utf8_bytes = _utf8_string.encode("utf-8")
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_exec_good_command_output(self):
+ chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
+ closed_prop = mock.PropertyMock(return_value=True)
+ type(chan_mock).closed = closed_prop
+
+ chan_mock.recv_exit_status.return_value = 0
+ chan_mock.recv.side_effect = [self._utf8_bytes[0:1],
+ self._utf8_bytes[1:], b'R', b'']
+ chan_mock.recv_stderr.return_value = b''
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ out_data = client.exec_command("test")
+ self.assertEqual(self._utf8_string + 'R', out_data)
+
+ @mock.patch('select.POLLIN', SELECT_POLLIN, create=True)
+ def test_exec_bad_command_output(self):
+ chan_mock, poll_mock, _ = self._set_mocks_for_select([1, 0, 0])
+ closed_prop = mock.PropertyMock(return_value=True)
+ type(chan_mock).closed = closed_prop
+
+ chan_mock.recv_exit_status.return_value = 1
+ chan_mock.recv.return_value = b''
+ chan_mock.recv_stderr.side_effect = [b'R', self._utf8_bytes[0:1],
+ self._utf8_bytes[1:], b'']
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ exc = self.assertRaises(exceptions.SSHExecCommandFailed,
+ client.exec_command, "test")
+ self.assertIn('R' + self._utf8_string, six.text_type(exc))
+
+ def test_exec_command_no_select(self):
+ gsc_mock = self.patch('tempest.lib.common.ssh.Client.'
+ '_get_ssh_connection')
+ csp_mock = self.patch(
+ 'tempest.lib.common.ssh.Client._can_system_poll')
+ csp_mock.return_value = False
+
+ select_mock = self.patch('select.poll', create=True)
+ client_mock = mock.MagicMock()
+ tran_mock = mock.MagicMock()
+ chan_mock = mock.MagicMock()
+
+ # Test for proper reading of STDOUT and STDERROR
+
+ gsc_mock.return_value = client_mock
+ client_mock.get_transport.return_value = tran_mock
+ tran_mock.open_session.return_value = chan_mock
+ chan_mock.recv_exit_status.return_value = 0
+
+ std_out_mock = mock.MagicMock(StringIO)
+ std_err_mock = mock.MagicMock(StringIO)
+ chan_mock.makefile.return_value = std_out_mock
+ chan_mock.makefile_stderr.return_value = std_err_mock
+
+ client = ssh.Client('localhost', 'root', timeout=2)
+ client.exec_command("test")
+
+ chan_mock.makefile.assert_called_once_with('rb', 1024)
+ chan_mock.makefile_stderr.assert_called_once_with('rb', 1024)
+ std_out_mock.read.assert_called_once_with()
+ std_err_mock.read.assert_called_once_with()
+ self.assertFalse(select_mock.called)
diff --git a/tempest/tests/lib/test_tempest_lib.py b/tempest/tests/lib/test_tempest_lib.py
new file mode 100644
index 0000000..9731e96
--- /dev/null
+++ b/tempest/tests/lib/test_tempest_lib.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+"""
+test_tempest.lib
+----------------------------------
+
+Tests for `tempest.lib` module.
+"""
+
+from tempest.tests.lib import base
+
+
+class TestTempest_lib(base.TestCase):
+
+ def test_something(self):
+ pass
diff --git a/tempest/tests/services/compute/test_base_compute_client.py b/tempest/tests/services/compute/test_base_compute_client.py
index 7a55cdb..1a78247 100644
--- a/tempest/tests/services/compute/test_base_compute_client.py
+++ b/tempest/tests/services/compute/test_base_compute_client.py
@@ -13,10 +13,12 @@
# under the License.
import httplib2
+import mock
from oslotest import mockpatch
-from tempest_lib.common import rest_client
+from tempest.api.compute import api_microversion_fixture
from tempest import exceptions
+from tempest.lib.common import rest_client
from tempest.services.compute.json import base_compute_client
from tempest.tests import fake_auth_provider
from tempest.tests.services.compute import base
@@ -29,7 +31,7 @@
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = base_compute_client.BaseComputeClient(
fake_auth, 'compute', 'regionOne')
- self.client.set_api_microversion('2.2')
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture('2.2'))
def _check_microverion_header_in_response(self, fake_response):
def request(*args, **kwargs):
@@ -75,7 +77,8 @@
super(TestSchemaVersionsNone, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = DummyServiceClient1(fake_auth, 'compute', 'regionOne')
- self.client.api_microversion = self.api_microversion
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+ self.api_microversion))
def test_schema(self):
self.assertEqual(self.expected_schema,
@@ -129,8 +132,62 @@
super(TestSchemaVersionsNotFound, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = DummyServiceClient2(fake_auth, 'compute', 'regionOne')
- self.client.api_microversion = self.api_microversion
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+ self.api_microversion))
def test_schema(self):
self.assertRaises(exceptions.JSONSchemaNotFound,
self.client.return_selected_schema)
+
+
+class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestClientWithoutMicroversionHeader, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_compute_client.BaseComputeClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def test_no_microverion_header(self):
+ header = self.client.get_headers()
+ self.assertNotIn('X-OpenStack-Nova-API-Version', header)
+
+ def test_no_microverion_header_in_raw_request(self):
+ def raw_request(*args, **kwargs):
+ self.assertNotIn('X-OpenStack-Nova-API-Version', kwargs['headers'])
+ return (httplib2.Response({'status': 200}), {})
+
+ with mock.patch.object(rest_client.RestClient,
+ 'raw_request') as mock_get:
+ mock_get.side_effect = raw_request
+ self.client.get('fake_url')
+
+
+class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
+
+ def setUp(self):
+ super(TestClientWithMicroversionHeader, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_compute_client.BaseComputeClient(
+ fake_auth, 'compute', 'regionOne')
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture('2.2'))
+
+ def test_microverion_header(self):
+ header = self.client.get_headers()
+ self.assertIn('X-OpenStack-Nova-API-Version', header)
+ self.assertEqual('2.2',
+ header['X-OpenStack-Nova-API-Version'])
+
+ def test_microverion_header_in_raw_request(self):
+ def raw_request(*args, **kwargs):
+ self.assertIn('X-OpenStack-Nova-API-Version', kwargs['headers'])
+ self.assertEqual('2.2',
+ kwargs['headers']['X-OpenStack-Nova-API-Version'])
+ return (httplib2.Response(
+ {'status': 200,
+ self.client.api_microversion_header_name: '2.2'}), {})
+
+ with mock.patch.object(rest_client.RestClient,
+ 'raw_request') as mock_get:
+ mock_get.side_effect = raw_request
+ self.client.get('fake_url')
diff --git a/tempest/tests/services/compute/test_keypairs_client.py b/tempest/tests/services/compute/test_keypairs_client.py
index 03aee53..e8f8280 100644
--- a/tempest/tests/services/compute/test_keypairs_client.py
+++ b/tempest/tests/services/compute/test_keypairs_client.py
@@ -14,9 +14,8 @@
import copy
-from tempest_lib.tests import fake_auth_provider
-
from tempest.services.compute.json import keypairs_client
+from tempest.tests.lib import fake_auth_provider
from tempest.tests.services.compute import base
@@ -38,7 +37,7 @@
def _test_list_keypairs(self, bytes_body=False):
self.check_service_client_function(
self.client.list_keypairs,
- 'tempest_lib.common.rest_client.RestClient.get',
+ 'tempest.lib.common.rest_client.RestClient.get',
{"keypairs": []},
bytes_body)
@@ -60,7 +59,7 @@
self.check_service_client_function(
self.client.show_keypair,
- 'tempest_lib.common.rest_client.RestClient.get',
+ 'tempest.lib.common.rest_client.RestClient.get',
fake_keypair,
bytes_body,
keypair_name="test")
@@ -77,7 +76,7 @@
self.check_service_client_function(
self.client.create_keypair,
- 'tempest_lib.common.rest_client.RestClient.post',
+ 'tempest.lib.common.rest_client.RestClient.post',
fake_keypair,
bytes_body,
name="test")
@@ -91,5 +90,5 @@
def test_delete_keypair(self):
self.check_service_client_function(
self.client.delete_keypair,
- 'tempest_lib.common.rest_client.RestClient.delete',
+ 'tempest.lib.common.rest_client.RestClient.delete',
{}, status=202, keypair_name='test')
diff --git a/tempest/tests/services/test_base_microversion_client.py b/tempest/tests/services/test_base_microversion_client.py
deleted file mode 100644
index 11b8170..0000000
--- a/tempest/tests/services/test_base_microversion_client.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2016 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import httplib2
-import mock
-from tempest_lib.common import rest_client
-
-from tempest.services import base_microversion_client
-from tempest.tests import fake_auth_provider
-from tempest.tests.services.compute import base
-
-
-class TestClientWithoutMicroversionHeader(base.BaseComputeServiceTest):
-
- def setUp(self):
- super(TestClientWithoutMicroversionHeader, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = base_microversion_client.BaseMicroversionClient(
- fake_auth, 'compute', 'regionOne', 'X-OpenStack-Nova-API-Version')
-
- def test_no_microverion_header(self):
- header = self.client.get_headers()
- self.assertNotIn(self.client.api_microversion_header_name, header)
-
- def test_no_microverion_header_in_raw_request(self):
- def raw_request(*args, **kwargs):
- self.assertNotIn(self.client.api_microversion_header_name,
- kwargs['headers'])
- return (httplib2.Response({'status': 200}), {})
-
- with mock.patch.object(rest_client.RestClient,
- 'raw_request') as mock_get:
- mock_get.side_effect = raw_request
- self.client.get('fake_url')
-
-
-class TestClientWithMicroversionHeader(base.BaseComputeServiceTest):
-
- def setUp(self):
- super(TestClientWithMicroversionHeader, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = base_microversion_client.BaseMicroversionClient(
- fake_auth, 'compute', 'regionOne', 'X-OpenStack-Nova-API-Version')
- self.client.set_api_microversion('2.2')
-
- def test_microverion_header(self):
- header = self.client.get_headers()
- self.assertIn(self.client.api_microversion_header_name, header)
- self.assertEqual(self.client.api_microversion,
- header[self.client.api_microversion_header_name])
-
- def test_microverion_header_in_raw_request(self):
- def raw_request(*args, **kwargs):
- self.assertIn(self.client.api_microversion_header_name,
- kwargs['headers'])
- self.assertEqual(
- self.client.api_microversion,
- kwargs['headers'][self.client.api_microversion_header_name])
- return (httplib2.Response({'status': 200}), {})
-
- with mock.patch.object(rest_client.RestClient,
- 'raw_request') as mock_get:
- mock_get.side_effect = raw_request
- self.client.get('fake_url')
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
index 0ec2a5d..dfe0291 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -16,9 +16,8 @@
import shlex
import subprocess
-from tempest_lib import exceptions
-
from oslo_log import log as logging
+from tempest.lib import exceptions
from tempest.tests import base
LOG = logging.getLogger(__name__)
diff --git a/tempest/version.py b/tempest/version.py
new file mode 100644
index 0000000..bc9f651
--- /dev/null
+++ b/tempest/version.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# 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 pbr.version
+
+version_info = pbr.version.VersionInfo('tempest')
diff --git a/test-requirements.txt b/test-requirements.txt
index eb43f31..a92adb0 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -6,6 +6,7 @@
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
+reno>=0.1.1 # Apache2
mox>=0.5.3 # Apache-2.0
mock>=1.2 # BSD
coverage>=3.6 # Apache-2.0
diff --git a/tools/use_tempest_lib.sh b/tools/use_tempest_lib.sh
deleted file mode 100755
index ca62c4a..0000000
--- a/tools/use_tempest_lib.sh
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/bin/bash
-#
-# Use this script to use interfaces/files from tempest-lib.
-# Many files have been migrated to tempest-lib and tempest has
-# its own copy too.
-# This script helps to remove those from tempest and make use of tempest-lib.
-# It adds the change-id of each file on which they were migrated in lib.
-# This should only be done for files which were migrated to lib with
-# "Migrated" in commit message as done by tempest-lib/tools/migrate_from_tempest.sh script.
-# "Migrated" keyword is used to fetch the migration commit history from lib.
-# To use:
-# 1. Create a new branch in the tempest repo so not to destroy your current
-# working branch
-# 2. Run the script from the repo dir and specify the file paths relative to
-# the root tempest dir(only code and unit tests):
-#
-# tools/use_tempest_lib.sh.sh tempest/file1.py tempest/file2.py
-
-
-function usage {
- echo "Usage: $0 [OPTION] file1 file2 .."
- echo "Use files from tempest-lib"
- echo -e "Input files should be tempest files with path. \n Example- tempest/file1.py tempest/file2.py .."
- echo ""
- echo "-s, --service_client Specify if files are service clients."
- echo "-u, --tempest_lib_git_url Specify the repo to clone tempest-lib from."
-}
-
-function check_all_files_valid {
- failed=0
- for file in $files; do
- # Get the latest change-id for each file
- latest_commit_id=`git log -n1 -- $file | grep "^commit" | awk '{print $2}'`
- cd $tmpdir
- filename=`basename $file`
- lib_path=`find ./ -name $filename`
- if [ -z $lib_path ]; then
- echo "ERROR: $filename does not exist in tempest-lib."
- failed=$(( failed + 1))
- cd - > /dev/null
- continue
- fi
- # Get the CHANGE_ID of tempest-lib patch where file was migrated
- migration_change_id=`git log -n1 --grep "Migrated" -- $lib_path | grep "Change-Id: " | awk '{print $2}'`
- MIGRATION_IDS=`echo -e "$MIGRATION_IDS\n * $filename: $migration_change_id"`
- # Get tempest CHANGE_ID of file which was migrated to lib
- migrated_change_id=`git log -n1 --grep "Migrated" -- $lib_path | grep "* $filename"`
- migrated_change_id=${migrated_change_id#*:}
- cd - > /dev/null
- # Get the commit-id of tempest which was migrated to tempest-lib
- migrated_commit_id=`git log --grep "$migrated_change_id" -- $file | grep "^commit" | awk '{print $2}'`
- DIFF=$(git diff $latest_commit_id $migrated_commit_id $file)
- if [ "$DIFF" != "" ]; then
- echo "ERROR: $filename in tempest has been updated after migration to tempest-lib. First sync the file to tempest-lib."
- failed=$(( failed + 1))
- fi
- done
- if [[ $failed -gt 0 ]]; then
- echo "$failed files had issues"
- exit $failed
- fi
-}
-
-set -e
-
-service_client=0
-file_list=''
-
-while [ $# -gt 0 ]; do
- case "$1" in
- -h|--help) usage; exit;;
- -u|--tempest_lib_git_url) tempest_lib_git_url="$2"; shift;;
- -s|--service_client) service_client=1;;
- *) files="$files $1";;
- esac
- shift
-done
-
-if [ -z "$files" ]; then
- usage; exit
-fi
-
-TEMPEST_LIB_GIT_URL=${tempest_lib_git_url:-git://git.openstack.org/openstack/tempest-lib}
-
-tmpdir=$(mktemp -d -t use-tempest-lib.XXXX)
-
-# Clone tempest-lib
-git clone $TEMPEST_LIB_GIT_URL $tmpdir
-
-# Checks all provided files are present in lib and
-# not updated in tempest after migration to lib.
-check_all_files_valid
-
-for file in $files; do
- rm -f $file
- tempest_dir=`pwd`
- tempest_dir="$tempest_dir/tempest/"
- tempest_dirname=`dirname $file`
- lib_dirname=`echo $tempest_dirname | sed s@tempest\/@tempest_lib/\@`
- # Convert tempest dirname to import string
- tempest_import="${tempest_dirname//\//.}"
- tempest_import=${tempest_import:2:${#tempest_import}}
- if [ $service_client -eq 1 ]; then
- # Remove /json path because tempest-lib supports JSON only without XML
- lib_dirname=`echo $lib_dirname | sed s@\/json@@`
- fi
- # Convert tempest-lib dirname to import string
- tempest_lib_import="${lib_dirname//\//.}"
- tempest_lib_import=${tempest_lib_import:2:${#tempest_lib_import}}
- module_name=`basename $file .py`
- tempest_import1="from $tempest_import.$module_name"
- tempest_lib_import1="from $tempest_lib_import.$module_name"
- tempest_import2="from $tempest_import import $module_name"
- tempest_lib_import2="from $tempest_lib_import import $module_name"
- set +e
- grep -rl "$tempest_import1" $tempest_dir | xargs sed -i'' s/"$tempest_import1"/"$tempest_lib_import1"/g 2> /dev/null
- grep -rl "$tempest_import2" $tempest_dir | xargs sed -i'' s/"$tempest_import2"/"$tempest_lib_import2"/g 2> /dev/null
- set -e
- if [[ -z "$file_list" ]]; then
- file_list="$module_name"
- else
- tmp_file_list="$file_list, $module_name"
- char_size=`echo $tmp_file_list | wc -c`
- if [ $char_size -lt 27 ]; then
- file_list="$file_list, $module_name"
- fi
- fi
-done
-
-rm -rf $tmpdir
-echo "Completed. Run pep8 and fix error if any"
-
-git add -A tempest/
-# Generate a migration commit
-commit_message="Use $file_list from tempest-lib"
-pre_list=$"The files below have been migrated to tempest-lib\n"
-pre_list=`echo -e $pre_list`
-post_list=$"Now Tempest-lib provides those as stable interfaces. So Tempest should\nstart using those from lib and remove its own copy."
-post_list=`echo -e $post_list`
-
-git commit -m "$commit_message" -m "$pre_list" -m "$MIGRATION_IDS" -m "$post_list"
diff --git a/tox.ini b/tox.ini
index 95f2cf1..96a97f4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -132,3 +132,6 @@
ignore = E125,E123,E129
show-source = True
exclude = .git,.venv,.tox,dist,doc,openstack,*egg
+
+[testenv:releasenotes]
+commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html