Merge "Get NIC name by "ip -o link""
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/README.rst b/README.rst
index 71e185f..c859ddd 100644
--- a/README.rst
+++ b/README.rst
@@ -94,6 +94,45 @@
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
.. _ostestr: http://docs.openstack.org/developer/os-testr/
+Library
+-------
+Tempest exposes a library interface. This interface is a stable interface and
+should be backwards compatible (including backwards compatibility with the
+old tempest-lib package, with the exception of the import). If you plan to
+directly consume tempest in your project you should only import code from the
+tempest library interface, other pieces of tempest do not have the same
+stable interface and there are no guarantees on the Python API unless otherwise
+stated.
+
+For more details refer to the library documentation here: :ref:`library`
+
+Release Versioning
+------------------
+Tempest's released versions are broken into 2 sets of information. Depending on
+how you intend to consume tempest you might need
+
+The version is a set of 3 numbers:
+
+X.Y.Z
+
+While this is almost `semver`_ like, the way versioning is handled is slightly
+different:
+
+X is used to represent the supported OpenStack releases for tempest tests
+in-tree, and to signify major feature changes to tempest. It's a monotonically
+increasing integer where each version either indicates a new supported OpenStack
+release, the drop of support for an OpenStack release (which will coincide with
+the upstream stable branch going EOL), or a major feature lands (or is removed)
+from tempest.
+
+Y.Z is used to represent library interface changes. This is treated the same
+way as minor and patch versions from `semver`_ but only for the library
+interface. When Y is incremented we've added functionality to the library
+interface and when Z is incremented it's a bug fix release for the library.
+Also note that both Y and Z are reset to 0 at each increment of X.
+
+.. _semver: http://semver.org/
+
Configuration
-------------
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index e428592..524c0fa 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -139,6 +139,11 @@
#. ``alt_password``
#. ``alt_tenant_name``
+If using Identity API v3, use the ``domain_name`` option to specify a
+domain other than the default domain. The ``auth_version`` setting is
+used to switch between v2 (``v2``) or v3 (``v3``) versions of the Identity
+API.
+
And in the ``auth`` section:
#. ``use_dynamic_credentials = False``
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 32e6e51..17def1c 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -11,6 +11,7 @@
HACKING
REVIEWING
plugin
+ library
------------
Field Guides
diff --git a/doc/source/library.rst b/doc/source/library.rst
new file mode 100644
index 0000000..8b263f2
--- /dev/null
+++ b/doc/source/library.rst
@@ -0,0 +1,68 @@
+.. _library:
+
+Tempest Library Documentation
+=============================
+
+Tempest provides a stable library interface that provides external tools or
+test suites an interface for reusing pieces of tempest code. Any public
+interface that lives in tempest/lib in the tempest repo is treated as a stable
+public interface and it should be safe to external consume that. Every effort
+goes into maintaining backwards compatibility with any change. Just as with
+tempest-lib the library is self contained and doesn't have any dependency on
+other tempest internals outside of lib. (including no usage of tempest
+configuration)
+
+Stability
+---------
+Just as tempest-lib before it any code that lives in tempest/lib will be treated
+as a stable interface, nothing has changed in regards to interface stability.
+This means that any public interface under the tempest/lib directory is
+expected to be a stable interface suitable for public consumption. However, for
+any interfaces outside of tempest/lib in the tempest tree (unless otherwise
+noted) or any private interfaces the same stability guarantees don't apply.
+
+Adding Interfaces
+'''''''''''''''''
+When adding an interface to tempest/lib we have to make sure there are no
+dependencies on any pieces of tempest outside of tempest/lib. This means if
+for example there is a dependency on the configuration file we need remove that.
+The other aspect when adding an interface is to make sure it's really an
+interface ready for external consumption and something we want to commit to
+supporting.
+
+Making changes
+''''''''''''''
+When making changes to tempest/lib you have to be conscious of the effect of
+any changes on external consumers. If your proposed changeset will change the
+default behaviour of any interface, or make something which previously worked
+not after your change, then it is not acceptable. Every effort needs to go into
+preserving backwards compatibility in changes.
+
+Reviewing
+'''''''''
+When reviewing a proposed change to tempest/lib code we need to be careful to
+ensure that we don't break backwards compatibility. For patches that change
+existing interfaces we have to be careful to make sure we don't break any
+external consumers. Some common red flags are:
+
+ * a change to an existing API requires a change outside the library directory
+ where the interface is being consumed
+ * a unit test has to be significantly changed to make the proposed change pass
+
+Testing
+'''''''
+When adding a new interface to the library we need to at a minimum have unit
+test coverage. A proposed change to add an interface to tempest/lib that
+doesn't have unit tests shouldn't be accepted. Ideally these unit tests will
+provide sufficient coverage to ensure a stable interface moving forward.
+
+Current Library APIs
+--------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ library/cli
+ library/decorators
+ library/rest_client
+ library/utils
diff --git a/doc/source/library/cli.rst b/doc/source/library/cli.rst
new file mode 100644
index 0000000..6bd7881
--- /dev/null
+++ b/doc/source/library/cli.rst
@@ -0,0 +1,18 @@
+.. _cli:
+
+CLI Testing Framework Usage
+===========================
+
+-------------------
+The cli.base module
+-------------------
+
+.. automodule:: tempest.lib.cli.base
+ :members:
+
+----------------------------
+The cli.output_parser module
+----------------------------
+
+.. automodule:: tempest.lib.cli.output_parser
+ :members:
diff --git a/doc/source/library/decorators.rst b/doc/source/library/decorators.rst
new file mode 100644
index 0000000..a173967
--- /dev/null
+++ b/doc/source/library/decorators.rst
@@ -0,0 +1,13 @@
+.. _decorators:
+
+Decorators Usage Guide
+======================
+
+---------------------
+The decorators module
+---------------------
+
+.. automodule:: tempest.lib.decorators
+ :members:
+
+
diff --git a/doc/source/library/rest_client.rst b/doc/source/library/rest_client.rst
new file mode 100644
index 0000000..3045694
--- /dev/null
+++ b/doc/source/library/rest_client.rst
@@ -0,0 +1,11 @@
+.. _rest_client:
+
+Rest Client Usage
+=================
+
+----------------------
+The rest_client module
+----------------------
+
+.. automodule:: tempest.lib.common.rest_client
+ :members:
diff --git a/doc/source/library/utils.rst b/doc/source/library/utils.rst
new file mode 100644
index 0000000..bc2f79b
--- /dev/null
+++ b/doc/source/library/utils.rst
@@ -0,0 +1,11 @@
+.. _utils:
+
+Utils Usage
+===========
+
+---------------
+The misc module
+---------------
+
+.. automodule:: tempest.lib.common.utils.misc
+ :members:
diff --git a/tempest/api/messaging/__init__.py b/releasenotes/notes/.placeholder
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to releasenotes/notes/.placeholder
diff --git a/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml b/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml
new file mode 100644
index 0000000..217c2f6
--- /dev/null
+++ b/releasenotes/notes/10.0-supported-openstack-releases-b88db468695348f6.yaml
@@ -0,0 +1,13 @@
+---
+other:
+ - OpenStack Releases Supported at this time are the same as in the
+ previous release 9,
+ **Kilo** and
+ **Liberty**.
+
+
+ The release under current development as of this tag is Mitaka,
+ meaning that every Tempest commit is also tested against master during
+ the Mitaka cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a Mitaka (or future releases)
+ cloud.
diff --git a/releasenotes/notes/Tempest-library-interface-0eb680b810139a50.yaml b/releasenotes/notes/Tempest-library-interface-0eb680b810139a50.yaml
new file mode 100644
index 0000000..0ed3130
--- /dev/null
+++ b/releasenotes/notes/Tempest-library-interface-0eb680b810139a50.yaml
@@ -0,0 +1,11 @@
+---
+prelude: |
+ This release includes the addition of the stable library interface for
+ tempest. This behaves just as tempest-lib did prior to this, but instead
+ it lives directly in the tempest project. For more information refer to
+ the `library docs`_.
+
+ .. _library docs: http://docs.openstack.org/developer/tempest/library.html#library
+
+features:
+ - Tempest library interface
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/tempest/api/messaging/__init__.py b/releasenotes/source/_static/.placeholder
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to releasenotes/source/_static/.placeholder
diff --git a/tempest/api/messaging/__init__.py b/releasenotes/source/_templates/.placeholder
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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..9dd57a9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.6 # Apache-2.0
-cliff!=1.16.0,>=1.15.0 # Apache-2.0
+cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
anyjson>=0.3.3 # BSD
httplib2>=0.7.5 # MIT
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
@@ -11,17 +11,17 @@
netaddr!=0.7.16,>=0.7.12 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
pyOpenSSL>=0.14 # Apache-2.0
-oslo.concurrency>=2.3.0 # Apache-2.0
-oslo.config>=3.4.0 # Apache-2.0
+oslo.concurrency>=3.5.0 # Apache-2.0
+oslo.config>=3.7.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
-oslo.utils>=3.4.0 # Apache-2.0
+oslo.utils>=3.5.0 # Apache-2.0
six>=1.9.0 # MIT
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 653a3cd..ead6db3 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
@@ -62,7 +63,7 @@
def _get_host_for_server(self, server_id):
return self._get_server_details(server_id)[self._host_key]
- def _migrate_server_to(self, server_id, dest_host, volume_backed):
+ def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
block_migration = (CONF.compute_feature_enabled.
block_migration_for_live_migration and
not volume_backed)
@@ -76,9 +77,6 @@
if host != target_host:
return target_host
- def _get_server_status(self, server_id):
- return self._get_server_details(server_id)['status']
-
def _volume_clean_up(self, server_id, volume_id):
body = self.volumes_client.show_volume(volume_id)['volume']
if body['status'] == 'in-use':
@@ -129,13 +127,11 @@
@test.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
- @testtools.skipUnless(CONF.compute_feature_enabled
- .live_migrate_paused_instances,
- 'Live migration of paused instances is not '
- 'available.')
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..e91944f 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
@@ -68,7 +67,7 @@
resp = {}
resp['status'] = None
self.assertRaises(lib_exc.NotFound, self.create_image_from_server,
- '!@$%^&*()', name=name, meta=meta)
+ '!@$^&*()', name=name, meta=meta)
@test.attr(type=['negative'])
@test.idempotent_id('ec176029-73dc-4037-8d72-2e4ff60cf538')
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 73e852f..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
@@ -31,7 +30,6 @@
def setup_clients(cls):
super(AbsoluteLimitsNegativeTestJSON, cls).setup_clients()
cls.client = cls.limits_client
- cls.server_client = cls.servers_client
@test.attr(type=['negative'])
@test.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
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 a6ccdd3..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
@@ -45,6 +44,7 @@
def setup_clients(cls):
super(AttachInterfacesTestJSON, cls).setup_clients()
cls.client = cls.os.interfaces_client
+ cls.ports_client = cls.os.ports_client
def wait_for_interface_status(self, server, port_id, status):
"""Waits for an interface to reach a given status."""
@@ -108,6 +108,18 @@
self._check_interface(iface, network_id=network_id)
return iface
+ def _test_create_interface_by_port_id(self, server, ifs):
+ network_id = ifs[0]['net_id']
+ port = self.ports_client.create_port(network_id=network_id)
+ port_id = port['port']['id']
+ self.addCleanup(self.ports_client.delete_port, port_id)
+ iface = self.client.create_interface(
+ server['id'], port_id=port_id)['interfaceAttachment']
+ iface = self.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface, port_id=port_id)
+ return iface
+
def _test_show_interface(self, server, ifs):
iface = ifs[0]
_iface = self.client.show_interface(
@@ -167,6 +179,9 @@
iface = self._test_create_interface_by_network_id(server, ifs)
ifs.append(iface)
+ iface = self._test_create_interface_by_port_id(server, ifs)
+ ifs.append(iface)
+
_ifs = (self.client.list_interfaces(server['id'])
['interfaceAttachments'])
self._compare_iface_list(ifs, _ifs)
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_group.py b/tempest/api/compute/servers/test_server_group.py
index c23b365..e32f6b0 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -13,8 +13,6 @@
# 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.utils import data_utils
from tempest import test
@@ -81,13 +79,6 @@
policy = ['anti-affinity']
self._create_delete_server_group(policy)
- @decorators.skip_because(bug="1324348")
- @test.idempotent_id('6d9bae05-eb32-425d-a673-e14e1b1c6306')
- def test_create_delete_server_group_with_multiple_policies(self):
- # Create and Delete the server-group with multiple policies
- policies = ['affinity', 'affinity']
- self._create_delete_server_group(policies)
-
@test.idempotent_id('154dc5a4-a2fe-44b5-b99e-f15806a4a113')
def test_create_delete_multiple_server_groups_with_same_name_policy(self):
# Create and Delete the server-groups with same name and same policy
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 b6d0c48..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
@@ -183,27 +183,6 @@
('5.3.0', copy.deepcopy(BASE_CDH_DESC)),
('5', copy.deepcopy(BASE_CDH_DESC))
]),
- 'mapr': OrderedDict([
- ('4.0.1.mrv2', {
- 'NODES': {
- 'master1': {
- 'count': 1,
- 'node_processes': ['CLDB', 'FileServer', 'ZooKeeper',
- 'NodeManager', 'ResourceManager',
- 'HistoryServer', 'Oozie']
- },
- 'worker1': {
- 'count': 1,
- 'node_processes': ['FileServer', 'NodeManager', 'Pig']
- }
- },
- 'cluster_configs': {
- 'Hive': {
- 'Hive Version': '0.13',
- }
- }
- })
- ]),
}
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_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 0e76d37..7c2e8e0 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -36,13 +36,13 @@
description=data_utils.rand_name('project-desc'))['project']
cls.projects.append(cls.project['id'])
- cls.user_body = cls.client.create_user(
+ cls.user_body = cls.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.projects[0])['user']
@classmethod
def resource_cleanup(cls):
- cls.client.delete_user(cls.user_body['id'])
+ cls.users_client.delete_user(cls.user_body['id'])
for p in cls.projects:
cls.projects_client.delete_project(p)
super(CredentialsTestJSON, cls).resource_cleanup()
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 661adb8..18a50d0 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)
@@ -56,11 +55,13 @@
# create a user in the domain, with the previous project as his
# default project
user_name = data_utils.rand_name('user')
- user_body = self.client.create_user(user_name, password=user_name,
- domain_id=dom_id,
- default_project_id=proj_id)['user']
+ user_body = self.users_client.create_user(
+ user_name,
+ password=user_name,
+ domain_id=dom_id,
+ default_project_id=proj_id)['user']
user_id = user_body['id']
- self.addCleanup(self.client.delete_user, user_id)
+ self.addCleanup(self.users_client.delete_user, user_id)
self.assertEqual(user_body['domain_id'], dom_id,
"user " + user_name +
"doesn't have domain id " + dom_id)
@@ -70,8 +71,8 @@
admin_role_id = admin_role['id']
# grant the admin role to the user on his project
- self.client.assign_user_role_on_project(proj_id, user_id,
- admin_role_id)
+ self.roles_client.assign_user_role_on_project(proj_id, user_id,
+ admin_role_id)
# create a new client with user's credentials (NOTE: unscoped token!)
creds = auth.KeystoneV3Credentials(username=user_name,
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_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 03b8b29..010e4a0 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -68,9 +68,9 @@
for i in range(3):
name = data_utils.rand_name('User')
password = data_utils.rand_password()
- user = self.client.create_user(name, password)['user']
+ user = self.users_client.create_user(name, password)['user']
users.append(user)
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list users in group
@@ -87,9 +87,9 @@
@test.idempotent_id('64573281-d26a-4a52-b899-503cb0f4e4ec')
def test_list_user_groups(self):
# create a user
- user = self.client.create_user(
+ user = self.users_client.create_user(
data_utils.rand_name('User'), data_utils.rand_password())['user']
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# create two groups, and add user into them
groups = []
for i in range(2):
@@ -99,7 +99,7 @@
self.addCleanup(self.groups_client.delete_group, group['id'])
self.groups_client.add_group_user(group['id'], user['id'])
# list groups which user belongs to
- user_groups = self.client.list_user_groups(user['id'])['groups']
+ user_groups = self.users_client.list_user_groups(user['id'])['groups']
self.assertEqual(sorted(groups), sorted(user_groups))
self.assertEqual(2, len(user_groups))
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
new file mode 100644
index 0000000..fe20349
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -0,0 +1,147 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class BaseInheritsV3Test(base.BaseIdentityV3AdminTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(BaseInheritsV3Test, cls).skip_checks()
+ if not test.is_extension_enabled('OS-INHERIT', 'identity'):
+ raise cls.skipException("Inherits aren't enabled")
+
+ @classmethod
+ def resource_setup(cls):
+ super(BaseInheritsV3Test, cls).resource_setup()
+ u_name = data_utils.rand_name('user-')
+ u_desc = '%s description' % u_name
+ u_email = '%s@testmail.tm' % u_name
+ u_password = data_utils.rand_name('pass-')
+ 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(
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'),
+ domain_id=cls.domain['id'])['project']
+ cls.group = cls.groups_client.create_group(
+ name=data_utils.rand_name('group-'), project_id=cls.project['id'],
+ domain_id=cls.domain['id'])['group']
+ cls.user = cls.users_client.create_user(
+ u_name, description=u_desc, password=u_password,
+ email=u_email, project_id=cls.project['id'],
+ domain_id=cls.domain['id'])['user']
+
+ @classmethod
+ def resource_cleanup(cls):
+ cls.groups_client.delete_group(cls.group['id'])
+ cls.users_client.delete_user(cls.user['id'])
+ cls.projects_client.delete_project(cls.project['id'])
+ cls.domains_client.update_domain(cls.domain['id'], enabled=False)
+ cls.domains_client.delete_domain(cls.domain['id'])
+ super(BaseInheritsV3Test, cls).resource_cleanup()
+
+ def _list_assertions(self, body, fetched_role_ids, role_id):
+ self.assertEqual(len(body), 1)
+ self.assertIn(role_id, fetched_role_ids)
+
+
+class InheritsV3TestJSON(BaseInheritsV3Test):
+
+ @test.idempotent_id('4e6f0366-97c8-423c-b2be-41eae6ac91c8')
+ def test_inherit_assign_list_check_revoke_roles_on_domains_user(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on domains user
+ self.roles_client.assign_inherited_role_on_domains_user(
+ self.domain['id'], self.user['id'], src_role['id'])
+ # list role on domains user
+ roles = self.roles_client.\
+ list_inherited_project_role_for_user_on_domain(
+ self.domain['id'], self.user['id'])['roles']
+
+ fetched_role_ids = [i['id'] for i in roles]
+ self._list_assertions(roles, fetched_role_ids,
+ src_role['id'])
+
+ # Check role on domains user
+ self.roles_client.check_user_inherited_project_role_on_domain(
+ self.domain['id'], self.user['id'], src_role['id'])
+ # Revoke role from domains user.
+ self.roles_client.revoke_inherited_role_from_user_on_domain(
+ self.domain['id'], self.user['id'], src_role['id'])
+
+ @test.idempotent_id('c7a8dda2-be50-4fb4-9a9c-e830771078b1')
+ def test_inherit_assign_list_check_revoke_roles_on_domains_group(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on domains group
+ self.roles_client.assign_inherited_role_on_domains_group(
+ self.domain['id'], self.group['id'], src_role['id'])
+ # List role on domains group
+ roles = self.roles_client.\
+ list_inherited_project_role_for_group_on_domain(
+ self.domain['id'], self.group['id'])['roles']
+
+ fetched_role_ids = [i['id'] for i in roles]
+ self._list_assertions(roles, fetched_role_ids,
+ src_role['id'])
+
+ # Check role on domains group
+ self.roles_client.check_group_inherited_project_role_on_domain(
+ self.domain['id'], self.group['id'], src_role['id'])
+ # Revoke role from domains group
+ self.roles_client.revoke_inherited_role_from_group_on_domain(
+ self.domain['id'], self.group['id'], src_role['id'])
+
+ @test.idempotent_id('18b70e45-7687-4b72-8277-b8f1a47d7591')
+ def test_inherit_assign_check_revoke_roles_on_projects_user(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on projects user
+ self.roles_client.assign_inherited_role_on_projects_user(
+ self.project['id'], self.user['id'], src_role['id'])
+ # Check role on projects user
+ self.roles_client.check_user_has_flag_on_inherited_to_project(
+ self.project['id'], self.user['id'], src_role['id'])
+ # Revoke role from projects user
+ self.roles_client.revoke_inherited_role_from_user_on_project(
+ self.project['id'], self.user['id'], src_role['id'])
+
+ @test.idempotent_id('26021436-d5a4-4256-943c-ded01e0d4b45')
+ def test_inherit_assign_check_revoke_roles_on_projects_group(self):
+ # Create role
+ src_role = self.roles_client.create_role(
+ name=data_utils.rand_name('Role'))['role']
+ self.addCleanup(self.roles_client.delete_role, src_role['id'])
+ # Assign role on projects group
+ self.roles_client.assign_inherited_role_on_projects_group(
+ self.project['id'], self.group['id'], src_role['id'])
+ # Check role on projects group
+ self.roles_client.check_group_has_flag_on_inherited_to_project(
+ self.project['id'], self.group['id'], src_role['id'])
+ # Revoke role from projects group
+ self.roles_client.revoke_inherited_role_from_group_on_project(
+ self.project['id'], self.group['id'], src_role['id'])
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 4921c00..5b27ab1 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -25,7 +25,7 @@
# assert the response based on expected and not_expected
# expected: user expected in the list response
# not_expected: user, which should not be present in list response
- body = self.client.list_users(params)['users']
+ body = self.users_client.list_users(params)['users']
self.assertIn(expected[key], map(lambda x: x[key], body))
self.assertNotIn(not_expected[key],
map(lambda x: x[key], body))
@@ -39,13 +39,13 @@
cls.data.setup_test_domain()
# Create user with Domain
u1_name = data_utils.rand_name('test_user')
- cls.domain_enabled_user = cls.client.create_user(
+ cls.domain_enabled_user = cls.users_client.create_user(
u1_name, password=alt_password,
email=cls.alt_email, domain_id=cls.data.domain['id'])['user']
cls.data.users.append(cls.domain_enabled_user)
# Create default not enabled user
u2_name = data_utils.rand_name('test_user')
- cls.non_domain_enabled_user = cls.client.create_user(
+ cls.non_domain_enabled_user = cls.users_client.create_user(
u2_name, password=alt_password,
email=cls.alt_email, enabled=False)['user']
cls.data.users.append(cls.non_domain_enabled_user)
@@ -77,7 +77,7 @@
@test.idempotent_id('b30d4651-a2ea-4666-8551-0c0e49692635')
def test_list_users(self):
# List users
- body = self.client.list_users()['users']
+ body = self.users_client.list_users()['users']
fetched_ids = [u['id'] for u in body]
missing_users = [u['id'] for u in self.data.users
if u['id'] not in fetched_ids]
@@ -88,7 +88,7 @@
@test.idempotent_id('b4baa3ae-ac00-4b4e-9e27-80deaad7771f')
def test_get_user(self):
# Get a user detail
- user = self.client.show_user(self.data.users[0]['id'])['user']
+ user = self.users_client.show_user(self.data.users[0]['id'])['user']
self.assertEqual(self.data.users[0]['id'], user['id'])
self.assertEqual(self.data.users[0]['name'], user['name'])
self.assertEqual(self.alt_email, user['email'])
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 92f5a40..607bebe 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -163,14 +163,14 @@
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=project['id'])['user']
# Delete the User at the end of this method
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Get User To validate the user details
- new_user_get = self.client.show_user(user['id'])['user']
+ new_user_get = self.users_client.show_user(user['id'])['user']
# Assert response body of GET
self.assertEqual(u_name, new_user_get['name'])
self.assertEqual(u_desc, new_user_get['description'])
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 f3cdd96..12ef369 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,14 +25,14 @@
super(RolesV3TestJSON, cls).resource_setup()
for _ in range(3):
role_name = data_utils.rand_name(name='role')
- role = cls.client.create_role(name=role_name)['role']
+ role = cls.roles_client.create_role(name=role_name)['role']
cls.data.roles.append(role)
cls.fetched_role_ids = list()
u_name = data_utils.rand_name('user')
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(
@@ -42,23 +42,23 @@
cls.group_body = cls.groups_client.create_group(
name=data_utils.rand_name('Group'), project_id=cls.project['id'],
domain_id=cls.domain['id'])['group']
- cls.user_body = cls.client.create_user(
+ cls.user_body = cls.users_client.create_user(
u_name, description=u_desc, password=cls.u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])['user']
- cls.role = cls.client.create_role(
+ cls.role = cls.roles_client.create_role(
name=data_utils.rand_name('Role'))['role']
@classmethod
def resource_cleanup(cls):
- cls.client.delete_role(cls.role['id'])
+ cls.roles_client.delete_role(cls.role['id'])
cls.groups_client.delete_group(cls.group_body['id'])
- cls.client.delete_user(cls.user_body['id'])
+ cls.users_client.delete_user(cls.user_body['id'])
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):
@@ -67,34 +67,35 @@
@test.attr(type='smoke')
@test.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
- def test_role_create_update_get_list(self):
+ def test_role_create_update_show_list(self):
r_name = data_utils.rand_name('Role')
- role = self.client.create_role(name=r_name)['role']
- self.addCleanup(self.client.delete_role, role['id'])
+ role = self.roles_client.create_role(name=r_name)['role']
+ self.addCleanup(self.roles_client.delete_role, role['id'])
self.assertIn('name', role)
self.assertEqual(role['name'], r_name)
new_name = data_utils.rand_name('NewRole')
- updated_role = self.client.update_role(role['id'],
- name=new_name)['role']
+ updated_role = self.roles_client.update_role(role['id'],
+ name=new_name)['role']
self.assertIn('name', updated_role)
self.assertIn('id', updated_role)
self.assertIn('links', updated_role)
self.assertNotEqual(r_name, updated_role['name'])
- new_role = self.client.show_role(role['id'])['role']
+ new_role = self.roles_client.show_role(role['id'])['role']
self.assertEqual(new_name, new_role['name'])
self.assertEqual(updated_role['id'], new_role['id'])
- roles = self.client.list_roles()['roles']
+ roles = self.roles_client.list_roles()['roles']
self.assertIn(role['id'], [r['id'] for r in roles])
@test.idempotent_id('c6b80012-fe4a-498b-9ce8-eb391c05169f')
def test_grant_list_revoke_role_to_user_on_project(self):
- self.client.assign_user_role_on_project(
- self.project['id'], self.user_body['id'], self.role['id'])
+ self.roles_client.assign_user_role_on_project(self.project['id'],
+ self.user_body['id'],
+ self.role['id'])
- roles = self.client.list_user_roles_on_project(
+ roles = self.roles_client.list_user_roles_on_project(
self.project['id'], self.user_body['id'])['roles']
for i in roles:
@@ -103,18 +104,18 @@
self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- self.client.check_user_role_existence_on_project(
+ self.roles_client.check_user_role_existence_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
- self.client.delete_role_from_user_on_project(
+ self.roles_client.delete_role_from_user_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
@test.idempotent_id('6c9a2940-3625-43a3-ac02-5dcec62ef3bd')
def test_grant_list_revoke_role_to_user_on_domain(self):
- self.client.assign_user_role_on_domain(
+ self.roles_client.assign_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
- roles = self.client.list_user_roles_on_domain(
+ roles = self.roles_client.list_user_roles_on_domain(
self.domain['id'], self.user_body['id'])['roles']
for i in roles:
@@ -123,19 +124,19 @@
self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- self.client.check_user_role_existence_on_domain(
+ self.roles_client.check_user_role_existence_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
- self.client.delete_role_from_user_on_domain(
+ self.roles_client.delete_role_from_user_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
@test.idempotent_id('cbf11737-1904-4690-9613-97bcbb3df1c4')
def test_grant_list_revoke_role_to_group_on_project(self):
# Grant role to group on project
- self.client.assign_group_role_on_project(
+ self.roles_client.assign_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
# List group roles on project
- roles = self.client.list_group_roles_on_project(
+ roles = self.roles_client.list_group_roles_on_project(
self.project['id'], self.group_body['id'])['roles']
for i in roles:
@@ -157,19 +158,19 @@
self.assertEqual(len(roles), 1)
self.assertEqual(roles[0]['id'], self.role['id'])
- self.client.check_role_from_group_on_project_existence(
+ self.roles_client.check_role_from_group_on_project_existence(
self.project['id'], self.group_body['id'], self.role['id'])
# Revoke role to group on project
- self.client.delete_role_from_group_on_project(
+ self.roles_client.delete_role_from_group_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
@test.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7')
def test_grant_list_revoke_role_to_group_on_domain(self):
- self.client.assign_group_role_on_domain(
+ self.roles_client.assign_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
- roles = self.client.list_group_roles_on_domain(
+ roles = self.roles_client.list_group_roles_on_domain(
self.domain['id'], self.group_body['id'])['roles']
for i in roles:
@@ -178,15 +179,15 @@
self._list_assertions(roles, self.fetched_role_ids,
self.role['id'])
- self.client.check_role_from_group_on_domain_existence(
+ self.roles_client.check_role_from_group_on_domain_existence(
self.domain['id'], self.group_body['id'], self.role['id'])
- self.client.delete_role_from_group_on_domain(
+ self.roles_client.delete_role_from_group_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
@test.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94')
def test_list_roles(self):
# Return a list of all roles
- body = self.client.list_roles()['roles']
+ body = self.roles_client.list_roles()['roles']
found = [role for role in body if role in self.data.roles]
self.assertEqual(len(found), len(self.data.roles))
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 3452d95..6f12939 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
@@ -30,10 +29,10 @@
u_desc = '%s-description' % u_name
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email)['user']
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Perform Authentication
resp = self.token.auth(user_id=user['id'],
password=u_password).response
@@ -61,9 +60,9 @@
# Create a user.
user_name = data_utils.rand_name(name='user')
user_password = data_utils.rand_password()
- user = self.client.create_user(user_name,
- password=user_password)['user']
- self.addCleanup(self.client.delete_user, user['id'])
+ user = self.users_client.create_user(user_name,
+ password=user_password)['user']
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Create a couple projects
project1_name = data_utils.rand_name(name='project')
@@ -78,15 +77,17 @@
# Create a role
role_name = data_utils.rand_name(name='role')
- role = self.client.create_role(name=role_name)['role']
- self.addCleanup(self.client.delete_role, role['id'])
+ role = self.roles_client.create_role(name=role_name)['role']
+ self.addCleanup(self.roles_client.delete_role, role['id'])
# Grant the user the role on both projects.
- self.client.assign_user_role(project1['id'], user['id'],
- role['id'])
+ self.roles_client.assign_user_role_on_project(project1['id'],
+ user['id'],
+ role['id'])
- self.client.assign_user_role(project2['id'], user['id'],
- role['id'])
+ self.roles_client.assign_user_role_on_project(project2['id'],
+ user['id'],
+ role['id'])
# Get an unscoped token.
token_auth = self.token.auth(user_id=user['id'],
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 5f44b8e..09ae468 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
@@ -56,7 +56,7 @@
u_desc = self.trustor_username + 'description'
u_email = self.trustor_username + '@testmail.xx'
self.trustor_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
self.trustor_username,
description=u_desc,
password=self.trustor_password,
@@ -69,19 +69,22 @@
self.delegated_role = data_utils.rand_name('DelegatedRole')
self.not_delegated_role = data_utils.rand_name('NotDelegatedRole')
- role = self.client.create_role(name=self.delegated_role)['role']
+ role = self.roles_client.create_role(name=self.delegated_role)['role']
self.delegated_role_id = role['id']
- role = self.client.create_role(name=self.not_delegated_role)['role']
+ role = self.roles_client.create_role(
+ name=self.not_delegated_role)['role']
self.not_delegated_role_id = role['id']
# Assign roles to trustor
- self.client.assign_user_role(self.trustor_project_id,
- self.trustor_user_id,
- self.delegated_role_id)
- self.client.assign_user_role(self.trustor_project_id,
- self.trustor_user_id,
- self.not_delegated_role_id)
+ self.roles_client.assign_user_role_on_project(
+ self.trustor_project_id,
+ self.trustor_user_id,
+ self.delegated_role_id)
+ self.roles_client.assign_user_role_on_project(
+ self.trustor_project_id,
+ self.trustor_user_id,
+ self.not_delegated_role_id)
# Get trustee user ID, use the demo user
trustee_username = self.non_admin_client.user
@@ -97,17 +100,17 @@
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:
- self.client.delete_user(self.trustor_user_id)
+ self.users_client.delete_user(self.trustor_user_id)
if self.trustor_project_id:
self.projects_client.delete_project(self.trustor_project_id)
if self.delegated_role_id:
- self.client.delete_role(self.delegated_role_id)
+ self.roles_client.delete_role(self.delegated_role_id)
if self.not_delegated_role_id:
- self.client.delete_role(self.not_delegated_role_id)
+ self.roles_client.delete_role(self.not_delegated_role_id)
def create_trust(self, impersonate=True, expires=None):
@@ -264,7 +267,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.py b/tempest/api/identity/admin/v3/test_users.py
index 75c8ca5..e26624a 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -30,11 +30,11 @@
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False)['user']
# Delete the User at the end of this method
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Creating second project for updation
project = self.projects_client.create_project(
data_utils.rand_name('project'),
@@ -45,7 +45,7 @@
u_name2 = data_utils.rand_name('user2')
u_email2 = u_name2 + '@testmail.tm'
u_description2 = u_name2 + ' description'
- update_user = self.client.update_user(
+ update_user = self.users_client.update_user(
user['id'], name=u_name2, description=u_description2,
project_id=project['id'],
email=u_email2, enabled=False)['user']
@@ -56,7 +56,7 @@
self.assertEqual(u_email2, update_user['email'])
self.assertEqual(False, update_user['enabled'])
# GET by id after updation
- new_user_get = self.client.show_user(user['id'])['user']
+ new_user_get = self.users_client.show_user(user['id'])['user']
# Assert response body of GET after updation
self.assertEqual(u_name2, new_user_get['name'])
self.assertEqual(u_description2, new_user_get['description'])
@@ -70,14 +70,15 @@
# Creating User to check password updation
u_name = data_utils.rand_name('user')
original_password = data_utils.rand_password()
- user = self.client.create_user(
+ user = self.users_client.create_user(
u_name, password=original_password)['user']
# Delete the User at the end all test methods
- self.addCleanup(self.client.delete_user, user['id'])
+ self.addCleanup(self.users_client.delete_user, user['id'])
# Update user with new password
new_password = data_utils.rand_password()
- self.client.update_user_password(user['id'], password=new_password,
- original_password=original_password)
+ self.users_client.update_user_password(
+ user['id'], password=new_password,
+ original_password=original_password)
# TODO(lbragstad): Sleeping after the response status has been checked
# and the body loaded as JSON allows requests to fail-fast. The sleep
# is necessary because keystone will err on the side of security and
@@ -110,19 +111,19 @@
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- user_body = self.client.create_user(
+ user_body = self.users_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False, project_id=u_project['id'])['user']
# Delete the User at the end of this method
- self.addCleanup(self.client.delete_user, user_body['id'])
+ self.addCleanup(self.users_client.delete_user, user_body['id'])
# Creating Role
- role_body = self.client.create_role(
+ role_body = self.roles_client.create_role(
name=data_utils.rand_name('role'))['role']
# Delete the Role at the end of this method
- self.addCleanup(self.client.delete_role, role_body['id'])
+ self.addCleanup(self.roles_client.delete_role, role_body['id'])
- user = self.client.show_user(user_body['id'])['user']
- role = self.client.show_role(role_body['id'])['role']
+ user = self.users_client.show_user(user_body['id'])['user']
+ role = self.roles_client.show_role(role_body['id'])['role']
for i in range(2):
# Creating project so as to assign role
project_body = self.projects_client.create_project(
@@ -134,11 +135,11 @@
self.addCleanup(
self.projects_client.delete_project, project_body['id'])
# Assigning roles to user on project
- self.client.assign_user_role(project['id'],
- user['id'],
- role['id'])
+ self.roles_client.assign_user_role_on_project(project['id'],
+ user['id'],
+ role['id'])
assigned_project_ids.append(project['id'])
- body = self.client.list_user_projects(user['id'])['projects']
+ body = self.users_client.list_user_projects(user['id'])['projects']
for i in body:
fetched_project_ids.append(i['id'])
# verifying the project ids in list
@@ -154,5 +155,5 @@
def test_get_user(self):
# Get a user detail
self.data.setup_test_user()
- user = self.client.show_user(self.data.user['id'])['user']
+ user = self.users_client.show_user(self.data.user['id'])['user']
self.assertEqual(self.data.user['id'], user['id'])
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index 39c89a5..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
@@ -29,7 +28,7 @@
u_name = data_utils.rand_name('user')
u_email = u_name + '@testmail.tm'
u_password = data_utils.rand_password()
- self.assertRaises(lib_exc.NotFound, self.client.create_user,
+ self.assertRaises(lib_exc.NotFound, self.users_client.create_user,
u_name, u_password,
email=u_email,
domain_id=data_utils.rand_uuid_hex())
@@ -39,7 +38,7 @@
def test_authentication_for_disabled_user(self):
# Attempt to authenticate for disabled user should fail
self.data.setup_test_user()
- self.disable_user(self.data.user['name'])
+ self.disable_user(self.data.user['name'], self.data.user['domain_id'])
self.assertRaises(lib_exc.Unauthorized, self.token.auth,
username=self.data.user['name'],
password=self.data.user_password,
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 455a2c6..3bcae17 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
@@ -37,8 +37,12 @@
cls.tenants_client.update_tenant(tenant['id'], enabled=False)
@classmethod
- def get_user_by_name(cls, name):
- users = cls.users_client.list_users()['users']
+ def get_user_by_name(cls, name, domain_id=None):
+ if domain_id:
+ params = {'domain_id': domain_id}
+ users = cls.users_client.list_users(params)['users']
+ else:
+ users = cls.users_client.list_users()['users']
user = [u for u in users if u['name'] == name]
if len(user) > 0:
return user[0]
@@ -102,14 +106,14 @@
cls.non_admin_roles_client = cls.os.roles_client
cls.users_client = cls.os_adm.users_client
cls.non_admin_users_client = cls.os.users_client
- cls.services_client = cls.os_adm.services_v2_client
- cls.endpoints_client = cls.os_adm.endpoints_v2_client
+ cls.services_client = cls.os_adm.identity_services_client
+ cls.endpoints_client = cls.os_adm.endpoints_client
@classmethod
def resource_setup(cls):
super(BaseIdentityV2AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV2(cls.client, cls.tenants_client,
- cls.users_client, cls.roles_client)
+ cls.data = DataGeneratorV2(cls.tenants_client, cls.users_client,
+ cls.roles_client)
@classmethod
def resource_cleanup(cls):
@@ -129,6 +133,7 @@
def setup_clients(cls):
super(BaseIdentityV3Test, cls).setup_clients()
cls.non_admin_client = cls.os.identity_v3_client
+ cls.non_admin_users_client = cls.os.users_v3_client
cls.non_admin_token = cls.os.token_v3_client
cls.non_admin_projects_client = cls.os.projects_client
@@ -145,10 +150,14 @@
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.roles_client = cls.os_adm.roles_v3_client
cls.token = cls.os_adm.token_v3_client
- cls.endpoints_client = cls.os_adm.endpoints_client
+ cls.endpoints_client = cls.os_adm.endpoints_v3_client
cls.regions_client = cls.os_adm.regions_client
- cls.services_client = cls.os_adm.identity_services_client
+ cls.services_client = cls.os_adm.identity_services_v3_client
cls.policies_client = cls.os_adm.policies_client
cls.creds_client = cls.os_adm.credentials_client
cls.groups_client = cls.os_adm.groups_client
@@ -157,7 +166,8 @@
@classmethod
def resource_setup(cls):
super(BaseIdentityV3AdminTest, cls).resource_setup()
- cls.data = DataGeneratorV3(cls.client, cls.projects_client)
+ cls.data = DataGeneratorV3(cls.projects_client, cls.users_client,
+ cls.roles_client, cls.domains_client)
@classmethod
def resource_cleanup(cls):
@@ -165,39 +175,25 @@
super(BaseIdentityV3AdminTest, cls).resource_cleanup()
@classmethod
- def get_user_by_name(cls, name):
- users = cls.client.list_users()['users']
- user = [u for u in users if u['name'] == name]
- if len(user) > 0:
- return user[0]
-
- @classmethod
- def get_role_by_name(cls, name):
- roles = cls.client.list_roles()['roles']
- role = [r for r in roles if r['name'] == name]
- if len(role) > 0:
- return role[0]
-
- @classmethod
- def disable_user(cls, user_name):
- user = cls.get_user_by_name(user_name)
- cls.client.update_user(user['id'], user_name, enabled=False)
+ def disable_user(cls, user_name, domain_id=None):
+ user = cls.get_user_by_name(user_name, domain_id)
+ cls.users_client.update_user(user['id'], user_name, enabled=False)
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=None, roles_client=None):
- self.client = client
+ def __init__(self, projects_client, users_client, roles_client,
+ domains_client=None):
self.projects_client = projects_client
- self.users_client = users_client or client
- self.roles_client = roles_client or client
+ self.users_client = users_client
+ self.roles_client = roles_client
+ self.domains_client = domains_client
self.user_password = None
self.user = None
@@ -246,8 +242,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 +278,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 a547b06..995b77e 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
@@ -27,17 +26,21 @@
def test_list_projects_returns_only_authorized_projects(self):
alt_project_name =\
self.alt_manager.credentials.credentials.project_name
- resp = self.non_admin_client.list_user_projects(
+ resp = self.non_admin_users_client.list_user_projects(
self.os.credentials.user_id)
# check that user can see only that projects that he presents in so
# user can successfully authenticate using his credentials and
# project name from received projects list
for project in resp['projects']:
+ # 'user_domain_id' needs to be specified otherwise tempest_lib
+ # assumes it to be 'default'
token_id, body = self.non_admin_token.get_token(
username=self.os.credentials.username,
+ user_domain_id=self.os.credentials.user_domain_id,
password=self.os.credentials.password,
project_name=project['name'],
+ project_domain_id=project['domain_id'],
auth_data=True)
self.assertNotEmpty(token_id)
self.assertEqual(body['project']['id'], project['id'])
@@ -49,5 +52,7 @@
lib_exc.Unauthorized,
self.non_admin_token.get_token,
username=self.os.credentials.username,
+ user_domain_id=self.os.credentials.user_domain_id,
password=self.os.credentials.password,
- project_name=alt_project_name)
+ project_name=alt_project_name,
+ project_domain_id=project['domain_id'])
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index 3151763..593bf2a 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -28,10 +28,15 @@
user_id = creds.user_id
username = creds.username
password = creds.password
+ user_domain_id = creds.user_domain_id
- token_id, resp = self.non_admin_token.get_token(user_id=user_id,
- password=password,
- auth_data=True)
+ # 'user_domain_id' needs to be specified otherwise tempest_lib assumes
+ # it to be 'default'
+ token_id, resp = self.non_admin_token.get_token(
+ user_id=user_id,
+ user_domain_id=user_domain_id,
+ password=password,
+ auth_data=True)
self.assertNotEmpty(token_id)
self.assertIsInstance(token_id, six.string_types)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 93814d3..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
@@ -39,23 +38,24 @@
self.new_creds = copy.copy(self.creds.credentials)
self.new_creds.password = data_utils.rand_password()
# we need new non-admin Identity V3 Client with new credentials, since
- # current non_admin_client token will be revoked after updating
+ # current non_admin_users_client token will be revoked after updating
# password
- self.non_admin_client_for_cleanup = copy.copy(self.non_admin_client)
- self.non_admin_client_for_cleanup.auth_provider = (
+ self.non_admin_users_client_for_cleanup = (
+ copy.copy(self.non_admin_users_client))
+ self.non_admin_users_client_for_cleanup.auth_provider = (
manager.get_auth_provider(self.new_creds))
user_id = self.creds.credentials.user_id
old_pass = self.creds.credentials.password
new_pass = self.new_creds.password
# to change password back. important for allow_tenant_isolation = false
self.addCleanup(
- self.non_admin_client_for_cleanup.update_user_password,
+ self.non_admin_users_client_for_cleanup.update_user_password,
user_id,
password=old_pass,
original_password=new_pass)
# user updates own password
- self.non_admin_client.update_user_password(
+ self.non_admin_users_client.update_user_password(
user_id, password=new_pass, original_password=old_pass)
# TODO(lbragstad): Sleeping after the response status has been checked
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/messaging/base.py b/tempest/api/messaging/base.py
deleted file mode 100644
index a324c37..0000000
--- a/tempest/api/messaging/base.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 tempest.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-CONF = config.CONF
-
-
-class BaseMessagingTest(test.BaseTestCase):
-
- """Base class for the Messaging (Zaqar) tests
-
- It is assumed that the following option is defined in the
- [service_available] section of etc/tempest.conf
-
- messaging as True
- """
-
- credentials = ['primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseMessagingTest, cls).skip_checks()
- if not CONF.service_available.zaqar:
- raise cls.skipException("Zaqar support is required")
-
- @classmethod
- def setup_clients(cls):
- super(BaseMessagingTest, cls).setup_clients()
- cls.client = cls.os.messaging_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseMessagingTest, cls).resource_setup()
- cls.messaging_cfg = CONF.messaging
-
- @classmethod
- def create_queue(cls, queue_name):
- """Wrapper utility that returns a test queue."""
- resp, body = cls.client.create_queue(queue_name)
- return resp, body
-
- @classmethod
- def delete_queue(cls, queue_name):
- """Wrapper utility that deletes a test queue."""
- resp, body = cls.client.delete_queue(queue_name)
- return resp, body
-
- @classmethod
- def check_queue_exists(cls, queue_name):
- """Wrapper utility that checks the existence of a test queue."""
- resp, body = cls.client.show_queue(queue_name)
- return resp, body
-
- @classmethod
- def check_queue_exists_head(cls, queue_name):
- """Wrapper utility checks the head of a queue via http HEAD."""
- resp, body = cls.client.head_queue(queue_name)
- return resp, body
-
- @classmethod
- def list_queues(cls):
- """Wrapper utility that lists queues."""
- resp, body = cls.client.list_queues()
- return resp, body
-
- @classmethod
- def get_queue_stats(cls, queue_name):
- """Wrapper utility that returns the queue stats."""
- resp, body = cls.client.show_queue_stats(queue_name)
- return resp, body
-
- @classmethod
- def get_queue_metadata(cls, queue_name):
- """Wrapper utility that gets a queue metadata."""
- resp, body = cls.client.show_queue_metadata(queue_name)
- return resp, body
-
- @classmethod
- def set_queue_metadata(cls, queue_name, rbody):
- """Wrapper utility that sets the metadata of a queue."""
- resp, body = cls.client.set_queue_metadata(queue_name, rbody)
- return resp, body
-
- @classmethod
- def post_messages(cls, queue_name, rbody):
- """Wrapper utility that posts messages to a queue."""
- resp, body = cls.client.post_messages(queue_name, rbody)
-
- return resp, body
-
- @classmethod
- def list_messages(cls, queue_name):
- """Wrapper utility that lists the messages in a queue."""
- resp, body = cls.client.list_messages(queue_name)
-
- return resp, body
-
- @classmethod
- def delete_messages(cls, message_uri):
- """Wrapper utility that deletes messages."""
- resp, body = cls.client.delete_messages(message_uri)
-
- return resp, body
-
- @classmethod
- def post_claims(cls, queue_name, rbody, url_params=False):
- """Wrapper utility that claims messages."""
- resp, body = cls.client.post_claims(
- queue_name, rbody, url_params=False)
-
- return resp, body
-
- @classmethod
- def query_claim(cls, claim_uri):
- """Wrapper utility that gets a claim."""
- resp, body = cls.client.query_claim(claim_uri)
-
- return resp, body
-
- @classmethod
- def update_claim(cls, claim_uri, rbody):
- """Wrapper utility that updates a claim."""
- resp, body = cls.client.update_claim(claim_uri, rbody)
-
- return resp, body
-
- @classmethod
- def release_claim(cls, claim_uri):
- """Wrapper utility that deletes a claim."""
- resp, body = cls.client.delete_claim(claim_uri)
-
- return resp, body
-
- @classmethod
- def generate_message_body(cls, repeat=1):
- """Wrapper utility that sets the metadata of a queue."""
- message_ttl = data_utils.\
- rand_int_id(start=60, end=CONF.messaging.max_message_ttl)
-
- key = data_utils.arbitrary_string(size=20, base_text='MessagingKey')
- value = data_utils.arbitrary_string(size=20,
- base_text='MessagingValue')
- message_body = {key: value}
-
- rbody = ([{'body': message_body, 'ttl': message_ttl}] * repeat)
- return rbody
diff --git a/tempest/api/messaging/test_claims.py b/tempest/api/messaging/test_claims.py
deleted file mode 100644
index 99edde1..0000000
--- a/tempest/api/messaging/test_claims.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 six.moves.urllib import parse as urlparse
-from tempest_lib import decorators
-
-from tempest.api.messaging import base
-from tempest.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-
-CONF = config.CONF
-
-
-class TestClaims(base.BaseMessagingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TestClaims, cls).resource_setup()
- cls.queue_name = data_utils.rand_name('Queues-Test')
- # Create Queue
- cls.create_queue(cls.queue_name)
-
- def _post_and_claim_messages(self, queue_name, repeat=1):
- # Post Messages
- message_body = self.generate_message_body(repeat=repeat)
- self.client.post_messages(queue_name=self.queue_name,
- rbody=message_body)
-
- # Post Claim
- claim_ttl = data_utils.rand_int_id(start=60,
- end=CONF.messaging.max_claim_ttl)
- claim_grace = data_utils.\
- rand_int_id(start=60, end=CONF.messaging.max_claim_grace)
- claim_body = {"ttl": claim_ttl, "grace": claim_grace}
- resp, body = self.client.post_claims(queue_name=self.queue_name,
- rbody=claim_body)
-
- return resp, body
-
- @test.attr(type='smoke')
- @test.idempotent_id('936cb1ca-b7af-44dd-a752-805e8c98156f')
- def test_post_claim(self):
- _, body = self._post_and_claim_messages(queue_name=self.queue_name)
- claimed_message_uri = body[0]['href']
-
- # Skipping this step till bug-1331517 is fixed
- # Get posted claim
- # self.client.query_claim(claimed_message_uri)
-
- # Delete Claimed message
- self.client.delete_messages(claimed_message_uri)
-
- @decorators.skip_because(bug="1331517")
- @test.attr(type='smoke')
- @test.idempotent_id('84e491f4-68c6-451f-9846-b8f868eb27c5')
- def test_query_claim(self):
- # Post a Claim
- resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
-
- # Query Claim
- claim_uri = resp['location']
- self.client.query_claim(claim_uri)
-
- # Delete Claimed message
- claimed_message_uri = body[0]['href']
- self.delete_messages(claimed_message_uri)
-
- @decorators.skip_because(bug="1328111")
- @test.attr(type='smoke')
- @test.idempotent_id('420ef0c5-9bd6-4b82-b06d-d9da330fefd3')
- def test_update_claim(self):
- # Post a Claim
- resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
-
- claim_uri = resp['location']
- claimed_message_uri = body[0]['href']
-
- # Update Claim
- claim_ttl = data_utils.rand_int_id(start=60,
- end=CONF.messaging.max_claim_ttl)
- update_rbody = {"ttl": claim_ttl}
-
- self.client.update_claim(claim_uri, rbody=update_rbody)
-
- # Verify claim ttl >= updated ttl value
- _, body = self.client.query_claim(claim_uri)
- updated_claim_ttl = body["ttl"]
- self.assertTrue(updated_claim_ttl >= claim_ttl)
-
- # Delete Claimed message
- self.client.delete_messages(claimed_message_uri)
-
- @test.attr(type='smoke')
- @test.idempotent_id('fd4c7921-cb3f-4ed8-9ac8-e8f1e74c44aa')
- def test_release_claim(self):
- # Post a Claim
- resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
- claim_uri = resp['location']
-
- # Release Claim
- self.client.delete_claim(claim_uri)
-
- # Delete Claimed message
- # This will implicitly verify that the claim is deleted.
- message_uri = urlparse.urlparse(claim_uri).path
- self.client.delete_messages(message_uri)
-
- @classmethod
- def resource_cleanup(cls):
- cls.delete_queue(cls.queue_name)
- super(TestClaims, cls).resource_cleanup()
diff --git a/tempest/api/messaging/test_messages.py b/tempest/api/messaging/test_messages.py
deleted file mode 100644
index 7f4182a..0000000
--- a/tempest/api/messaging/test_messages.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 tempest.api.messaging import base
-from tempest.common.utils import data_utils
-from tempest import config
-from tempest import test
-
-
-CONF = config.CONF
-
-
-class TestMessages(base.BaseMessagingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TestMessages, cls).resource_setup()
- cls.queue_name = data_utils.rand_name('Queues-Test')
- # Create Queue
- cls.client.create_queue(cls.queue_name)
-
- def _post_messages(self, repeat=CONF.messaging.max_messages_per_page):
- message_body = self.generate_message_body(repeat=repeat)
- resp, body = self.post_messages(queue_name=self.queue_name,
- rbody=message_body)
- return resp, body
-
- @test.attr(type='smoke')
- @test.idempotent_id('93867172-a414-4eb3-a639-96e943c516b4')
- def test_post_messages(self):
- # Post Messages
- resp, _ = self._post_messages()
-
- # Get on the posted messages
- message_uri = resp['location']
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('c967d59a-e919-41cb-994b-1c4300452c80')
- def test_list_messages(self):
- # Post Messages
- self._post_messages()
-
- # List Messages
- resp, _ = self.list_messages(queue_name=self.queue_name)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('2a68e3de-24df-47c3-9039-ec4156656bf8')
- def test_get_message(self):
- # Post Messages
- _, body = self._post_messages()
- message_uri = body['resources'][0]
-
- # Get posted message
- resp, _ = self.client.show_single_message(message_uri)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('c4b0a30b-efda-4b87-a395-0c43140df74d')
- def test_get_multiple_messages(self):
- # Post Messages
- resp, _ = self._post_messages()
- message_uri = resp['location']
-
- # Get posted messages
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response cannot be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('200', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('fc0fca47-dd8b-4ecc-8522-d9c191f9bc9f')
- def test_delete_single_message(self):
- # Post Messages
- _, body = self._post_messages()
- message_uri = body['resources'][0]
-
- # Delete posted message & verify the delete operration
- self.client.delete_messages(message_uri)
-
- message_uri = message_uri.replace('/messages/', '/messages?ids=')
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response has to be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('204', resp['status'])
-
- @test.attr(type='smoke')
- @test.idempotent_id('00cca069-5c8f-4b42-bff1-c577da2a4546')
- def test_delete_multiple_messages(self):
- # Post Messages
- resp, _ = self._post_messages()
- message_uri = resp['location']
-
- # Delete multiple messages
- self.client.delete_messages(message_uri)
- resp, _ = self.client.show_multiple_messages(message_uri)
- # The test has an assertion here, because the response has to be 204
- # in this case (the client allows 200 or 204 for this API call).
- self.assertEqual('204', resp['status'])
-
- @classmethod
- def resource_cleanup(cls):
- cls.delete_queue(cls.queue_name)
- super(TestMessages, cls).resource_cleanup()
diff --git a/tempest/api/messaging/test_queues.py b/tempest/api/messaging/test_queues.py
deleted file mode 100644
index dcb5450..0000000
--- a/tempest/api/messaging/test_queues.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 six import moves
-from tempest_lib import exceptions as lib_exc
-from testtools import matchers
-
-from tempest.api.messaging import base
-from tempest.common.utils import data_utils
-from tempest import test
-
-
-class TestQueues(base.BaseMessagingTest):
-
- @test.attr(type='smoke')
- @test.idempotent_id('9f1c4c72-80c5-4dac-acf3-188cef42e36c')
- def test_create_delete_queue(self):
- # Create & Delete Queue
- queue_name = data_utils.rand_name('test')
- _, body = self.create_queue(queue_name)
-
- self.addCleanup(self.client.delete_queue, queue_name)
- # NOTE(gmann): create_queue returns response status code as 201
- # so specifically checking the expected empty response body as
- # this is not going to be checked in response_checker().
- self.assertEqual('', body)
-
- self.delete_queue(queue_name)
- self.assertRaises(lib_exc.NotFound,
- self.client.show_queue,
- queue_name)
-
-
-class TestManageQueue(base.BaseMessagingTest):
-
- @classmethod
- def resource_setup(cls):
- super(TestManageQueue, cls).resource_setup()
- cls.queues = list()
- for _ in moves.xrange(5):
- queue_name = data_utils.rand_name('Queues-Test')
- cls.queues.append(queue_name)
- # Create Queue
- cls.client.create_queue(queue_name)
-
- @test.attr(type='smoke')
- @test.idempotent_id('ccd3d69e-f156-4c5f-8a12-b4f24bee44e1')
- def test_check_queue_existence(self):
- # Checking Queue Existence
- for queue_name in self.queues:
- self.check_queue_exists(queue_name)
-
- @test.attr(type='smoke')
- @test.idempotent_id('e27634d8-9c8f-47d8-a677-655c47658d3e')
- def test_check_queue_head(self):
- # Checking Queue Existence by calling HEAD
- for queue_name in self.queues:
- self.check_queue_exists_head(queue_name)
-
- @test.attr(type='smoke')
- @test.idempotent_id('0a0feeca-7768-4303-806d-82bbbb796ad3')
- def test_list_queues(self):
- # Listing queues
- _, body = self.list_queues()
- self.assertEqual(len(body['queues']), len(self.queues))
- for item in body['queues']:
- self.assertIn(item['name'], self.queues)
-
- @test.attr(type='smoke')
- @test.idempotent_id('8fb66602-077d-49d6-ae1a-5f2091739178')
- def test_get_queue_stats(self):
- # Retrieve random queue
- queue_name = self.queues[data_utils.rand_int_id(0,
- len(self.queues) - 1)]
- # Get Queue Stats for a newly created Queue
- _, body = self.get_queue_stats(queue_name)
- msgs = body['messages']
- for element in ('free', 'claimed', 'total'):
- self.assertEqual(0, msgs[element])
- for element in ('oldest', 'newest'):
- self.assertNotIn(element, msgs)
-
- @test.attr(type='smoke')
- @test.idempotent_id('0e2441e6-6593-4bdb-a3c0-20e66eeb3fff')
- def test_set_and_get_queue_metadata(self):
- # Retrieve random queue
- queue_name = self.queues[data_utils.rand_int_id(0,
- len(self.queues) - 1)]
- # Check the Queue has no metadata
- _, body = self.get_queue_metadata(queue_name)
- self.assertThat(body, matchers.HasLength(0))
- # Create metadata
- key3 = [0, 1, 2, 3, 4]
- key2 = data_utils.rand_name('value')
- req_body1 = dict()
- req_body1[data_utils.rand_name('key3')] = key3
- req_body1[data_utils.rand_name('key2')] = key2
- req_body = dict()
- req_body[data_utils.rand_name('key1')] = req_body1
- # Set Queue Metadata
- self.set_queue_metadata(queue_name, req_body)
-
- # Get Queue Metadata
- _, body = self.get_queue_metadata(queue_name)
- self.assertThat(body, matchers.Equals(req_body))
-
- @classmethod
- def resource_cleanup(cls):
- for queue_name in cls.queues:
- cls.client.delete_queue(queue_name)
- super(TestManageQueue, cls).resource_cleanup()
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_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 78d6aea..7a4547a 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -74,14 +74,14 @@
# query and setup steps are only required if the extension is available
# and only if the router's default type is distributed.
if test.is_extension_enabled('dvr', 'network'):
- cls.is_dvr_router = cls.admin_client.show_router(
+ cls.is_dvr_router = cls.admin_routers_client.show_router(
cls.router['id'])['router'].get('distributed', False)
if cls.is_dvr_router:
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
cls.port = cls.create_port(cls.network)
- cls.client.add_router_interface(cls.router['id'],
- port_id=cls.port['id'])
+ cls.routers_client.add_router_interface(
+ cls.router['id'], port_id=cls.port['id'])
# NOTE: Sometimes we have seen this test fail with dvr in,
# multinode tests, since the dhcp port is not created before
# the test gets executed and so the router is not scheduled
@@ -92,15 +92,15 @@
external_gateway_info = {
'network_id': CONF.network.public_network_id,
'enable_snat': True}
- cls.admin_client.update_router_with_snat_gw_info(
+ cls.admin_routers_client.update_router_with_snat_gw_info(
cls.router['id'],
external_gateway_info=external_gateway_info)
@classmethod
def resource_cleanup(cls):
if cls.is_dvr_router:
- cls.client.remove_router_interface(cls.router['id'],
- port_id=cls.port['id'])
+ cls.routers_client.remove_router_interface(cls.router['id'],
+ port_id=cls.port['id'])
super(L3AgentSchedulerTestJSON, cls).resource_cleanup()
@test.idempotent_id('b7ce6e89-e837-4ded-9b78-9ed3c9c6a45a')
@@ -114,7 +114,8 @@
self.agent['id'],
router_id=self.router['id'])
body = (
- self.admin_client.list_l3_agents_hosting_router(self.router['id']))
+ self.admin_routers_client.list_l3_agents_hosting_router(
+ self.router['id']))
for agent in body['agents']:
l3_agent_ids.append(agent['id'])
self.assertIn('agent_type', agent)
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/admin/test_routers_dvr.py b/tempest/api/network/admin/test_routers_dvr.py
index 3e787af..2bc86ad 100644
--- a/tempest/api/network/admin/test_routers_dvr.py
+++ b/tempest/api/network/admin/test_routers_dvr.py
@@ -34,8 +34,8 @@
# has a distributed attribute.
super(RoutersTestDVR, cls).resource_setup()
name = data_utils.rand_name('pretest-check')
- router = cls.admin_client.create_router(name)
- cls.admin_client.delete_router(router['router']['id'])
+ router = cls.admin_routers_client.create_router(name)
+ cls.admin_routers_client.delete_router(router['router']['id'])
if 'distributed' not in router['router']:
msg = "'distributed' flag not found. DVR Possibly not enabled"
raise cls.skipException(msg)
@@ -53,8 +53,9 @@
set to True
"""
name = data_utils.rand_name('router')
- router = self.admin_client.create_router(name, distributed=True)
- self.addCleanup(self.admin_client.delete_router,
+ router = self.admin_routers_client.create_router(name,
+ distributed=True)
+ self.addCleanup(self.admin_routers_client.delete_router,
router['router']['id'])
self.assertTrue(router['router']['distributed'])
@@ -72,8 +73,9 @@
as opposed to a "Distributed Virtual Router"
"""
name = data_utils.rand_name('router')
- router = self.admin_client.create_router(name, distributed=False)
- self.addCleanup(self.admin_client.delete_router,
+ router = self.admin_routers_client.create_router(name,
+ distributed=False)
+ self.addCleanup(self.admin_routers_client.delete_router,
router['router']['id'])
self.assertFalse(router['router']['distributed'])
@@ -93,11 +95,12 @@
"""
name = data_utils.rand_name('router')
# router needs to be in admin state down in order to be upgraded to DVR
- router = self.admin_client.create_router(name, distributed=False,
- admin_state_up=False)
- self.addCleanup(self.admin_client.delete_router,
+ router = self.admin_routers_client.create_router(name,
+ distributed=False,
+ admin_state_up=False)
+ self.addCleanup(self.admin_routers_client.delete_router,
router['router']['id'])
self.assertFalse(router['router']['distributed'])
- router = self.admin_client.update_router(router['router']['id'],
- distributed=True)
+ router = self.admin_routers_client.update_router(
+ router['router']['id'], distributed=True)
self.assertTrue(router['router']['distributed'])
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index f209f89..8e0c361 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
@@ -71,6 +71,7 @@
cls.agents_client = cls.os.network_agents_client
cls.network_extensions_client = cls.os.network_extensions_client
cls.networks_client = cls.os.networks_client
+ cls.routers_client = cls.os.routers_client
cls.subnetpools_client = cls.os.subnetpools_client
cls.subnets_client = cls.os.subnets_client
cls.ports_client = cls.os.ports_client
@@ -231,7 +232,7 @@
ext_gw_info['network_id'] = external_network_id
if enable_snat is not None:
ext_gw_info['enable_snat'] = enable_snat
- body = cls.client.create_router(
+ body = cls.routers_client.create_router(
router_name, external_gateway_info=ext_gw_info,
admin_state_up=admin_state_up, **kwargs)
router = body['router']
@@ -250,8 +251,8 @@
@classmethod
def create_router_interface(cls, router_id, subnet_id):
"""Wrapper utility that returns a router interface."""
- interface = cls.client.add_router_interface(router_id,
- subnet_id=subnet_id)
+ interface = cls.routers_client.add_router_interface(
+ router_id, subnet_id=subnet_id)
return interface
@classmethod
@@ -260,12 +261,12 @@
interfaces = body['ports']
for i in interfaces:
try:
- cls.client.remove_router_interface(
+ cls.routers_client.remove_router_interface(
router['id'],
subnet_id=i['fixed_ips'][0]['subnet_id'])
except lib_exc.NotFound:
pass
- cls.client.delete_router(router['id'])
+ cls.routers_client.delete_router(router['id'])
class BaseAdminNetworkTest(BaseNetworkTest):
@@ -278,6 +279,7 @@
cls.admin_client = cls.os_adm.network_client
cls.admin_agents_client = cls.os_adm.network_agents_client
cls.admin_networks_client = cls.os_adm.networks_client
+ cls.admin_routers_client = cls.os_adm.routers_client
cls.admin_subnets_client = cls.os_adm.subnets_client
cls.admin_ports_client = cls.os_adm.ports_client
cls.admin_quotas_client = cls.os_adm.network_quotas_client
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
index 3495b76f..807257f 100644
--- a/tempest/api/network/base_routers.py
+++ b/tempest/api/network/base_routers.py
@@ -33,31 +33,31 @@
self.addCleanup(self._cleanup_router, router)
return router
- def _delete_router(self, router_id, network_client=None):
- client = network_client or self.client
+ def _delete_router(self, router_id, routers_client=None):
+ client = routers_client or self.routers_client
client.delete_router(router_id)
# Asserting that the router is not found in the list
# after deletion
- list_body = self.client.list_routers()
+ list_body = self.routers_client.list_routers()
routers_list = list()
for router in list_body['routers']:
routers_list.append(router['id'])
self.assertNotIn(router_id, routers_list)
def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
- interface = self.client.add_router_interface(router_id,
- subnet_id=subnet_id)
+ interface = self.routers_client.add_router_interface(
+ router_id, subnet_id=subnet_id)
self.addCleanup(self._remove_router_interface_with_subnet_id,
router_id, subnet_id)
self.assertEqual(subnet_id, interface['subnet_id'])
return interface
def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
- body = self.client.remove_router_interface(router_id,
- subnet_id=subnet_id)
+ body = self.routers_client.remove_router_interface(router_id,
+ subnet_id=subnet_id)
self.assertEqual(subnet_id, body['subnet_id'])
def _remove_router_interface_with_port_id(self, router_id, port_id):
- body = self.client.remove_router_interface(router_id,
- port_id=port_id)
+ body = self.routers_client.remove_router_interface(router_id,
+ port_id=port_id)
self.assertEqual(port_id, body['port_id'])
diff --git a/tempest/api/network/test_dhcp_ipv6.py b/tempest/api/network/test_dhcp_ipv6.py
index dbb0d14..fbed5e8 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,10 +66,10 @@
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]):
- self.client.remove_router_interface(port['device_id'],
- port_id=port['id'])
+ if (port['device_owner'].startswith('network:router_interface') and
+ port['device_id'] in [r['id'] for r in self.routers]):
+ self.routers_client.remove_router_interface(port['device_id'],
+ port_id=port['id'])
else:
if port['id'] in [p['id'] for p in self.ports]:
self.ports_client.delete_port(port['id'])
@@ -80,11 +80,11 @@
if subnet['id'] in [s['id'] for s in self.subnets]:
self.subnets_client.delete_subnet(subnet['id'])
self._remove_from_list_by_index(self.subnets, subnet)
- body = self.client.list_routers()
+ body = self.routers_client.list_routers()
routers = body['routers']
for router in routers:
if router['id'] in [r['id'] for r in self.routers]:
- self.client.delete_router(router['id'])
+ self.routers_client.delete_router(router['id'])
self._remove_from_list_by_index(self.routers, router)
def _get_ips_from_subnet(self, **kwargs):
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..a31a4f0 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
@@ -451,7 +451,7 @@
# Creates 2 networks in one request
network_list = [{'name': data_utils.rand_name('network-')},
{'name': data_utils.rand_name('network-')}]
- body = self.client.create_bulk_network(networks=network_list)
+ body = self.networks_client.create_bulk_networks(networks=network_list)
created_networks = body['networks']
self.addCleanup(self._delete_networks, created_networks)
# Asserting that the networks are found in the list after creation
@@ -486,7 +486,7 @@
}
subnets_list.append(p1)
del subnets_list[1]['name']
- body = self.client.create_bulk_subnet(subnets=subnets_list)
+ body = self.subnets_client.create_bulk_subnets(subnets=subnets_list)
created_subnets = body['subnets']
self.addCleanup(self._delete_subnets, created_subnets)
# Asserting that the subnets are found in the list after creation
@@ -512,7 +512,7 @@
}
port_list.append(p1)
del port_list[1]['name']
- body = self.client.create_bulk_port(ports=port_list)
+ body = self.ports_client.create_bulk_ports(ports=port_list)
created_ports = body['ports']
self.addCleanup(self._delete_ports, created_ports)
# Asserting that the ports are found in the list after creation
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_ports.py b/tempest/api/network/test_ports.py
index d7b220b..5ff23c6 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -75,7 +75,7 @@
network2 = self.create_network(network_name=name)
network_list = [network1['id'], network2['id']]
port_list = [{'network_id': net_id} for net_id in network_list]
- body = self.client.create_bulk_port(ports=port_list)
+ body = self.ports_client.create_bulk_ports(ports=port_list)
created_ports = body['ports']
port1 = created_ports[0]
port2 = created_ports[1]
@@ -197,13 +197,13 @@
subnet = self.create_subnet(network)
self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
router = self.create_router(data_utils.rand_name('router-'))
- self.addCleanup(self.client.delete_router, router['id'])
+ self.addCleanup(self.routers_client.delete_router, router['id'])
port = self.ports_client.create_port(network_id=network['id'])
# Add router interface to port created above
- self.client.add_router_interface(router['id'],
- port_id=port['port']['id'])
- self.addCleanup(self.client.remove_router_interface, router['id'],
- port_id=port['port']['id'])
+ self.routers_client.add_router_interface(router['id'],
+ port_id=port['port']['id'])
+ self.addCleanup(self.routers_client.remove_router_interface,
+ router['id'], port_id=port['port']['id'])
# List ports filtered by router_id
port_list = self.ports_client.list_ports(device_id=router['id'])
ports = port_list['ports']
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 0b64be4..01afc51 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -52,7 +52,7 @@
# NOTE(salv-orlando): Do not invoke self.create_router
# as we need to check the response code
name = data_utils.rand_name('router-')
- create_body = self.client.create_router(
+ create_body = self.routers_client.create_router(
name, external_gateway_info={
"network_id": CONF.network.public_network_id},
admin_state_up=False)
@@ -63,24 +63,25 @@
CONF.network.public_network_id)
self.assertEqual(create_body['router']['admin_state_up'], False)
# Show details of the created router
- show_body = self.client.show_router(create_body['router']['id'])
+ show_body = self.routers_client.show_router(
+ create_body['router']['id'])
self.assertEqual(show_body['router']['name'], name)
self.assertEqual(
show_body['router']['external_gateway_info']['network_id'],
CONF.network.public_network_id)
self.assertEqual(show_body['router']['admin_state_up'], False)
# List routers and verify if created router is there in response
- list_body = self.client.list_routers()
+ list_body = self.routers_client.list_routers()
routers_list = list()
for router in list_body['routers']:
routers_list.append(router['id'])
self.assertIn(create_body['router']['id'], routers_list)
# Update the name of router and verify if it is updated
updated_name = 'updated ' + name
- update_body = self.client.update_router(create_body['router']['id'],
- name=updated_name)
+ update_body = self.routers_client.update_router(
+ create_body['router']['id'], name=updated_name)
self.assertEqual(update_body['router']['name'], updated_name)
- show_body = self.client.show_router(
+ show_body = self.routers_client.show_router(
create_body['router']['id'])
self.assertEqual(show_body['router']['name'], updated_name)
@@ -95,9 +96,9 @@
self.addCleanup(self.identity_utils.delete_project, project_id)
name = data_utils.rand_name('router-')
- create_body = self.admin_client.create_router(name,
- tenant_id=project_id)
- self.addCleanup(self.admin_client.delete_router,
+ create_body = self.admin_routers_client.create_router(
+ name, tenant_id=project_id)
+ self.addCleanup(self.admin_routers_client.delete_router,
create_body['router']['id'])
self.assertEqual(project_id, create_body['router']['tenant_id'])
@@ -122,9 +123,9 @@
external_gateway_info = {
'network_id': CONF.network.public_network_id,
'enable_snat': enable_snat}
- create_body = self.admin_client.create_router(
+ create_body = self.admin_routers_client.create_router(
name, external_gateway_info=external_gateway_info)
- self.addCleanup(self.admin_client.delete_router,
+ self.addCleanup(self.admin_routers_client.delete_router,
create_body['router']['id'])
# Verify snat attributes after router creation
self._verify_router_gateway(create_body['router']['id'],
@@ -137,8 +138,8 @@
subnet = self.create_subnet(network)
router = self._create_router(data_utils.rand_name('router-'))
# Add router interface with subnet id
- interface = self.client.add_router_interface(router['id'],
- subnet_id=subnet['id'])
+ interface = self.routers_client.add_router_interface(
+ router['id'], subnet_id=subnet['id'])
self.addCleanup(self._remove_router_interface_with_subnet_id,
router['id'], subnet['id'])
self.assertIn('subnet_id', interface.keys())
@@ -158,7 +159,7 @@
port_body = self.ports_client.create_port(
network_id=network['id'])
# add router interface to port created above
- interface = self.client.add_router_interface(
+ interface = self.routers_client.add_router_interface(
router['id'],
port_id=port_body['port']['id'])
self.addCleanup(self._remove_router_interface_with_port_id,
@@ -172,7 +173,7 @@
router['id'])
def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
- show_body = self.admin_client.show_router(router_id)
+ show_body = self.admin_routers_client.show_router(router_id)
actual_ext_gw_info = show_body['router']['external_gateway_info']
if exp_ext_gw_info is None:
self.assertIsNone(actual_ext_gw_info)
@@ -198,7 +199,7 @@
@test.idempotent_id('6cc285d8-46bf-4f36-9b1a-783e3008ba79')
def test_update_router_set_gateway(self):
router = self._create_router(data_utils.rand_name('router-'))
- self.client.update_router(
+ self.routers_client.update_router(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id})
@@ -212,7 +213,7 @@
@test.requires_ext(extension='ext-gw-mode', service='network')
def test_update_router_set_gateway_with_snat_explicit(self):
router = self._create_router(data_utils.rand_name('router-'))
- self.admin_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router_with_snat_gw_info(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -227,7 +228,7 @@
@test.requires_ext(extension='ext-gw-mode', service='network')
def test_update_router_set_gateway_without_snat(self):
router = self._create_router(data_utils.rand_name('router-'))
- self.admin_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router_with_snat_gw_info(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -243,7 +244,8 @@
router = self._create_router(
data_utils.rand_name('router-'),
external_network_id=CONF.network.public_network_id)
- self.client.update_router(router['id'], external_gateway_info={})
+ self.routers_client.update_router(router['id'],
+ external_gateway_info={})
self._verify_router_gateway(router['id'])
# No gateway port expected
list_body = self.admin_ports_client.list_ports(
@@ -257,7 +259,7 @@
router = self._create_router(
data_utils.rand_name('router-'),
external_network_id=CONF.network.public_network_id)
- self.admin_client.update_router_with_snat_gw_info(
+ self.admin_routers_client.update_router_with_snat_gw_info(
router['id'],
external_gateway_info={
'network_id': CONF.network.public_network_id,
@@ -270,7 +272,7 @@
@test.idempotent_id('c86ac3a8-50bd-4b00-a6b8-62af84a0765c')
@test.requires_ext(extension='extraroute', service='network')
- def test_update_extra_route(self):
+ def test_update_delete_extra_route(self):
# Create different cidr for each subnet to avoid cidr duplicate
# The cidr starts from tenant_cidr
next_cidr = netaddr.IPNetwork(self.tenant_cidr)
@@ -301,9 +303,9 @@
)
test_routes.sort(key=lambda x: x['destination'])
- extra_route = self.client.update_extra_routes(router['id'],
- routes=test_routes)
- show_body = self.client.show_router(router['id'])
+ extra_route = self.routers_client.update_extra_routes(
+ router['id'], routes=test_routes)
+ show_body = self.routers_client.show_router(router['id'])
# Assert the number of routes
self.assertEqual(routes_num, len(extra_route['router']['routes']))
self.assertEqual(routes_num, len(show_body['router']['routes']))
@@ -323,18 +325,23 @@
routes[i]['destination'])
self.assertEqual(test_routes[i]['nexthop'], routes[i]['nexthop'])
+ self.routers_client.delete_extra_routes(router['id'])
+ show_body_after_deletion = self.routers_client.show_router(
+ router['id'])
+ self.assertEmpty(show_body_after_deletion['router']['routes'])
+
def _delete_extra_routes(self, router_id):
- self.client.delete_extra_routes(router_id)
+ self.routers_client.delete_extra_routes(router_id)
@test.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9')
def test_update_router_admin_state(self):
router = self._create_router(data_utils.rand_name('router-'))
self.assertFalse(router['admin_state_up'])
# Update router admin state
- update_body = self.client.update_router(router['id'],
- admin_state_up=True)
+ update_body = self.routers_client.update_router(router['id'],
+ admin_state_up=True)
self.assertTrue(update_body['router']['admin_state_up'])
- show_body = self.client.show_router(router['id'])
+ show_body = self.routers_client.show_router(router['id'])
self.assertTrue(show_body['router']['admin_state_up'])
@test.attr(type='smoke')
@@ -381,21 +388,21 @@
@test.idempotent_id('141297aa-3424-455d-aa8d-f2d95731e00a')
def test_create_distributed_router(self):
name = data_utils.rand_name('router')
- create_body = self.admin_client.create_router(
+ create_body = self.admin_routers_client.create_router(
name, distributed=True)
self.addCleanup(self._delete_router,
create_body['router']['id'],
- self.admin_client)
+ self.admin_routers_client)
self.assertTrue(create_body['router']['distributed'])
@test.idempotent_id('644d7a4a-01a1-4b68-bb8d-0c0042cb1729')
def test_convert_centralized_router(self):
router = self._create_router(data_utils.rand_name('router'))
self.assertNotIn('distributed', router)
- update_body = self.admin_client.update_router(router['id'],
- distributed=True)
+ update_body = self.admin_routers_client.update_router(router['id'],
+ distributed=True)
self.assertTrue(update_body['router']['distributed'])
- show_body = self.admin_client.show_router(router['id'])
+ show_body = self.admin_routers_client.show_router(router['id'])
self.assertTrue(show_body['router']['distributed'])
- show_body = self.client.show_router(router['id'])
+ show_body = self.routers_client.show_router(router['id'])
self.assertNotIn('distributed', show_body['router'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 7b07d42..36aaf2d 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
@@ -47,7 +47,7 @@
@test.idempotent_id('37a94fc0-a834-45b9-bd23-9a81d2fd1e22')
def test_router_add_gateway_invalid_network_returns_404(self):
self.assertRaises(lib_exc.NotFound,
- self.client.update_router,
+ self.routers_client.update_router,
self.router['id'],
external_gateway_info={
'network_id': self.router['id']})
@@ -60,7 +60,7 @@
sub_cidr = netaddr.IPNetwork(self.tenant_cidr).next()
self.create_subnet(alt_network, cidr=sub_cidr)
self.assertRaises(lib_exc.BadRequest,
- self.client.update_router,
+ self.routers_client.update_router,
self.router['id'],
external_gateway_info={
'network_id': alt_network['id']})
@@ -84,31 +84,31 @@
@test.attr(type=['negative'])
@test.idempotent_id('04df80f9-224d-47f5-837a-bf23e33d1c20')
def test_router_remove_interface_in_use_returns_409(self):
- self.client.add_router_interface(self.router['id'],
- subnet_id=self.subnet['id'])
+ self.routers_client.add_router_interface(self.router['id'],
+ subnet_id=self.subnet['id'])
self.assertRaises(lib_exc.Conflict,
- self.client.delete_router,
+ self.routers_client.delete_router,
self.router['id'])
@test.attr(type=['negative'])
@test.idempotent_id('c2a70d72-8826-43a7-8208-0209e6360c47')
def test_show_non_existent_router_returns_404(self):
router = data_utils.rand_name('non_exist_router')
- self.assertRaises(lib_exc.NotFound, self.client.show_router,
+ self.assertRaises(lib_exc.NotFound, self.routers_client.show_router,
router)
@test.attr(type=['negative'])
@test.idempotent_id('b23d1569-8b0c-4169-8d4b-6abd34fad5c7')
def test_update_non_existent_router_returns_404(self):
router = data_utils.rand_name('non_exist_router')
- self.assertRaises(lib_exc.NotFound, self.client.update_router,
+ self.assertRaises(lib_exc.NotFound, self.routers_client.update_router,
router, name="new_name")
@test.attr(type=['negative'])
@test.idempotent_id('c7edc5ad-d09d-41e6-a344-5c0c31e2e3e4')
def test_delete_non_existent_router_returns_404(self):
router = data_utils.rand_name('non_exist_router')
- self.assertRaises(lib_exc.NotFound, self.client.delete_router,
+ self.assertRaises(lib_exc.NotFound, self.routers_client.delete_router,
router)
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..c6cc8e2 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
@@ -30,7 +30,7 @@
Lists subnet pool.
Show subnet pool details.
- v2.0 of the Neutron API is assumed. It is assumed that subnetpools
+ v2.0 of the Neutron API is assumed. It is assumed that subnet_allocation
options mentioned in the [network-feature-enabled] section and
default_network option mentioned in the [network] section of
etc/tempest.conf:
@@ -40,8 +40,8 @@
@classmethod
def skip_checks(cls):
super(SubnetPoolsTestJSON, cls).skip_checks()
- if not test.is_extension_enabled('subnetpools', 'network'):
- msg = "subnet pools extension not enabled."
+ if not test.is_extension_enabled('subnet_allocation', 'network'):
+ msg = "subnet_allocation extension not enabled."
raise cls.skipException(msg)
@test.attr(type='smoke')
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/templates/cinder_basic.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
index ffff580..61c271c 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
@@ -1,10 +1,15 @@
heat_template_version: 2013-05-23
+parameters:
+ volume_size:
+ type: number
+ default: 1
+
resources:
volume:
type: OS::Cinder::Volume
properties:
- size: 1
+ size: { get_param: volume_size }
description: a descriptive description
name: volume_name
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
index b660c19..0bc6d69 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
@@ -1,11 +1,16 @@
heat_template_version: 2013-05-23
+parameters:
+ volume_size:
+ type: number
+ default: 1
+
resources:
volume:
deletion_policy: 'Retain'
type: OS::Cinder::Volume
properties:
- size: 1
+ size: { get_param: volume_size }
description: a descriptive description
name: volume_name
diff --git a/tempest/api/orchestration/stacks/test_limits.py b/tempest/api/orchestration/stacks/test_limits.py
index 2acf97b..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
@@ -33,7 +32,7 @@
Foo: bar''' % fill
ex = self.assertRaises(lib_exc.BadRequest, self.create_stack,
stack_name, template)
- self.assertIn('Template exceeds maximum allowed size', str(ex))
+ self.assertIn('exceeds maximum allowed size', str(ex))
@test.idempotent_id('d1b83e73-7cad-4a22-9839-036548c5387c')
def test_exceed_max_resources_per_stack(self):
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 09e863e..5483361 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")
@@ -149,7 +151,7 @@
def test_created_router(self):
"""Verifies created router."""
router_id = self.test_resources.get('Router')['physical_resource_id']
- body = self.network_client.show_router(router_id)
+ body = self.routers_client.show_router(router_id)
router = body['router']
self.assertEqual(self.neutron_basic_template['resources'][
'Router']['properties']['name'], router['name'])
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..a5aaf6e 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
@@ -33,8 +32,7 @@
self.assertIsNotNone(volume_id)
volume = self.volumes_client.show_volume(volume_id)['volume']
self.assertEqual('available', volume.get('status'))
- self.assertEqual(template['resources']['volume']['properties'][
- 'size'], volume.get('size'))
+ self.assertEqual(CONF.volume.volume_size, volume.get('size'))
# Some volume properties have been renamed with Cinder v2
if CONF.volume_feature_enabled.api_v2:
@@ -52,8 +50,8 @@
def _outputs_verify(self, stack_identifier, template):
self.assertEqual('available',
self.get_stack_output(stack_identifier, 'status'))
- self.assertEqual(str(template['resources']['volume']['properties'][
- 'size']), self.get_stack_output(stack_identifier, 'size'))
+ self.assertEqual(str(CONF.volume.volume_size),
+ self.get_stack_output(stack_identifier, 'size'))
self.assertEqual(template['resources']['volume']['properties'][
'description'], self.get_stack_output(stack_identifier,
'display_description'))
@@ -66,7 +64,12 @@
"""Create and delete a volume via OS::Cinder::Volume."""
stack_name = data_utils.rand_name('heat')
template = self.read_template('cinder_basic')
- stack_identifier = self.create_stack(stack_name, template)
+ stack_identifier = self.create_stack(
+ stack_name,
+ template,
+ parameters={
+ 'volume_size': CONF.volume.volume_size
+ })
self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
# Verify with cinder that the volume exists, with matching details
@@ -95,7 +98,12 @@
"""Ensure the 'Retain' deletion policy is respected."""
stack_name = data_utils.rand_name('heat')
template = self.read_template('cinder_basic_delete_retain')
- stack_identifier = self.create_stack(stack_name, template)
+ stack_identifier = self.create_stack(
+ stack_name,
+ template,
+ parameters={
+ 'volume_size': CONF.volume.volume_size
+ })
self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
# Verify with cinder that the volume exists, with matching details
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 2b7ee45..755365d 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -12,11 +12,20 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
from tempest.api.volume import base
+from tempest import config
from tempest import test
+CONF = config.CONF
+
+
+def _get_host(host):
+ if CONF.volume_feature_enabled.volume_services:
+ host = host.split('@')[0]
+ return host
+
+
class VolumesServicesV2TestJSON(base.BaseVolumeAdminTest):
"""Tests Volume Services API.
@@ -28,7 +37,10 @@
super(VolumesServicesV2TestJSON, cls).resource_setup()
cls.services = (cls.admin_volume_services_client.list_services()
['services'])
- cls.host_name = cls.services[0]['host']
+ # NOTE: Cinder service-list API returns the list contains
+ # "<host name>@<driver name>" like "nova-compute01@lvmdriver-1".
+ # So here picks <host name> up as a host.
+ cls.host_name = _get_host(cls.services[0]['host'])
cls.binary_name = cls.services[0]['binary']
@test.idempotent_id('e0218299-0a59-4f43-8b2b-f1c035b3d26d')
@@ -48,7 +60,7 @@
@test.idempotent_id('178710e4-7596-4e08-9333-745cb8bc4f8d')
def test_get_service_by_host_name(self):
services_on_host = [service for service in self.services if
- service['host'] == self.host_name]
+ _get_host(service['host']) == self.host_name]
services = (self.admin_volume_services_client.list_services(
host=self.host_name)['services'])
@@ -68,7 +80,7 @@
host=self.host_name, binary=self.binary_name))['services']
self.assertEqual(1, len(services))
- self.assertEqual(self.host_name, services[0]['host'])
+ self.assertEqual(self.host_name, _get_host(services[0]['host']))
self.assertEqual(self.binary_name, services[0]['binary'])
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/api_schema/response/messaging/__init__.py b/tempest/api_schema/response/messaging/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api_schema/response/messaging/__init__.py
+++ /dev/null
diff --git a/tempest/api_schema/response/messaging/v1/queues.py b/tempest/api_schema/response/messaging/v1/queues.py
deleted file mode 100644
index 09e0147..0000000
--- a/tempest/api_schema/response/messaging/v1/queues.py
+++ /dev/null
@@ -1,239 +0,0 @@
-
-# Copyright (c) 2014 Rackspace, 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.
-
-
-list_link = {
- 'type': 'object',
- 'properties': {
- 'rel': {'type': 'string'},
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- }
- },
- 'required': ['href', 'rel']
-}
-
-list_queue = {
- 'type': 'object',
- 'properties': {
- 'name': {'type': 'string'},
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'metadata': {'type': 'object'}
- },
- 'required': ['name', 'href']
-}
-
-list_queues = {
- 'status_code': [200, 204],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'links': {
- 'type': 'array',
- 'items': list_link,
- 'maxItems': 1
- },
- 'queues': {
- 'type': 'array',
- 'items': list_queue
- }
- },
- 'required': ['links', 'queues']
- }
-}
-
-age = {
- 'type': 'number',
- 'minimum': 0
-}
-
-message_link = {
- 'type': 'object',
- 'properties': {
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'age': age,
- 'created': {
- 'type': 'string',
- 'format': 'date-time'
- }
- },
- 'required': ['href', 'age', 'created']
-}
-
-messages = {
- 'type': 'object',
- 'properties': {
- 'free': {'type': 'number'},
- 'claimed': {'type': 'number'},
- 'total': {'type': 'number'},
- 'oldest': message_link,
- 'newest': message_link
- },
- 'required': ['free', 'claimed', 'total']
-}
-
-queue_stats = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'messages': messages
- },
- 'required': ['messages']
- }
-}
-
-resource_schema = {
- 'type': 'array',
- 'items': {
- 'type': 'string'
- },
- 'minItems': 1
-}
-
-post_messages = {
- 'status_code': [201],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'resources': resource_schema,
- 'partial': {'type': 'boolean'}
- }
- },
- 'required': ['resources', 'partial']
-}
-
-message_ttl = {
- 'type': 'number',
- 'minimum': 1
-}
-
-list_messages_links = {
- 'type': 'array',
- 'maxItems': 1,
- 'minItems': 1,
- 'items': {
- 'type': 'object',
- 'properties': {
- 'rel': {'type': 'string'},
- 'href': {'type': 'string'}
- },
- 'required': ['rel', 'href']
- }
-}
-
-list_messages_response = {
- 'type': 'array',
- 'minItems': 1,
- 'items': {
- 'type': 'object',
- 'properties': {
- 'href': {'type': 'string'},
- 'ttl': message_ttl,
- 'age': age,
- 'body': {'type': 'object'}
- },
- 'required': ['href', 'ttl', 'age', 'body']
- }
-}
-
-list_messages = {
- 'status_code': [200, 204],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'links': list_messages_links,
- 'messages': list_messages_response
- }
- },
- 'required': ['links', 'messages']
-}
-
-single_message = {
- 'type': 'object',
- 'properties': {
- 'href': {'type': 'string'},
- 'ttl': message_ttl,
- 'age': age,
- 'body': {'type': 'object'}
- },
- 'required': ['href', 'ttl', 'age', 'body']
-}
-
-get_single_message = {
- 'status_code': [200],
- 'response_body': single_message
-}
-
-get_multiple_messages = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'array',
- 'items': single_message,
- 'minItems': 1
- }
-}
-
-messages_claimed = {
- 'type': 'object',
- 'properties': {
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'ttl': message_ttl,
- 'age': {'type': 'number'},
- 'body': {'type': 'object'}
- },
- 'required': ['href', 'ttl', 'age', 'body']
-}
-
-claim_messages = {
- 'status_code': [201, 204],
- 'response_body': {
- 'type': 'array',
- 'items': messages_claimed,
- 'minItems': 1
- }
-}
-
-claim_ttl = {
- 'type': 'number',
- 'minimum': 1
-}
-
-query_claim = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'age': {'type': 'number'},
- 'ttl': claim_ttl,
- 'messages': {
- 'type': 'array',
- 'minItems': 1
- }
- },
- 'required': ['ttl', 'age', 'messages']
- }
-}
diff --git a/tempest/clients.py b/tempest/clients.py
index 2ac5e82..8931706 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -16,80 +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.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
@@ -102,40 +103,37 @@
DatabaseLimitsClient
from tempest.services.database.json.versions_client import \
DatabaseVersionsClient
-from tempest.services.identity.v2.json.endpoints_client import \
- EndpointsClient as EndpointsV2Client
-from tempest.services.identity.v2.json.identity_client import \
- IdentityClient
-from tempest.services.identity.v2.json.roles_client import \
- RolesClient
+from tempest.services.identity.v2.json.endpoints_client import EndpointsClient
+from tempest.services.identity.v2.json.identity_client import IdentityClient
+from tempest.services.identity.v2.json.roles_client import RolesClient
from tempest.services.identity.v2.json.services_client import \
- ServicesClient as ServicesV2Client
-from tempest.services.identity.v2.json.tenants_client import \
- TenantsClient
-from tempest.services.identity.v2.json.users_client import \
- UsersClient
+ ServicesClient as IdentityServicesClient
+from tempest.services.identity.v2.json.tenants_client import TenantsClient
+from tempest.services.identity.v2.json.users_client import UsersClient
from tempest.services.identity.v3.json.credentials_client import \
- CredentialsClient as CredentialsV3Client
+ CredentialsClient
+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 \
- GroupsClient as GroupsV3Client
-from tempest.services.identity.v3.json.identity_client import IdentityV3Client
-from tempest.services.identity.v3.json.policies_client import \
- PoliciesClient as PoliciesV3Client
+ EndPointsClient as EndPointsV3Client
+from tempest.services.identity.v3.json.groups_client import GroupsClient
+from tempest.services.identity.v3.json.identity_client import \
+ IdentityClient as IdentityV3Client
+from tempest.services.identity.v3.json.policies_client import PoliciesClient
from tempest.services.identity.v3.json.projects_client import ProjectsClient
-from tempest.services.identity.v3.json.regions_client import \
- RegionsClient as RegionsV3Client
+from tempest.services.identity.v3.json.regions_client import RegionsClient
+from tempest.services.identity.v3.json.roles_client import \
+ RolesClient as RolesV3Client
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 \
+ UsersClient as UsersV3Client
from tempest.services.image.v1.json.images_client import ImagesClient
from tempest.services.image.v2.json.images_client import ImagesClientV2
-from tempest.services.messaging.json.messaging_client import \
- MessagingClient
from tempest.services.network.json.network_client import NetworkClient
+from tempest.services.network.json.routers_client import RoutersClient
from tempest.services.network.json.security_group_rules_client import \
SecurityGroupRulesClient
-from tempest.services.network.json.subnetpools_client import SubnetpoolsClient
from tempest.services.object_storage.account_client import AccountClient
from tempest.services.object_storage.container_client import ContainerClient
from tempest.services.object_storage.object_client import ObjectClient
@@ -203,30 +201,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()
@@ -327,6 +309,14 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**self.default_params)
+ self.routers_client = RoutersClient(
+ self.auth_provider,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **self.default_params)
self.security_group_rules_client = SecurityGroupRulesClient(
self.auth_provider,
CONF.network.catalog_type,
@@ -343,11 +333,6 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**self.default_params)
- self.messaging_client = MessagingClient(
- self.auth_provider,
- CONF.messaging.catalog_type,
- CONF.identity.region,
- **self.default_params_with_timeout_values)
if CONF.service_available.ceilometer:
self.telemetry_client = TelemetryClient(
self.auth_provider,
@@ -396,8 +381,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,
@@ -506,18 +489,16 @@
# Clients below use the admin endpoint type of Keystone API v2
params_v2_admin = params.copy()
params_v2_admin['endpoint_type'] = CONF.identity.v2_admin_endpoint_type
- self.endpoints_v2_client = EndpointsV2Client(self.auth_provider,
- **params_v2_admin)
+ self.endpoints_client = EndpointsClient(self.auth_provider,
+ **params_v2_admin)
self.identity_client = IdentityClient(self.auth_provider,
**params_v2_admin)
self.tenants_client = TenantsClient(self.auth_provider,
**params_v2_admin)
- self.roles_client = RolesClient(self.auth_provider,
- **params_v2_admin)
- self.users_client = UsersClient(self.auth_provider,
- **params_v2_admin)
- self.services_v2_client = ServicesV2Client(self.auth_provider,
- **params_v2_admin)
+ self.roles_client = RolesClient(self.auth_provider, **params_v2_admin)
+ self.users_client = UsersClient(self.auth_provider, **params_v2_admin)
+ self.identity_services_client = IdentityServicesClient(
+ self.auth_provider, **params_v2_admin)
# Clients below use the public endpoint type of Keystone API v2
params_v2_public = params.copy()
@@ -533,19 +514,23 @@
# 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.endpoints_client = EndPointV3Client(self.auth_provider,
- **params_v3)
- self.identity_services_client = IdentityServicesV3Client(
+ self.trusts_client = TrustsClient(self.auth_provider, **params_v3)
+ self.users_v3_client = UsersV3Client(self.auth_provider, **params_v3)
+ self.endpoints_v3_client = EndPointsV3Client(self.auth_provider,
+ **params_v3)
+ self.roles_v3_client = RolesV3Client(self.auth_provider, **params_v3)
+ self.identity_services_v3_client = IdentityServicesV3Client(
self.auth_provider, **params_v3)
- self.policies_client = PoliciesV3Client(self.auth_provider,
- **params_v3)
+ self.policies_client = PoliciesClient(self.auth_provider, **params_v3)
self.projects_client = ProjectsClient(self.auth_provider, **params_v3)
- self.regions_client = RegionsV3Client(self.auth_provider, **params_v3)
- self.credentials_client = CredentialsV3Client(self.auth_provider,
- **params_v3)
- self.groups_client = GroupsV3Client(self.auth_provider, **params_v3)
+ self.regions_client = RegionsClient(self.auth_provider, **params_v3)
+ self.credentials_client = CredentialsClient(self.auth_provider,
+ **params_v3)
+ self.groups_client = GroupsClient(self.auth_provider, **params_v3)
# Token clients do not use the catalog. They only need default_params.
# They read auth_url, so they should only be set if the corresponding
@@ -628,15 +613,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..03dfd7b 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -89,21 +89,22 @@
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
from tempest.services.identity.v2.json import users_client
from tempest.services.network.json import network_client
+from tempest.services.network.json import routers_client
LOG = None
CONF = config.CONF
@@ -121,7 +122,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 +132,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':
@@ -171,6 +172,7 @@
)
network_admin = None
networks_admin = None
+ routers_admin = None
subnets_admin = None
neutron_iso_networks = False
if (CONF.service_available.neutron and
@@ -188,6 +190,12 @@
CONF.network.region or CONF.identity.region,
endpoint_type='adminURL',
**params)
+ routers_admin = routers_client.RoutersClient(
+ _auth,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type='adminURL',
+ **params)
subnets_admin = subnets_client.SubnetsClient(
_auth,
CONF.network.catalog_type,
@@ -195,12 +203,13 @@
endpoint_type='adminURL',
**params)
return (identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, network_admin, networks_admin, subnets_admin)
+ neutron_iso_networks, network_admin, networks_admin, routers_admin,
+ subnets_admin)
def create_resources(opts, resources):
(identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, network_admin, networks_admin,
+ neutron_iso_networks, network_admin, networks_admin, routers_admin,
subnets_admin) = get_admin_clients(opts)
roles = roles_admin.list_roles()['roles']
for u in resources['users']:
@@ -223,14 +232,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']),
@@ -246,27 +255,27 @@
for u in resources['users']:
tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
network_name, router_name = create_network_resources(
- network_admin, networks_admin, subnets_admin, tenant['id'],
- u['name'])
+ network_admin, networks_admin, routers_admin, subnets_admin,
+ tenant['id'], u['name'])
u['network'] = network_name
u['router'] = router_name
LOG.info('Networks created')
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')
@@ -274,7 +283,8 @@
def create_network_resources(network_admin_client, networks_admin_client,
- subnets_admin_client, tenant_id, name):
+ routers_admin_client, subnets_admin_client,
+ tenant_id, name):
def _create_network(name):
resp_body = networks_admin_client.create_network(
@@ -294,7 +304,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:
@@ -305,14 +315,14 @@
def _create_router(router_name):
external_net_id = dict(
network_id=CONF.network.public_network_id)
- resp_body = network_admin_client.create_router(
+ resp_body = routers_admin_client.create_router(
router_name,
external_gateway_info=external_net_id,
tenant_id=tenant_id)
return resp_body['router']
def _add_router_interface(router_id, subnet_id):
- network_admin_client.add_router_interface(router_id,
+ routers_admin_client.add_router_interface(router_id,
subnet_id=subnet_id)
network_name = name + "-network"
@@ -480,8 +490,8 @@
def main(opts=None):
setup_logging()
if not opts:
- LOG.warn("Use of: 'tempest-account-generator' is deprecated, "
- "please use: 'tempest account-generator'")
+ LOG.warning("Use of: 'tempest-account-generator' is deprecated, "
+ "please use: 'tempest account-generator'")
opts = get_options()
if opts.config_file:
config.CONF.set_config_path(opts.config_file)
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index 7b73a61..5a52043 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -77,7 +77,8 @@
def take_action(self, parsed_args):
try:
self.init(parsed_args)
- self._cleanup()
+ if not parsed_args.init_saved_state:
+ self._cleanup()
except Exception:
LOG.exception("Failure during cleanup")
traceback.print_exc()
@@ -231,7 +232,6 @@
return 'Cleanup after tempest run'
def _add_admin(self, tenant_id):
- id_cl = self.admin_mgr.identity_client
rl_cl = self.admin_mgr.roles_client
needs_role = True
roles = rl_cl.list_user_roles(tenant_id, self.admin_id)['roles']
@@ -241,7 +241,7 @@
LOG.debug("User already had admin privilege for this tenant")
if needs_role:
LOG.debug("Adding admin privilege for : %s" % tenant_id)
- id_cl.assign_user_role(tenant_id, self.admin_id,
+ rl_cl.assign_user_role(tenant_id, self.admin_id,
self.admin_role_id)
self.admin_role_added.append(tenant_id)
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 33f19b1..28ffb56 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -78,7 +78,7 @@
if IS_NEUTRON:
CONF_PRIV_NETWORK = _get_network_id(CONF.compute.fixed_network_name,
- CONF.identity.tenant_name)
+ CONF.auth.admin_tenant_name)
CONF_NETWORKS = [CONF_PUB_NETWORK, CONF_PRIV_NETWORK]
@@ -449,7 +449,7 @@
class NetworkRouterService(NetworkService):
def list(self):
- client = self.client
+ client = self.routers_client
routers = client.list_routers(**self.tenant_filter)
routers = routers['routers']
if self.is_preserve:
@@ -460,7 +460,7 @@
return routers
def delete(self):
- client = self.client
+ client = self.routers_client
routers = self.list()
for router in routers:
try:
@@ -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..48c06ff 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -115,25 +115,26 @@
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
from tempest.services.identity.v2.json import users_client
from tempest.services.image.v2.json import images_client
from tempest.services.network.json import network_client
+from tempest.services.network.json import routers_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
from tempest.services.telemetry.json import alarming_client
@@ -270,6 +271,14 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**default_params)
+ self.routers = routers_client.RoutersClient(
+ _auth,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **default_params)
self.subnets = subnets_client.SubnetsClient(
_auth,
CONF.network.catalog_type,
@@ -739,7 +748,7 @@
def _get_router_namespace(client, network):
network_id = _get_resource_by_name(client.networks,
'networks', network)['id']
- n_body = client.networks.list_routers()
+ n_body = client.routers.list_routers()
for router in n_body['routers']:
router_id = router['id']
r_body = client.networks.list_router_interfaces(router_id)
@@ -756,7 +765,7 @@
# we cannot assume they all have the same signature so we need to discard
# the unused response first value it two values are being returned.
body = get_resources()
- if type(body) == tuple:
+ if isinstance(body, tuple):
body = body[1]
if isinstance(body, dict):
body = body[resource]
@@ -824,7 +833,7 @@
client = client_for_user(router['owner'])
# only create a router if the name isn't here
- body = client.networks.list_routers()
+ body = client.routers.list_routers()
if any(item['name'] == router['name'] for item in body['routers']):
LOG.warning("Duplicated router name: %s" % router['name'])
continue
@@ -841,9 +850,9 @@
for subnet in router['subnet']:
subnet_id = _get_resource_by_name(client.networks,
'subnets', subnet)['id']
- client.networks.remove_router_interface(router_id,
- subnet_id=subnet_id)
- client.networks.delete_router(router_id)
+ client.routers.remove_router_interface(router_id,
+ subnet_id=subnet_id)
+ client.routers.delete_router(router_id)
def add_router_interface(routers):
@@ -856,13 +865,13 @@
subnet_id = _get_resource_by_name(client.networks,
'subnets', subnet)['id']
# connect routers to their subnets
- client.networks.add_router_interface(router_id,
- subnet_id=subnet_id)
+ client.routers.add_router_interface(router_id,
+ subnet_id=subnet_id)
# connect routers to external network if set to "gateway"
if router['gateway']:
if CONF.network.public_network_id:
ext_net = CONF.network.public_network_id
- client.networks._update_router(
+ client.routers._update_router(
router_id, set_enable_snat=True,
external_gateway_info={"network_id": ext_net})
else:
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/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 92aa19e..5e5e127 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -269,7 +269,6 @@
'data_processing': 'sahara',
'baremetal': 'ironic',
'identity': 'keystone',
- 'messaging': 'zaqar',
'database': 'trove'
}
# Get catalog list for endpoints to use for validation
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/commands.py b/tempest/common/commands.py
deleted file mode 100644
index 392c9d0..0000000
--- a/tempest/common/commands.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# 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 shlex
-import subprocess
-
-from oslo_log import log as logging
-
-LOG = logging.getLogger(__name__)
-
-
-def copy_file_to_host(file_from, dest, host, username, pkey):
- dest = "%s@%s:%s" % (username, host, dest)
- cmd = "scp -v -o UserKnownHostsFile=/dev/null " \
- "-o StrictHostKeyChecking=no " \
- "-i %(pkey)s %(file1)s %(dest)s" % {'pkey': pkey,
- 'file1': file_from,
- 'dest': dest}
- args = shlex.split(cmd.encode('utf-8'))
- subprocess_args = {'stdout': subprocess.PIPE,
- 'stderr': subprocess.STDOUT}
- proc = subprocess.Popen(args, **subprocess_args)
- stdout, stderr = proc.communicate()
- if proc.returncode != 0:
- LOG.error(("Command {0} returned with exit status {1},"
- "output {2}, error {3}").format(cmd, proc.returncode,
- stdout, stderr))
- return stdout
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 73505e6..2fbd1b2 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 import rest_client
+from tempest.lib.common.utils import data_utils
CONF = config.CONF
@@ -129,7 +129,7 @@
servers = \
[s for s in body_servers['servers'] if s['name'].startswith(name)]
else:
- body = service_client.ResponseBody(body.response, body['server'])
+ body = rest_client.ResponseBody(body.response, body['server'])
servers = [body]
# The name of the method to associate a floating IP to as server is too
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index e2309bf..37c9727 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__)
@@ -31,15 +31,13 @@
admin credentials used for generating credentials.
"""
- def __init__(self, identity_client, projects_client,
- roles_client=None, users_client=None):
+ def __init__(self, identity_client, projects_client, users_client,
+ roles_client):
# The client implies version and credentials
self.identity_client = identity_client
+ self.users_client = users_client
self.projects_client = projects_client
- # this is temporary until v3 roles client and v3 users client are
- # separated, then these clients will become mandatory
- self.roles_client = roles_client or identity_client
- self.users_client = users_client or identity_client
+ self.roles_client = roles_client
def create_user(self, username, password, project, email):
user = self.users_client.create_user(
@@ -73,8 +71,7 @@
msg = 'No "%s" role found' % role_name
raise lib_exc.NotFound(msg)
try:
- self.roles_client.assign_user_role(project['id'], user['id'],
- role['id'])
+ self._assign_user_role(project, user, role)
except lib_exc.Conflict:
LOG.debug("Role %s already assigned on project %s for user %s" % (
role['id'], project['id'], user['id']))
@@ -97,12 +94,12 @@
class V2CredsClient(CredsClient):
- def __init__(self, identity_client, projects_client, roles_client,
- users_client):
+ def __init__(self, identity_client, projects_client, users_client,
+ roles_client):
super(V2CredsClient, self).__init__(identity_client,
projects_client,
- roles_client,
- users_client)
+ users_client,
+ roles_client)
def create_project(self, name, description):
tenant = self.projects_client.create_tenant(
@@ -123,15 +120,23 @@
tenant_name=project['name'], tenant_id=project['id'],
password=password)
+ def _assign_user_role(self, project, user, role):
+ self.roles_client.assign_user_role(project['id'], user['id'],
+ role['id'])
+
class V3CredsClient(CredsClient):
- def __init__(self, identity_client, projects_client, domain_name):
- super(V3CredsClient, self).__init__(identity_client, projects_client)
+ def __init__(self, identity_client, projects_client, users_client,
+ roles_client, domains_client, domain_name):
+ super(V3CredsClient, self).__init__(identity_client, projects_client,
+ users_client, roles_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
@@ -160,19 +165,21 @@
project_domain_id=self.creds_domain['id'],
project_domain_name=self.creds_domain['name'])
- def _list_roles(self):
- roles = self.identity_client.list_roles()['roles']
- return roles
+ def _assign_user_role(self, project, user, role):
+ self.roles_client.assign_user_role_on_project(project['id'],
+ user['id'],
+ role['id'])
def get_creds_client(identity_client,
projects_client,
- roles_client=None,
- users_client=None,
+ users_client,
+ roles_client,
+ domains_client=None,
project_domain_name=None):
if isinstance(identity_client, v2_identity.IdentityClient):
- return V2CredsClient(identity_client, projects_client, roles_client,
- users_client)
+ return V2CredsClient(identity_client, projects_client, users_client,
+ roles_client)
else:
- return V3CredsClient(identity_client,
- projects_client, project_domain_name)
+ return V3CredsClient(identity_client, projects_client, users_client,
+ roles_client, 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 9e5e0dd..2ffc92d 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__)
@@ -59,10 +59,12 @@
self.default_admin_creds = admin_creds
(self.identity_admin_client,
self.tenants_admin_client,
- self.roles_admin_client,
self.users_admin_client,
+ self.roles_admin_client,
+ self.domains_admin_client,
self.network_admin_client,
self.networks_admin_client,
+ self.routers_admin_client,
self.subnets_admin_client,
self.ports_admin_client,
self.security_groups_admin_client) = self._get_admin_clients()
@@ -76,8 +78,9 @@
self.creds_client = cred_client.get_creds_client(
self.identity_admin_client,
self.tenants_admin_client,
- self.roles_admin_client,
self.users_admin_client,
+ self.roles_admin_client,
+ self.domains_admin_client,
self.creds_domain_name)
def _get_admin_clients(self):
@@ -89,14 +92,16 @@
"""
os = clients.Manager(self.default_admin_creds)
if self.identity_version == 'v2':
- return (os.identity_client, os.tenants_client, os.roles_client,
- os.users_client, os.network_client, os.networks_client,
+ return (os.identity_client, os.tenants_client, os.users_client,
+ os.roles_client, None, os.network_client,
+ os.networks_client, os.routers_client, os.subnets_client,
+ os.ports_client, os.security_groups_client)
+ else:
+ return (os.identity_v3_client, os.projects_client,
+ os.users_v3_client, os.roles_v3_client, os.domains_client,
+ os.network_client, os.networks_client, os.routers_client,
os.subnets_client, os.ports_client,
os.security_groups_client)
- else:
- return (os.identity_v3_client, os.projects_client, None, None,
- 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.
@@ -181,12 +186,19 @@
router = self._create_router(router_name, tenant_id)
self._add_router_interface(router['id'], subnet['id'])
except Exception:
- if router:
- self._clear_isolated_router(router['id'], router['name'])
- if subnet:
- self._clear_isolated_subnet(subnet['id'], subnet['name'])
- if network:
- self._clear_isolated_network(network['id'], network['name'])
+ try:
+ if router:
+ self._clear_isolated_router(router['id'], router['name'])
+ if subnet:
+ self._clear_isolated_subnet(subnet['id'], subnet['name'])
+ if network:
+ self._clear_isolated_network(network['id'],
+ network['name'])
+ except Exception as cleanup_exception:
+ msg = "There was an exception trying to setup network " \
+ "resources for tenant %s, and this error happened " \
+ "trying to clean them up: %s"
+ LOG.warning(msg % (tenant_id, cleanup_exception))
raise
return network, subnet, router
@@ -227,14 +239,14 @@
def _create_router(self, router_name, tenant_id):
external_net_id = dict(
network_id=CONF.network.public_network_id)
- resp_body = self.network_admin_client.create_router(
+ resp_body = self.routers_admin_client.create_router(
router_name,
external_gateway_info=external_net_id,
tenant_id=tenant_id)
return resp_body['router']
def _add_router_interface(self, router_id, subnet_id):
- self.network_admin_client.add_router_interface(router_id,
+ self.routers_admin_client.add_router_interface(router_id,
subnet_id=subnet_id)
def get_credentials(self, credential_type):
@@ -285,9 +297,9 @@
return self.get_credentials(roles)
def _clear_isolated_router(self, router_id, router_name):
- net_client = self.network_admin_client
+ client = self.routers_admin_client
try:
- net_client.delete_router(router_id)
+ client.delete_router(router_id)
except lib_exc.NotFound:
LOG.warning('router with name: %s not found for delete' %
router_name)
@@ -321,7 +333,7 @@
(secgroup['name'], secgroup['id']))
def _clear_isolated_net_resources(self):
- net_client = self.network_admin_client
+ client = self.routers_admin_client
for cred in self._creds:
creds = self._creds.get(cred)
if (not creds or not any([creds.router, creds.network,
@@ -334,7 +346,7 @@
if (not self.network_resources or
(self.network_resources.get('router') and creds.subnet)):
try:
- net_client.remove_router_interface(
+ client.remove_router_interface(
creds.router['id'],
subnet_id=creds.subnet['id'])
except lib_exc.NotFound:
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/glance_http.py b/tempest/common/glance_http.py
index 800e977..baf796d 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -47,7 +47,6 @@
self.endpoint_scheme = endpoint_parts.scheme
self.endpoint_hostname = endpoint_parts.hostname
self.endpoint_port = endpoint_parts.port
- self.endpoint_path = endpoint_parts.path
self.connection_class = self._get_connection_class(
self.endpoint_scheme)
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/negative_rest_client.py b/tempest/common/negative_rest_client.py
index d97411c..3495a24 100644
--- a/tempest/common/negative_rest_client.py
+++ b/tempest/common/negative_rest_client.py
@@ -15,30 +15,19 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common import service_client
from tempest import config
+from tempest.lib.common import rest_client
CONF = config.CONF
-class NegativeRestClient(service_client.ServiceClient):
+class NegativeRestClient(rest_client.RestClient):
"""Version of RestClient that does not raise exceptions."""
- def __init__(self, auth_provider, service,
- build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
+ def __init__(self, auth_provider, service, **kwargs):
region, endpoint_type = self._get_region_and_endpoint_type(service)
super(NegativeRestClient, self).__init__(
- auth_provider,
- service,
- region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=(
- disable_ssl_certificate_validation),
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, service, region, endpoint_type=endpoint_type,
+ **kwargs)
def _get_region_and_endpoint_type(self, service):
"""Returns the region for a specific service"""
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
deleted file mode 100644
index b3a5a09..0000000
--- a/tempest/common/service_client.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# 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.common import rest_client
-
-
-class ServiceClient(rest_client.RestClient):
-
- def __init__(self, auth_provider, service, region,
- endpoint_type=None, build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
-
- dscv = disable_ssl_certificate_validation
- params = {
- 'disable_ssl_certificate_validation': dscv,
- 'ca_certs': ca_certs,
- 'trace_requests': trace_requests
- }
-
- if endpoint_type is not None:
- params.update({'endpoint_type': endpoint_type})
- if build_interval is not None:
- params.update({'build_interval': build_interval})
- if build_timeout is not None:
- params.update({'build_timeout': build_timeout})
- super(ServiceClient, self).__init__(auth_provider, service, region,
- **params)
-
-
-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/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 adb9b1a..36e3e3a 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
@@ -186,7 +186,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 14a6ad2..ea151ae 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -352,13 +352,6 @@
help="Does the test environment block migration support "
"cinder iSCSI volumes. Note, libvirt doesn't support this, "
"see https://bugs.launchpad.net/nova/+bug/1398999"),
- # TODO(gilliard): Remove live_migrate_paused_instances at juno-eol.
- cfg.BoolOpt('live_migrate_paused_instances',
- default=False,
- help="Does the test system allow live-migration of paused "
- "instances? Note, this is more than just the ANDing of "
- "paused and live_migrate, but all 3 should be set to True "
- "to run those tests"),
cfg.BoolOpt('vnc_console',
default=False,
help='Enable VNC console. This configuration value should '
@@ -394,12 +387,6 @@
cfg.BoolOpt('personality',
default=True,
help='Does the test environment support server personality'),
- # TODO(mriedem): Remove preserve_ports once juno-eol happens.
- cfg.BoolOpt('preserve_ports',
- default=False,
- help='Does Nova preserve preexisting ports from Neutron '
- 'when deleting an instance? This should be set to True '
- 'if testing Kilo+ Nova.'),
cfg.BoolOpt('attach_encrypted_volume',
default=True,
help='Does the test environment support attaching an '
@@ -417,6 +404,15 @@
cfg.BoolOpt('config_drive',
default=True,
help='Enable special configuration drive with metadata.'),
+ cfg.ListOpt('scheduler_available_filters',
+ default=['all'],
+ help="A list of enabled filters that nova will accept as hints"
+ " to the scheduler when creating a server. A special "
+ "entry 'all' indicates all filters are enabled. Empty "
+ "list indicates all filters are disabled. The full "
+ "available list of filters is in nova.conf: "
+ "DEFAULT.scheduler_available_filters"),
+
]
@@ -574,41 +570,6 @@
" port admin state"),
]
-messaging_group = cfg.OptGroup(name='messaging',
- title='Messaging Service')
-
-MessagingGroup = [
- cfg.StrOpt('catalog_type',
- default='messaging',
- help='Catalog type of the Messaging service.'),
- cfg.IntOpt('max_queues_per_page',
- default=20,
- help='The maximum number of queue records per page when '
- 'listing queues'),
- cfg.IntOpt('max_queue_metadata',
- default=65536,
- help='The maximum metadata size for a queue'),
- cfg.IntOpt('max_messages_per_page',
- default=20,
- help='The maximum number of queue message per page when '
- 'listing (or) posting messages'),
- cfg.IntOpt('max_message_size',
- default=262144,
- help='The maximum size of a message body'),
- cfg.IntOpt('max_messages_per_claim',
- default=20,
- help='The maximum number of messages per claim'),
- cfg.IntOpt('max_message_ttl',
- default=1209600,
- help='The maximum ttl for a message'),
- cfg.IntOpt('max_claim_ttl',
- default=43200,
- help='The maximum ttl for a claim'),
- cfg.IntOpt('max_claim_grace',
- default=43200,
- help='The maximum grace period for a claim'),
-]
-
validation_group = cfg.OptGroup(name='validation',
title='SSH Validation options')
@@ -788,7 +749,11 @@
default=True,
help='Update bootable status of a volume '
'Not implemented on icehouse ',
- deprecated_for_removal=True)
+ deprecated_for_removal=True),
+ # TODO(ynesenenko): Remove volume_services once liberty-eol happens.
+ cfg.BoolOpt('volume_services',
+ default=False,
+ help='Extract correct host info from host@backend')
]
@@ -997,7 +962,7 @@
DataProcessingFeaturesGroup = [
cfg.ListOpt('plugins',
- default=["vanilla", "hdp"],
+ default=["vanilla", "cdh"],
deprecated_group="data_processing-feature-enabled",
help="List of enabled data processing plugins")
]
@@ -1071,11 +1036,6 @@
default='cirros-0.3.1-x86_64-vmlinuz',
help='AKI image file name',
deprecated_for_removal=True),
- cfg.IntOpt(
- 'large_ops_number',
- default=0,
- help="specifies how many resources to request at once. Used "
- "for large operations testing."),
# TODO(yfried): add support for dhcpcd
cfg.StrOpt('dhcp_client',
default='udhcpc',
@@ -1126,9 +1086,6 @@
cfg.BoolOpt('trove',
default=False,
help="Whether or not Trove is expected to be available"),
- cfg.BoolOpt('zaqar',
- default=False,
- help="Whether or not Zaqar is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@@ -1255,7 +1212,6 @@
(image_feature_group, ImageFeaturesGroup),
(network_group, NetworkGroup),
(network_feature_group, NetworkFeaturesGroup),
- (messaging_group, MessagingGroup),
(validation_group, ValidationGroup),
(volume_group, VolumeGroup),
(volume_feature_group, VolumeFeaturesGroup),
@@ -1296,7 +1252,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
@@ -1331,7 +1290,6 @@
'object-storage-feature-enabled']
self.database = _CONF.database
self.orchestration = _CONF.orchestration
- self.messaging = _CONF.messaging
self.telemetry = _CONF.telemetry
self.telemetry_feature_enabled = _CONF['telemetry-feature-enabled']
self.dashboard = _CONF.dashboard
@@ -1384,7 +1342,8 @@
_CONF([], project='tempest')
logging_cfg_path = "%s/logging.conf" % os.path.dirname(path)
- if (not hasattr(_CONF, 'log_config_append') and
+ if ((not hasattr(_CONF, 'log_config_append') or
+ _CONF.log_config_append is None) and
os.path.isfile(logging_cfg_path)):
# if logging conf is in place we need to set log_config_append
_CONF.log_config_append = logging_cfg_path
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 88598de..c666c96 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -20,7 +20,7 @@
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
'trove', 'ironic', 'savanna', 'heat', 'ceilometer',
- 'zaqar', 'sahara']
+ 'sahara']
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
@@ -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/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
index f1f21d1..5d3fc93 100644
--- a/tempest/hacking/ignored_list_T110.txt
+++ b/tempest/hacking/ignored_list_T110.txt
@@ -1,4 +1,3 @@
-./tempest/services/messaging/json/messaging_client.py
./tempest/services/object_storage/object_client.py
./tempest/services/telemetry/json/alarming_client.py
./tempest/services/telemetry/json/telemetry_client.py
@@ -6,3 +5,4 @@
./tempest/services/volume/base/base_backups_client.py
./tempest/services/baremetal/base.py
./tempest/services/network/json/network_client.py
+./tempest/services/network/json/routers_client.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/__init__.py
similarity index 100%
rename from tempest/api/messaging/__init__.py
rename to tempest/lib/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/api_schema/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/api_schema/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/api_schema/response/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/api_schema/response/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/api_schema/response/compute/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/api_schema/response/compute/__init__.py
diff --git a/tempest/api_schema/response/messaging/v1/__init__.py b/tempest/lib/api_schema/response/compute/v2_1/__init__.py
similarity index 100%
rename from tempest/api_schema/response/messaging/v1/__init__.py
rename to 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..3289f04
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -0,0 +1,554 @@
+# 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', 'null']},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': ['integer', 'string']}
+ },
+ 'additionalProperties': False,
+ # 'device' is optional in response.
+ 'required': ['id', '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..2d20a0b
--- /dev/null
+++ b/tempest/lib/auth.py
@@ -0,0 +1,700 @@
+# 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: %s, region: %s, endpoint_type: %s" %
+ (service, region, endpoint_type))
+
+ parts = urlparse.urlparse(_base_url)
+ if filters.get('api_version', None) is not None:
+ version_path = '/%s' % filters['api_version']
+ path = re.sub(r'(^|/)+v\d+(?:\.\d+)?',
+ version_path,
+ parts.path,
+ count=1)
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ path or version_path,
+ parts.params,
+ parts.query,
+ parts.fragment))
+ if filters.get('skip_path', None) is not None and parts.path != '':
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ '/',
+ parts.params,
+ parts.query,
+ parts.fragment))
+
+ 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:
+ version_path = '/%s' % filters['api_version']
+ path = re.sub(r'(^|/)+v\d+(?:\.\d+)?',
+ version_path,
+ parts.path,
+ count=1)
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ path or version_path,
+ parts.params,
+ parts.query,
+ parts.fragment))
+ if filters.get('skip_path', None) is not None:
+ _base_url = urlparse.urlunparse((parts.scheme,
+ parts.netloc,
+ '/',
+ parts.params,
+ parts.query,
+ parts.fragment))
+
+ 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/api/messaging/__init__.py b/tempest/lib/cli/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/lib/cmd/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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..be3aa49
--- /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 isinstance(node, ast.Import):
+ return node.names[0].name
+ elif isinstance(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/api/messaging/__init__.py b/tempest/lib/common/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/lib/common/utils/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/lib/services/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/services/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/services/compute/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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..ed82c74
--- /dev/null
+++ b/tempest/lib/services/compute/versions_client.py
@@ -0,0 +1,60 @@
+# 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.
+
+import re
+
+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 got from keystone's catalog contains
+ # API version and project-id like "/app-name/v2/{project-id}" or
+ # "/v2/{project-id}", but we need to access the URL which doesn't
+ # contain API version for getting API versions. For that, here
+ # should use raw_request() instead of get().
+ endpoint = self.base_url
+ url = urllib.parse.urlsplit(endpoint)
+ new_path = re.split(r'(^|/)+v\d+(\.\d+)?', url.path)[0]
+ url = list(url)
+ url[2] = new_path + '/'
+ return urllib.parse.urlunsplit(url)
+
+ 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/api/messaging/__init__.py b/tempest/lib/services/identity/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/lib/services/identity/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/lib/services/identity/v2/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/lib/services/identity/v3/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/lib/services/network/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/services/network/json/base.py b/tempest/lib/services/network/base.py
similarity index 85%
rename from tempest/services/network/json/base.py
rename to tempest/lib/services/network/base.py
index 6ebc245..a6ada04 100644
--- a/tempest/services/network/json/base.py
+++ b/tempest/lib/services/network/base.py
@@ -13,10 +13,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseNetworkClient(service_client.ServiceClient):
+class BaseNetworkClient(rest_client.RestClient):
"""Base class for Tempest REST clients for Neutron.
@@ -34,13 +34,13 @@
resp, body = self.get(req_uri)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ 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 service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_resource(self, uri, **fields):
# fields is a dict which key is 'fields' and value is a
@@ -52,7 +52,7 @@
resp, body = self.get(req_uri)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_resource(self, uri, post_data):
req_uri = self.uri_prefix + uri
@@ -60,7 +60,7 @@
resp, body = self.post(req_uri, req_post_data)
body = json.loads(body)
self.expected_success(201, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_resource(self, uri, post_data):
req_uri = self.uri_prefix + uri
@@ -68,4 +68,4 @@
resp, body = self.put(req_uri, req_post_data)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ 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/services/network/json/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
similarity index 96%
rename from tempest/services/network/json/subnetpools_client.py
rename to tempest/lib/services/network/subnetpools_client.py
index f921bb0..12349b1 100644
--- a/tempest/services/network/json/subnetpools_client.py
+++ b/tempest/lib/services/network/subnetpools_client.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class SubnetpoolsClient(base.BaseNetworkClient):
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 9904aa6..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
@@ -50,8 +49,6 @@
creds = self.credentials
# Creates an auth provider for the credentials
self.auth_provider = get_auth_provider(creds, pre_auth=True)
- # FIXME(andreaf) unused
- self.client_attr_names = []
def get_auth_provider_class(credentials):
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 1962286..0c16056 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
@@ -66,6 +66,7 @@
cls.network_client = cls.manager.network_client
cls.networks_client = cls.manager.networks_client
cls.ports_client = cls.manager.ports_client
+ cls.routers_client = cls.manager.routers_client
cls.subnets_client = cls.manager.subnets_client
cls.floating_ips_client = cls.manager.floating_ips_client
cls.security_groups_client = cls.manager.security_groups_client
@@ -690,17 +691,21 @@
cls.tenant_id = cls.manager.identity_client.tenant_id
def _create_network(self, client=None, networks_client=None,
- tenant_id=None, namestart='network-smoke-'):
+ routers_client=None, tenant_id=None,
+ namestart='network-smoke-'):
if not client:
client = self.network_client
if not networks_client:
networks_client = self.networks_client
+ if not routers_client:
+ routers_client = self.routers_client
if not tenant_id:
tenant_id = client.tenant_id
name = data_utils.rand_name(namestart)
result = networks_client.create_network(name=name, tenant_id=tenant_id)
network = net_resources.DeletableNetwork(
- networks_client=networks_client, **result['network'])
+ networks_client=networks_client, routers_client=routers_client,
+ **result['network'])
self.assertEqual(network.name, name)
self.addCleanup(self.delete_wrapper, network.delete)
return network
@@ -719,7 +724,7 @@
def _list_routers(self, *args, **kwargs):
"""List routers using admin creds """
- routers_list = self.admin_manager.network_client.list_routers(
+ routers_list = self.admin_manager.routers_client.list_routers(
*args, **kwargs)
return routers_list['routers']
@@ -736,7 +741,8 @@
return agents_list['agents']
def _create_subnet(self, network, client=None, subnets_client=None,
- namestart='subnet-smoke', **kwargs):
+ routers_client=None, namestart='subnet-smoke',
+ **kwargs):
"""Create a subnet for the given network
within the cidr block configured for tenant networks.
@@ -745,6 +751,8 @@
client = self.network_client
if not subnets_client:
subnets_client = self.subnets_client
+ if not routers_client:
+ routers_client = self.routers_client
def cidr_in_use(cidr, tenant_id):
"""Check cidr existence
@@ -792,7 +800,7 @@
self.assertIsNotNone(result, 'Unable to allocate tenant network')
subnet = net_resources.DeletableSubnet(
network_client=client, subnets_client=subnets_client,
- **result['subnet'])
+ routers_client=routers_client, **result['subnet'])
self.assertEqual(subnet.cidr, str_cidr)
self.addCleanup(self.delete_wrapper, subnet.delete)
return subnet
@@ -813,15 +821,18 @@
return port
def _get_server_port_id_and_ip4(self, server, ip_addr=None):
- ports = self._list_ports(device_id=server['id'], status='ACTIVE',
- fixed_ip=ip_addr)
+ ports = self._list_ports(device_id=server['id'], fixed_ip=ip_addr)
# A port can have more then one IP address in some cases.
# If the network is dual-stack (IPv4 + IPv6), this port is associated
# with 2 subnets
port_map = [(p["id"], fxip["ip_address"])
for p in ports
for fxip in p["fixed_ips"]
- if netaddr.valid_ipv4(fxip["ip_address"])]
+ if netaddr.valid_ipv4(fxip["ip_address"])
+ and p['status'] == 'ACTIVE']
+ inactive = [p for p in ports if p['status'] != 'ACTIVE']
+ if inactive:
+ LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
self.assertNotEqual(0, len(port_map),
"No IPv4 addresses found in: %s" % ports)
@@ -989,7 +1000,7 @@
sg_dict['tenant_id'] = tenant_id
result = client.create_security_group(**sg_dict)
secgroup = net_resources.DeletableSecurityGroup(
- client=client,
+ client=client, routers_client=self.routers_client,
**result['security_group']
)
self.assertEqual(secgroup.name, sg_name)
@@ -1125,7 +1136,7 @@
routes traffic to the public network.
"""
if not client:
- client = self.network_client
+ client = self.routers_client
if not tenant_id:
tenant_id = client.tenant_id
router_id = CONF.network.public_router_id
@@ -1144,14 +1155,14 @@
def _create_router(self, client=None, tenant_id=None,
namestart='router-smoke'):
if not client:
- client = self.network_client
+ client = self.routers_client
if not tenant_id:
tenant_id = client.tenant_id
name = data_utils.rand_name(namestart)
result = client.create_router(name=name,
admin_state_up=True,
tenant_id=tenant_id)
- router = net_resources.DeletableRouter(client=client,
+ router = net_resources.DeletableRouter(routers_client=client,
**result['router'])
self.assertEqual(router.name, name)
self.addCleanup(self.delete_wrapper, router.delete)
@@ -1162,8 +1173,8 @@
self.assertEqual(admin_state_up, router.admin_state_up)
def create_networks(self, client=None, networks_client=None,
- subnets_client=None, tenant_id=None,
- dns_nameservers=None):
+ routers_client=None, subnets_client=None,
+ tenant_id=None, dns_nameservers=None):
"""Create a network with a subnet connected to a router.
The baremetal driver is a special case since all nodes are
@@ -1191,10 +1202,12 @@
network = self._create_network(
client=client, networks_client=networks_client,
tenant_id=tenant_id)
- router = self._get_router(client=client, tenant_id=tenant_id)
+ router = self._get_router(client=routers_client,
+ tenant_id=tenant_id)
subnet_kwargs = dict(network=network, client=client,
- subnets_client=subnets_client)
+ subnets_client=subnets_client,
+ routers_client=routers_client)
# use explicit check because empty list is a valid option
if dns_nameservers is not None:
subnet_kwargs['dns_nameservers'] = dns_nameservers
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index 15d9b66..655d19d 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -15,7 +15,6 @@
from oslo_log import log as logging
-from tempest.common import waiters
from tempest import config
from tempest.scenario import manager
from tempest import test
@@ -37,32 +36,10 @@
* Verifies SSH connectivity using created keypair via fixed IP
* Associates a floating ip
* Verifies SSH connectivity using created keypair via floating IP
- * Verifies instance rebuild with ephemeral partition preservation
* Deletes instance
* Monitors the associated Ironic node for power and
expected state transitions
"""
- def rebuild_instance(self, preserve_ephemeral=False):
- self.rebuild_server(server_id=self.instance['id'],
- preserve_ephemeral=preserve_ephemeral,
- wait=False)
-
- node = self.get_node(instance_id=self.instance['id'])
-
- # We should remain on the same node
- self.assertEqual(self.node['uuid'], node['uuid'])
- self.node = node
-
- waiters.wait_for_server_status(
- self.servers_client,
- server_id=self.instance['id'],
- status='REBUILD',
- ready_wait=False)
- waiters.wait_for_server_status(
- self.servers_client,
- server_id=self.instance['id'],
- status='ACTIVE')
-
def verify_partition(self, client, label, mount, gib_size):
"""Verify a labeled partition's mount point and size."""
LOG.info("Looking for partition %s mounted on %s" % (label, mount))
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
deleted file mode 100644
index 402077f..0000000
--- a/tempest/scenario/test_large_ops.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# 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 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.scenario import manager
-from tempest import test
-
-CONF = config.CONF
-
-
-class TestLargeOpsScenario(manager.ScenarioTest):
-
- """Test large operations.
-
- This test below:
- * Spin up multiple instances in one nova call, and repeat three times
- * as a regular user
- * TODO: same thing for cinder
-
- """
-
- @classmethod
- def skip_checks(cls):
- super(TestLargeOpsScenario, cls).skip_checks()
- if CONF.scenario.large_ops_number < 1:
- raise cls.skipException("large_ops_number not set to multiple "
- "instances")
-
- @classmethod
- def setup_credentials(cls):
- cls.set_network_resources()
- super(TestLargeOpsScenario, cls).setup_credentials()
-
- @classmethod
- def resource_setup(cls):
- super(TestLargeOpsScenario, cls).resource_setup()
- # list of cleanup calls to be executed in reverse order
- cls._cleanup_resources = []
-
- @classmethod
- def resource_cleanup(cls):
- while cls._cleanup_resources:
- function, args, kwargs = cls._cleanup_resources.pop(-1)
- try:
- function(*args, **kwargs)
- except lib_exc.NotFound:
- pass
- super(TestLargeOpsScenario, cls).resource_cleanup()
-
- @classmethod
- def addCleanupClass(cls, function, *arguments, **keywordArguments):
- cls._cleanup_resources.append((function, arguments, keywordArguments))
-
- def _wait_for_server_status(self, status):
- for server in self.servers:
- # Make sure nova list keeps working throughout the build process
- self.servers_client.list_servers()
- waiters.wait_for_server_status(self.servers_client,
- server['id'], status)
-
- def nova_boot(self, image):
- name = data_utils.rand_name('scenario-server')
- flavor_id = CONF.compute.flavor_ref
- # Explicitly create secgroup to avoid cleanup at the end of testcases.
- # Since no traffic is tested, we don't need to actually add rules to
- # secgroup
- secgroup = self.compute_security_groups_client.create_security_group(
- name='secgroup-%s' % name,
- description='secgroup-desc-%s' % name)['security_group']
- self.addCleanupClass(
- self.compute_security_groups_client.delete_security_group,
- secgroup['id'])
- create_kwargs = {
- 'min_count': CONF.scenario.large_ops_number,
- 'security_groups': [{'name': secgroup['name']}]
- }
- network = self.get_tenant_network()
- create_kwargs = fixed_network.set_networks_kwarg(network,
- create_kwargs)
- self.servers_client.create_server(
- name=name,
- imageRef=image,
- flavorRef=flavor_id,
- **create_kwargs)
- # needed because of bug 1199788
- params = {'name': name}
- server_list = self.servers_client.list_servers(**params)
- self.servers = server_list['servers']
- for server in self.servers:
- # after deleting all servers - wait for all servers to clear
- # before cleanup continues
- self.addCleanupClass(waiters.wait_for_server_termination,
- self.servers_client,
- server['id'])
- for server in self.servers:
- self.addCleanupClass(self.servers_client.delete_server,
- server['id'])
- self._wait_for_server_status('ACTIVE')
-
- def _large_ops_scenario(self):
- image = self.glance_image_create()
- self.nova_boot(image)
-
- @test.idempotent_id('14ba0e78-2ed9-4d17-9659-a48f4756ecb3')
- @test.services('compute', 'image')
- def test_large_ops_scenario_1(self):
- self._large_ops_scenario()
-
- @test.idempotent_id('b9b79b88-32aa-42db-8f8f-dcc8f4b4ccfe')
- @test.services('compute', 'image')
- def test_large_ops_scenario_2(self):
- self._large_ops_scenario()
-
- @test.idempotent_id('3aab7e82-2de3-419a-9da1-9f3a070668fb')
- @test.services('compute', 'image')
- def test_large_ops_scenario_3(self):
- self._large_ops_scenario()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 63c844b..9e2477e 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -626,9 +626,6 @@
"admin_state_up of instance port to True")
@test.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa')
- @testtools.skipUnless(CONF.compute_feature_enabled.preserve_ports,
- 'Preserving ports on instance delete may not be '
- 'supported in the version of Nova being tested.')
@test.services('compute', 'network')
def test_preserve_preexisting_port(self):
"""Test preserve pre-existing port
@@ -683,7 +680,7 @@
# TODO(yfried): refactor this test to be used for other agents (dhcp)
# as well
- list_hosts = (self.admin_manager.network_client.
+ list_hosts = (self.admin_manager.routers_client.
list_l3_agents_hosting_router)
schedule_router = (self.admin_manager.network_agents_client.
create_router_on_l3_agent)
@@ -696,7 +693,7 @@
# NOTE(kevinbenton): we have to use the admin credentials to check
# for the distributed flag because self.router only has a tenant view.
- admin = self.admin_manager.network_client.show_router(self.router.id)
+ admin = self.admin_manager.routers_client.show_router(self.router.id)
if admin['router'].get('distributed', False):
msg = "Rescheduling test does not apply to distributed routers."
raise self.skipException(msg)
@@ -741,6 +738,8 @@
msg='After router rescheduling')
@test.requires_ext(service='network', extension='port-security')
+ @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+ 'NIC hotplug not available')
@test.idempotent_id('7c0bb1a2-d053-49a4-98f9-ca1a1d849f63')
@test.services('compute', 'network')
def test_port_security_macspoofing_port(self):
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 18bd764..058f43b 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -12,6 +12,7 @@
# 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
from tempest import clients
from tempest.common.utils import data_utils
@@ -20,6 +21,7 @@
from tempest import test
CONF = config.CONF
+LOG = log.getLogger(__name__)
class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest):
@@ -151,6 +153,14 @@
@classmethod
def resource_setup(cls):
super(TestSecurityGroupsBasicOps, cls).resource_setup()
+
+ cls.multi_node = CONF.compute.min_compute_nodes > 1 and \
+ test.is_scheduler_filter_enabled("DifferentHostFilter")
+ if cls.multi_node:
+ LOG.info("Working in Multi Node mode")
+ else:
+ LOG.info("Working in Single Node mode")
+
cls.floating_ips = {}
cls.tenants = {}
creds = cls.manager.credentials
@@ -162,6 +172,12 @@
cls.floating_ip_access = not CONF.network.public_router_id
def setUp(self):
+ """Set up a single tenant with an accessible server.
+
+ If multi-host is enabled, save created server uuids.
+ """
+ self.servers = []
+
super(TestSecurityGroupsBasicOps, self).setUp()
self._deploy_tenant(self.primary_tenant)
self._verify_network_details(self.primary_tenant)
@@ -233,21 +249,44 @@
# and distributed routers; 'device_owner' is "" by default.
return port['device_owner'].startswith('network:router_interface')
- def _create_server(self, name, tenant, security_groups=None):
- """creates a server and assigns to security group"""
+ def _create_server(self, name, tenant, security_groups=None, **kwargs):
+ """Creates a server and assigns it to security group.
+
+ If multi-host is enabled, Ensures servers are created on different
+ compute nodes, by storing created servers' ids and uses different_host
+ as scheduler_hints on creation.
+ Validates servers are created as requested, using admin client.
+ """
if security_groups is None:
security_groups = [tenant.security_groups['default']]
security_groups_names = [{'name': s['name']} for s in security_groups]
+ if self.multi_node:
+ kwargs["scheduler_hints"] = {'different_host': self.servers}
server = self.create_server(
name=name,
networks=[{'uuid': tenant.network.id}],
key_name=tenant.keypair['name'],
security_groups=security_groups_names,
wait_until='ACTIVE',
- clients=tenant.manager)
+ clients=tenant.manager,
+ **kwargs)
self.assertEqual(
sorted([s['name'] for s in security_groups]),
sorted([s['name'] for s in server['security_groups']]))
+
+ # Verify servers are on different compute nodes
+ if self.multi_node:
+ adm_get_server = self.admin_manager.servers_client.show_server
+ new_host = adm_get_server(server["id"])["server"][
+ "OS-EXT-SRV-ATTR:host"]
+ host_list = [adm_get_server(s)["server"]["OS-EXT-SRV-ATTR:host"]
+ for s in self.servers]
+ self.assertNotIn(new_host, host_list,
+ message="Failed to boot servers on different "
+ "Compute nodes.")
+
+ self.servers.append(server["id"])
+
return server
def _create_tenant_servers(self, tenant, num=1):
@@ -284,6 +323,7 @@
network, subnet, router = self.create_networks(
client=tenant.manager.network_client,
networks_client=tenant.manager.networks_client,
+ routers_client=tenant.manager.routers_client,
subnets_client=tenant.manager.subnets_client)
tenant.set_network(network, subnet, router)
@@ -348,6 +388,7 @@
)
self._create_security_group_rule(
secgroup=tenant.security_groups['default'],
+ security_groups_client=tenant.manager.security_groups_client,
**ruleset
)
access_point_ssh = self._connect_to_access_point(tenant)
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/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 4ce57db..71bb50e 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log as logging
+
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
@@ -17,6 +19,7 @@
from tempest import test
CONF = config.CONF
+LOG = logging.getLogger(__name__)
class TestVolumeBootPattern(manager.ScenarioTest):
@@ -32,6 +35,11 @@
* Boot an additional instance from the new snapshot based volume
* Check written content in the instance booted from snapshot
"""
+
+ # Boot from volume scenario is quite slow, and needs extra
+ # breathing room to get through deletes in the time allotted.
+ TIMEOUT_SCALING_FACTOR = 2
+
@classmethod
def skip_checks(cls):
super(TestVolumeBootPattern, cls).skip_checks()
@@ -101,42 +109,53 @@
@test.attr(type='smoke')
@test.services('compute', 'volume', 'image')
def test_volume_boot_pattern(self):
+ LOG.info("Creating keypair and security group")
keypair = self.create_keypair()
security_group = self._create_security_group()
# create an instance from volume
+ LOG.info("Booting instance 1 from volume")
volume_origin = self._create_volume_from_image()
instance_1st = self._boot_instance_from_volume(volume_origin['id'],
keypair, security_group)
+ LOG.info("Booted first instance: %s" % instance_1st)
# write content to volume on instance
+ LOG.info("Setting timestamp in instance %s" % instance_1st)
ip_instance_1st = self.get_server_ip(instance_1st)
timestamp = self.create_timestamp(ip_instance_1st,
private_key=keypair['private_key'])
# delete instance
+ LOG.info("Deleting first instance: %s" % instance_1st)
self._delete_server(instance_1st)
# create a 2nd instance from volume
instance_2nd = self._boot_instance_from_volume(volume_origin['id'],
keypair, security_group)
+ LOG.info("Booted second instance %s" % instance_2nd)
# check the content of written file
+ LOG.info("Getting timestamp in instance %s" % instance_2nd)
ip_instance_2nd = self.get_server_ip(instance_2nd)
timestamp2 = self.get_timestamp(ip_instance_2nd,
private_key=keypair['private_key'])
self.assertEqual(timestamp, timestamp2)
# snapshot a volume
+ LOG.info("Creating snapshot from volume: %s" % volume_origin['id'])
snapshot = self._create_snapshot_from_volume(volume_origin['id'])
# create a 3rd instance from snapshot
+ LOG.info("Creating third instance from snapshot: %s" % snapshot['id'])
volume = self._create_volume_from_snapshot(snapshot['id'])
server_from_snapshot = (
self._boot_instance_from_volume(volume['id'],
keypair, security_group))
# check the content of written file
+ LOG.info("Logging into third instance to get timestamp: %s" %
+ server_from_snapshot)
server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
timestamp3 = self.get_timestamp(server_from_snapshot_ip,
private_key=keypair['private_key'])
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/baremetal/base.py b/tempest/services/baremetal/base.py
index d8cb99d..6e24801 100644
--- a/tempest/services/baremetal/base.py
+++ b/tempest/services/baremetal/base.py
@@ -16,7 +16,7 @@
import six
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
def handle_errors(f):
@@ -39,7 +39,7 @@
return wrapper
-class BaremetalClient(service_client.ServiceClient):
+class BaremetalClient(rest_client.RestClient):
"""Base Tempest REST client for Ironic API."""
uri_prefix = ''
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/compute/json/keypairs_client.py b/tempest/services/compute/json/keypairs_client.py
index ec9b1e0..045f03c 100644
--- a/tempest/services/compute/json/keypairs_client.py
+++ b/tempest/services/compute/json/keypairs_client.py
@@ -17,7 +17,7 @@
from tempest.api_schema.response.compute.v2_1 import keypairs as schemav21
from tempest.api_schema.response.compute.v2_2 import keypairs as schemav22
-from tempest.common import service_client
+from tempest.lib.common import rest_client
from tempest.services.compute.json import base_compute_client
@@ -31,14 +31,14 @@
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.list_keypairs, resp, body)
- return service_client.ResponseBody(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)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.get_keypair, resp, body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_keypair(self, **kwargs):
post_body = json.dumps({'keypair': kwargs})
@@ -46,10 +46,10 @@
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.create_keypair, resp, body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_keypair(self, keypair_name):
resp, body = self.delete("os-keypairs/%s" % keypair_name)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.delete_keypair, resp, body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/data_processing/v1_1/data_processing_client.py b/tempest/services/data_processing/v1_1/data_processing_client.py
index 5aa2622..c74672f 100644
--- a/tempest/services/data_processing/v1_1/data_processing_client.py
+++ b/tempest/services/data_processing/v1_1/data_processing_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DataProcessingClient(service_client.ServiceClient):
+class DataProcessingClient(rest_client.RestClient):
def _request_and_check_resp(self, request_func, uri, resp_status):
"""Make a request and check response status code.
@@ -26,7 +26,7 @@
"""
resp, body = request_func(uri)
self.expected_success(resp_status, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _request_and_check_resp_data(self, request_func, uri, resp_status):
"""Make a request and check response status code.
@@ -47,7 +47,7 @@
resp, body = request_func(uri, headers=headers, *args, **kwargs)
self.expected_success(resp_status, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_node_group_templates(self):
"""List all node group templates for a user."""
diff --git a/tempest/services/database/json/flavors_client.py b/tempest/services/database/json/flavors_client.py
index dbb5172..bd8ffb0 100644
--- a/tempest/services/database/json/flavors_client.py
+++ b/tempest/services/database/json/flavors_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves import urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DatabaseFlavorsClient(service_client.ServiceClient):
+class DatabaseFlavorsClient(rest_client.RestClient):
def list_db_flavors(self, params=None):
url = 'flavors'
@@ -29,10 +29,10 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_db_flavor(self, db_flavor_id):
resp, body = self.get("flavors/%s" % db_flavor_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/database/json/limits_client.py b/tempest/services/database/json/limits_client.py
index da495d7..a1c58c2 100644
--- a/tempest/services/database/json/limits_client.py
+++ b/tempest/services/database/json/limits_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DatabaseLimitsClient(service_client.ServiceClient):
+class DatabaseLimitsClient(rest_client.RestClient):
def list_db_limits(self, params=None):
"""List all limits."""
@@ -29,4 +29,4 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/database/json/versions_client.py b/tempest/services/database/json/versions_client.py
index 7a560d9..2f28203 100644
--- a/tempest/services/database/json/versions_client.py
+++ b/tempest/services/database/json/versions_client.py
@@ -16,24 +16,14 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class DatabaseVersionsClient(service_client.ServiceClient):
+class DatabaseVersionsClient(rest_client.RestClient):
- def __init__(self, auth_provider, service, region,
- endpoint_type=None, build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
- dscv = disable_ssl_certificate_validation
+ def __init__(self, auth_provider, service, region, **kwargs):
super(DatabaseVersionsClient, self).__init__(
- auth_provider, service, region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, service, region, **kwargs)
self.skip_path()
def list_db_versions(self, params=None):
@@ -45,4 +35,4 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/endpoints_client.py b/tempest/services/identity/v2/json/endpoints_client.py
index ff9907d..ba9f867 100644
--- a/tempest/services/identity/v2/json/endpoints_client.py
+++ b/tempest/services/identity/v2/json/endpoints_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class EndpointsClient(service_client.ServiceClient):
+class EndpointsClient(rest_client.RestClient):
api_version = "v2.0"
def create_endpoint(self, service_id, region_id, **kwargs):
@@ -33,18 +33,18 @@
resp, body = self.post('/endpoints', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_endpoints(self):
"""List Endpoints - Returns Endpoints."""
resp, body = self.get('/endpoints')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_endpoint(self, endpoint_id):
"""Delete an endpoint."""
url = '/endpoints/%s' % endpoint_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/identity_client.py b/tempest/services/identity/v2/json/identity_client.py
index f045bb7..6caff0e 100644
--- a/tempest/services/identity/v2/json/identity_client.py
+++ b/tempest/services/identity/v2/json/identity_client.py
@@ -12,10 +12,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class IdentityClient(service_client.ServiceClient):
+class IdentityClient(rest_client.RestClient):
api_version = "v2.0"
def show_api_description(self):
@@ -24,24 +24,24 @@
resp, body = self.get(url)
self.expected_success([200, 203], resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_token(self, token_id):
"""Get token details."""
resp, body = self.get("tokens/%s" % token_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_token(self, token_id):
"""Delete a token."""
resp, body = self.delete("tokens/%s" % token_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_extensions(self):
"""List all the extensions."""
resp, body = self.get('/extensions')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/roles_client.py b/tempest/services/identity/v2/json/roles_client.py
index ef6dfe9..acd97c6 100644
--- a/tempest/services/identity/v2/json/roles_client.py
+++ b/tempest/services/identity/v2/json/roles_client.py
@@ -12,10 +12,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class RolesClient(service_client.ServiceClient):
+class RolesClient(rest_client.RestClient):
api_version = "v2.0"
def create_role(self, **kwargs):
@@ -28,14 +28,14 @@
resp, body = self.post('OS-KSADM/roles', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_role(self, role_id):
"""Get a role by its id."""
resp, body = self.get('OS-KSADM/roles/%s' % role_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_role(self, role_id):
"""Delete a role."""
@@ -49,7 +49,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def assign_user_role(self, tenant_id, user_id, role_id):
"""Add roles to a user on a tenant."""
@@ -57,18 +57,18 @@
(tenant_id, user_id, role_id), "")
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_user_role(self, tenant_id, user_id, role_id):
"""Removes a role assignment for a user on a tenant."""
resp, body = self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_roles(self):
"""Returns roles."""
resp, body = self.get('OS-KSADM/roles')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/services_client.py b/tempest/services/identity/v2/json/services_client.py
index 436d00d..d8be6c6 100644
--- a/tempest/services/identity/v2/json/services_client.py
+++ b/tempest/services/identity/v2/json/services_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ServicesClient(service_client.ServiceClient):
+class ServicesClient(rest_client.RestClient):
api_version = "v2.0"
def create_service(self, name, type, **kwargs):
@@ -31,7 +31,7 @@
resp, body = self.post('/OS-KSADM/services', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_service(self, service_id):
"""Get Service."""
@@ -39,18 +39,18 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_services(self):
"""List Service - Returns Services."""
resp, body = self.get('/OS-KSADM/services')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_service(self, service_id):
"""Delete Service."""
url = '/OS-KSADM/services/%s' % service_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/tenants_client.py b/tempest/services/identity/v2/json/tenants_client.py
index 937ae6f..034938e 100644
--- a/tempest/services/identity/v2/json/tenants_client.py
+++ b/tempest/services/identity/v2/json/tenants_client.py
@@ -14,10 +14,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class TenantsClient(service_client.ServiceClient):
+class TenantsClient(rest_client.RestClient):
api_version = "v2.0"
def create_tenant(self, name, **kwargs):
@@ -36,27 +36,27 @@
resp, body = self.post('tenants', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_tenant(self, tenant_id):
"""Delete a tenant."""
resp, body = self.delete('tenants/%s' % str(tenant_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_tenant(self, tenant_id):
"""Get tenant details."""
resp, body = self.get('tenants/%s' % str(tenant_id))
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_tenants(self):
"""Returns tenants."""
resp, body = self.get('tenants')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_tenant(self, tenant_id, **kwargs):
"""Updates a tenant."""
@@ -74,11 +74,11 @@
resp, body = self.post('tenants/%s' % tenant_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_tenant_users(self, tenant_id):
"""List users for a Tenant."""
resp, body = self.get('/tenants/%s/users' % tenant_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/users_client.py b/tempest/services/identity/v2/json/users_client.py
index 5327638..5f8127f 100644
--- a/tempest/services/identity/v2/json/users_client.py
+++ b/tempest/services/identity/v2/json/users_client.py
@@ -12,10 +12,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class UsersClient(service_client.ServiceClient):
+class UsersClient(rest_client.RestClient):
api_version = "v2.0"
def create_user(self, name, password, tenant_id, email, **kwargs):
@@ -33,7 +33,7 @@
resp, body = self.post('users', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_user(self, user_id, **kwargs):
"""Updates a user."""
@@ -41,27 +41,27 @@
resp, body = self.put('users/%s' % user_id, put_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_user(self, user_id):
"""Delete a user."""
resp, body = self.delete("users/%s" % user_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_users(self):
"""Get the list of users."""
resp, body = self.get("users")
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def enable_disable_user(self, user_id, **kwargs):
"""Enables or disables a user.
@@ -77,7 +77,7 @@
resp, body = self.put('users/%s/enabled' % user_id, put_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_user_password(self, user_id, **kwargs):
"""Update User Password."""
@@ -89,7 +89,7 @@
resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_user_own_password(self, user_id, **kwargs):
"""User updates own password"""
@@ -104,7 +104,7 @@
resp, body = self.patch('OS-KSCRUD/users/%s' % user_id, patch_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_user_ec2_credentials(self, user_id, **kwargs):
# TODO(piyush): Current api-site doesn't contain this API description.
@@ -115,23 +115,23 @@
post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_user_ec2_credentials(self, user_id, access):
resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' %
(user_id, access))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_user_ec2_credentials(self, user_id):
resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_user_ec2_credentials(self, user_id, access):
resp, body = self.get('/users/%s/credentials/OS-EC2/%s' %
(user_id, access))
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/credentials_client.py b/tempest/services/identity/v3/json/credentials_client.py
index 753e960..6ab94d0 100644
--- a/tempest/services/identity/v3/json/credentials_client.py
+++ b/tempest/services/identity/v3/json/credentials_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class CredentialsClient(service_client.ServiceClient):
+class CredentialsClient(rest_client.RestClient):
api_version = "v3"
def create_credential(self, **kwargs):
@@ -36,7 +36,7 @@
self.expected_success(201, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_credential(self, credential_id, **kwargs):
"""Updates a credential.
@@ -49,7 +49,7 @@
self.expected_success(200, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_credential(self, credential_id):
"""To GET Details of a credential."""
@@ -57,17 +57,17 @@
self.expected_success(200, resp.status)
body = json.loads(body)
body['credential']['blob'] = json.loads(body['credential']['blob'])
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_credentials(self):
"""Lists out all the available credentials."""
resp, body = self.get('credentials')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_credential(self, credential_id):
"""Deletes a credential."""
resp, body = self.delete('credentials/%s' % credential_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
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..d129a0a
--- /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.lib.common import rest_client
+
+
+class DomainsClient(rest_client.RestClient):
+ 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 rest_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 rest_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 rest_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 rest_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 rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
index 8ab7464..db30508 100644
--- a/tempest/services/identity/v3/json/endpoints_client.py
+++ b/tempest/services/identity/v3/json/endpoints_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class EndPointClient(service_client.ServiceClient):
+class EndPointsClient(rest_client.RestClient):
api_version = "v3"
def list_endpoints(self):
@@ -30,7 +30,7 @@
resp, body = self.get('endpoints')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_endpoint(self, **kwargs):
"""Create endpoint.
@@ -42,7 +42,7 @@
resp, body = self.post('endpoints', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_endpoint(self, endpoint_id, **kwargs):
"""Updates an endpoint with given parameters.
@@ -54,17 +54,17 @@
resp, body = self.patch('endpoints/%s' % endpoint_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_endpoint(self, endpoint_id):
"""Delete endpoint."""
resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
self.expected_success(204, resp_header.status)
- return service_client.ResponseBody(resp_header, resp_body)
+ return rest_client.ResponseBody(resp_header, resp_body)
def show_endpoint(self, endpoint_id):
"""Get endpoint."""
resp_header, resp_body = self.get('endpoints/%s' % endpoint_id)
self.expected_success(200, resp_header.status)
resp_body = json.loads(resp_body)
- return service_client.ResponseBody(resp_header, resp_body)
+ return rest_client.ResponseBody(resp_header, resp_body)
diff --git a/tempest/services/identity/v3/json/groups_client.py b/tempest/services/identity/v3/json/groups_client.py
index 6ed85cf..1a495f8 100644
--- a/tempest/services/identity/v3/json/groups_client.py
+++ b/tempest/services/identity/v3/json/groups_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class GroupsClient(service_client.ServiceClient):
+class GroupsClient(rest_client.RestClient):
api_version = "v3"
def create_group(self, **kwargs):
@@ -35,21 +35,21 @@
resp, body = self.post('groups', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_group(self, group_id):
"""Get group details."""
resp, body = self.get('groups/%s' % group_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_groups(self):
"""Lists the groups."""
resp, body = self.get('groups')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_group(self, group_id, **kwargs):
"""Updates a group.
@@ -61,36 +61,36 @@
resp, body = self.patch('groups/%s' % group_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_group(self, group_id):
"""Delete a group."""
resp, body = self.delete('groups/%s' % str(group_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def add_group_user(self, group_id, user_id):
"""Add user into group."""
resp, body = self.put('groups/%s/users/%s' % (group_id, user_id),
None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_group_users(self, group_id):
"""List users in group."""
resp, body = self.get('groups/%s/users' % group_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_group_user(self, group_id, user_id):
"""Delete user in group."""
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def check_group_user_existence(self, group_id, user_id):
"""Check user in group."""
resp, body = self.head('groups/%s/users/%s' % (group_id, user_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 252fad7..8177e35 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -14,12 +14,11 @@
# under the License.
from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class IdentityV3Client(service_client.ServiceClient):
+class IdentityClient(rest_client.RestClient):
api_version = "v3"
def show_api_description(self):
@@ -28,205 +27,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def create_user(self, user_name, password=None, project_id=None,
- email=None, domain_id='default', **kwargs):
- """Creates a user."""
- en = kwargs.get('enabled', True)
- description = kwargs.get('description', None)
- default_project_id = kwargs.get('default_project_id')
- post_body = {
- 'project_id': project_id,
- 'default_project_id': default_project_id,
- 'description': description,
- 'domain_id': domain_id,
- 'email': email,
- 'enabled': en,
- 'name': user_name,
- 'password': password
- }
- post_body = json.dumps({'user': post_body})
- resp, body = self.post('users', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_user(self, user_id, name, **kwargs):
- """Updates a user."""
- body = self.show_user(user_id)['user']
- email = kwargs.get('email', body['email'])
- en = kwargs.get('enabled', body['enabled'])
- project_id = kwargs.get('project_id', body['project_id'])
- if 'default_project_id' in body.keys():
- default_project_id = kwargs.get('default_project_id',
- body['default_project_id'])
- else:
- default_project_id = kwargs.get('default_project_id')
- description = kwargs.get('description', body['description'])
- domain_id = kwargs.get('domain_id', body['domain_id'])
- post_body = {
- 'name': name,
- 'email': email,
- 'enabled': en,
- 'project_id': project_id,
- 'default_project_id': default_project_id,
- 'id': user_id,
- 'domain_id': domain_id,
- 'description': description
- }
- post_body = json.dumps({'user': post_body})
- resp, body = self.patch('users/%s' % user_id, post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_user_password(self, user_id, **kwargs):
- """Update a user password
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#changeUserPassword
- """
- update_user = json.dumps({'user': kwargs})
- resp, _ = self.post('users/%s/password' % user_id, update_user)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def list_user_projects(self, user_id):
- """Lists the projects on which a user has roles assigned."""
- resp, body = self.get('users/%s/projects' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_users(self, params=None):
- """Get the list of users."""
- url = 'users'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_user(self, user_id):
- """GET a user."""
- resp, body = self.get("users/%s" % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_user(self, user_id):
- """Deletes a User."""
- resp, body = self.delete("users/%s" % user_id)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def create_role(self, **kwargs):
- """Create a Role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#createRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.post('roles', post_body)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def show_role(self, role_id):
- """GET a Role."""
- resp, body = self.get('roles/%s' % str(role_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_roles(self):
- """Get the list of Roles."""
- resp, body = self.get("roles")
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def update_role(self, role_id, **kwargs):
- """Update a Role.
-
- Available params: see http://developer.openstack.org/
- api-ref-identity-v3.html#updateRole
- """
- post_body = json.dumps({'role': kwargs})
- resp, body = self.patch('roles/%s' % str(role_id), post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_role(self, role_id):
- """Delete a role."""
- resp, body = self.delete('roles/%s' % str(role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def assign_user_role(self, project_id, user_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id), None)
- 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)
+ return rest_client.ResponseBody(resp, body)
def show_token(self, resp_token):
"""Get token details."""
@@ -234,199 +35,11 @@
resp, body = self.get("auth/tokens", headers=headers)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_token(self, resp_token):
"""Deletes token."""
headers = {'X-Subject-Token': resp_token}
resp, body = self.delete("auth/tokens", headers=headers)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_user_groups(self, user_id):
- """Lists groups which a user belongs to."""
- resp, body = self.get('users/%s/groups' % user_id)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def assign_user_role_on_project(self, project_id, user_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def assign_user_role_on_domain(self, domain_id, user_id, role_id):
- """Add roles to a user on a domain."""
- resp, body = self.put('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_user_roles_on_project(self, project_id, user_id):
- """list roles of a user on a project."""
- resp, body = self.get('projects/%s/users/%s/roles' %
- (project_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_user_roles_on_domain(self, domain_id, user_id):
- """list roles of a user on a domain."""
- resp, body = self.get('domains/%s/users/%s/roles' %
- (domain_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_user_on_project(self, project_id, user_id, role_id):
- """Delete role of a user on a project."""
- resp, body = self.delete('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_user_on_domain(self, domain_id, user_id, role_id):
- """Delete role of a user on a domain."""
- resp, body = self.delete('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def check_user_role_existence_on_project(self, project_id,
- user_id, role_id):
- """Check role of a user on a project."""
- resp, body = self.head('projects/%s/users/%s/roles/%s' %
- (project_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def check_user_role_existence_on_domain(self, domain_id,
- user_id, role_id):
- """Check role of a user on a domain."""
- resp, body = self.head('domains/%s/users/%s/roles/%s' %
- (domain_id, user_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def assign_group_role_on_project(self, project_id, group_id, role_id):
- """Add roles to a user on a project."""
- resp, body = self.put('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def assign_group_role_on_domain(self, domain_id, group_id, role_id):
- """Add roles to a user on a domain."""
- resp, body = self.put('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id), None)
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def list_group_roles_on_project(self, project_id, group_id):
- """list roles of a user on a project."""
- resp, body = self.get('projects/%s/groups/%s/roles' %
- (project_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def list_group_roles_on_domain(self, domain_id, group_id):
- """list roles of a user on a domain."""
- resp, body = self.get('domains/%s/groups/%s/roles' %
- (domain_id, group_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_group_on_project(self, project_id, group_id, role_id):
- """Delete role of a user on a project."""
- resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
- """Delete role of a user on a domain."""
- resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
- (domain_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
-
- def check_role_from_group_on_project_existence(self, project_id,
- group_id, role_id):
- """Check role of a user on a project."""
- resp, body = self.head('projects/%s/groups/%s/roles/%s' %
- (project_id, group_id, role_id))
- self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
-
- def check_role_from_group_on_domain_existence(self, domain_id,
- group_id, role_id):
- """Check role of a user on a domain."""
- resp, body = self.head('domains/%s/groups/%s/roles/%s' %
- (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)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/policies_client.py b/tempest/services/identity/v3/json/policies_client.py
index 639ed6d..f28db9a 100644
--- a/tempest/services/identity/v3/json/policies_client.py
+++ b/tempest/services/identity/v3/json/policies_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class PoliciesClient(service_client.ServiceClient):
+class PoliciesClient(rest_client.RestClient):
api_version = "v3"
def create_policy(self, **kwargs):
@@ -35,14 +35,14 @@
resp, body = self.post('policies', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_policies(self):
"""Lists the policies."""
resp, body = self.get('policies')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_policy(self, policy_id):
"""Lists out the given policy."""
@@ -50,7 +50,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_policy(self, policy_id, **kwargs):
"""Updates a policy.
@@ -63,11 +63,11 @@
resp, body = self.patch(url, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_policy(self, policy_id):
"""Deletes the policy."""
url = "policies/%s" % policy_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/projects_client.py b/tempest/services/identity/v3/json/projects_client.py
index 2fa822f..dc553d0 100644
--- a/tempest/services/identity/v3/json/projects_client.py
+++ b/tempest/services/identity/v3/json/projects_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ProjectsClient(service_client.ServiceClient):
+class ProjectsClient(rest_client.RestClient):
api_version = "v3"
def create_project(self, name, **kwargs):
@@ -37,7 +37,7 @@
resp, body = self.post('projects', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_projects(self, params=None):
url = "projects"
@@ -46,7 +46,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_project(self, project_id, **kwargs):
body = self.show_project(project_id)['project']
@@ -65,17 +65,17 @@
resp, body = self.patch('projects/%s' % project_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_project(self, project_id):
"""GET a Project."""
resp, body = self.get("projects/%s" % project_id)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_project(self, project_id):
"""Delete a project."""
resp, body = self.delete('projects/%s' % str(project_id))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/regions_client.py b/tempest/services/identity/v3/json/regions_client.py
index bc4b7a1..90dd9d7 100644
--- a/tempest/services/identity/v3/json/regions_client.py
+++ b/tempest/services/identity/v3/json/regions_client.py
@@ -20,10 +20,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class RegionsClient(service_client.ServiceClient):
+class RegionsClient(rest_client.RestClient):
api_version = "v3"
def create_region(self, region_id=None, **kwargs):
@@ -45,7 +45,7 @@
resp, body = method(url, req_body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_region(self, region_id, **kwargs):
"""Updates a region.
@@ -57,7 +57,7 @@
resp, body = self.patch('regions/%s' % region_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_region(self, region_id):
"""Get region."""
@@ -65,7 +65,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_regions(self, params=None):
"""List regions."""
@@ -75,10 +75,10 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_region(self, region_id):
"""Delete region."""
resp, body = self.delete('regions/%s' % region_id)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/roles_client.py b/tempest/services/identity/v3/json/roles_client.py
new file mode 100644
index 0000000..bdb0490
--- /dev/null
+++ b/tempest/services/identity/v3/json/roles_client.py
@@ -0,0 +1,315 @@
+# 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.lib.common import rest_client
+
+
+class RolesClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_role(self, **kwargs):
+ """Create a Role.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#createRole
+ """
+ post_body = json.dumps({'role': kwargs})
+ resp, body = self.post('roles', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_role(self, role_id):
+ """GET a Role."""
+ resp, body = self.get('roles/%s' % str(role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_roles(self):
+ """Get the list of Roles."""
+ resp, body = self.get("roles")
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_role(self, role_id, **kwargs):
+ """Update a Role.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#updateRole
+ """
+ post_body = json.dumps({'role': kwargs})
+ resp, body = self.patch('roles/%s' % str(role_id), post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def assign_user_role_on_project(self, project_id, user_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def assign_user_role_on_domain(self, domain_id, user_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_project(self, project_id, user_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/users/%s/roles' %
+ (project_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_domain(self, domain_id, user_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/users/%s/roles' %
+ (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_user_on_project(self, project_id, user_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_user_on_domain(self, domain_id, user_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_role_existence_on_project(self, project_id,
+ user_id, role_id):
+ """Check role of a user on a project."""
+ resp, body = self.head('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_user_role_existence_on_domain(self, domain_id,
+ user_id, role_id):
+ """Check role of a user on a domain."""
+ resp, body = self.head('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_group_role_on_project(self, project_id, group_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def assign_group_role_on_domain(self, domain_id, group_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_project(self, project_id, group_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/groups/%s/roles' %
+ (project_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_domain(self, domain_id, group_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/groups/%s/roles' %
+ (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_group_on_project(self, project_id, group_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_role_from_group_on_domain(self, domain_id, group_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_role_from_group_on_project_existence(self, project_id,
+ group_id, role_id):
+ """Check role of a user on a project."""
+ resp, body = self.head('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_role_from_group_on_domain_existence(self, domain_id,
+ group_id, role_id):
+ """Check role of a user on a domain."""
+ resp, body = self.head('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_domains_user(
+ self, domain_id, user_id, role_id):
+ """Assigns a role to a user on projects owned by a domain."""
+ resp, body = self.put(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_user_on_domain(
+ self, domain_id, user_id, role_id):
+ """Revokes an inherited project role from a user on a domain."""
+ resp, body = self.delete(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_inherited_project_role_for_user_on_domain(
+ self, domain_id, user_id):
+ """Lists the inherited project roles on a domain for a user."""
+ resp, body = self.get(
+ "OS-INHERIT/domains/%s/users/%s/roles/inherited_to_projects"
+ % (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_inherited_project_role_on_domain(
+ self, domain_id, user_id, role_id):
+ """Checks whether a user has an inherited project role on a domain."""
+ resp, body = self.head(
+ "OS-INHERIT/domains/%s/users/%s/roles/%s/inherited_to_projects"
+ % (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_domains_group(
+ self, domain_id, group_id, role_id):
+ """Assigns a role to a group on projects owned by a domain."""
+ resp, body = self.put(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_group_on_domain(
+ self, domain_id, group_id, role_id):
+ """Revokes an inherited project role from a group on a domain."""
+ resp, body = self.delete(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_inherited_project_role_for_group_on_domain(
+ self, domain_id, group_id):
+ """Lists the inherited project roles on a domain for a group."""
+ resp, body = self.get(
+ "OS-INHERIT/domains/%s/groups/%s/roles/inherited_to_projects"
+ % (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_group_inherited_project_role_on_domain(
+ self, domain_id, group_id, role_id):
+ """Checks whether a group has an inherited project role on a domain."""
+ resp, body = self.head(
+ "OS-INHERIT/domains/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_projects_user(
+ self, project_id, user_id, role_id):
+ """Assigns a role to a user on projects in a subtree."""
+ resp, body = self.put(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_user_on_project(
+ self, project_id, user_id, role_id):
+ """Revokes an inherited role from a user on a project."""
+ resp, body = self.delete(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_user_has_flag_on_inherited_to_project(
+ self, project_id, user_id, role_id):
+ """Checks whether a user has a role assignment"""
+ """with the inherited_to_projects flag on a project."""
+ resp, body = self.head(
+ "OS-INHERIT/projects/%s/users/%s/roles/%s/inherited_to_projects"
+ % (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def assign_inherited_role_on_projects_group(
+ self, project_id, group_id, role_id):
+ """Assigns a role to a group on projects in a subtree."""
+ resp, body = self.put(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def revoke_inherited_role_from_group_on_project(
+ self, project_id, group_id, role_id):
+ """Revokes an inherited role from a group on a project."""
+ resp, body = self.delete(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_group_has_flag_on_inherited_to_project(
+ self, project_id, group_id, role_id):
+ """Checks whether a group has a role assignment"""
+ """with the inherited_to_projects flag on a project."""
+ resp, body = self.head(
+ "OS-INHERIT/projects/%s/groups/%s/roles/%s/inherited_to_projects"
+ % (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/identity/v3/json/services_client.py b/tempest/services/identity/v3/json/services_client.py
index dd65f1d..e863016 100644
--- a/tempest/services/identity/v3/json/services_client.py
+++ b/tempest/services/identity/v3/json/services_client.py
@@ -19,10 +19,10 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ServicesClient(service_client.ServiceClient):
+class ServicesClient(rest_client.RestClient):
api_version = "v3"
def update_service(self, service_id, **kwargs):
@@ -35,7 +35,7 @@
resp, body = self.patch('services/%s' % service_id, patch_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_service(self, service_id):
"""Get Service."""
@@ -43,7 +43,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_service(self, **kwargs):
"""Creates a service.
@@ -55,16 +55,16 @@
resp, body = self.post("services", body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_service(self, serv_id):
url = "services/" + serv_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_services(self):
resp, body = self.get('services')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_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..dedee05
--- /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.lib.common import rest_client
+
+
+class TrustsClient(rest_client.RestClient):
+ 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 rest_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 rest_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 rest_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 rest_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 rest_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 rest_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 rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v3/json/users_clients.py b/tempest/services/identity/v3/json/users_clients.py
new file mode 100644
index 0000000..3ab8eab
--- /dev/null
+++ b/tempest/services/identity/v3/json/users_clients.py
@@ -0,0 +1,121 @@
+# 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.lib.common import rest_client
+
+
+class UsersClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def create_user(self, user_name, password=None, project_id=None,
+ email=None, domain_id='default', **kwargs):
+ """Creates a user."""
+ en = kwargs.get('enabled', True)
+ description = kwargs.get('description', None)
+ default_project_id = kwargs.get('default_project_id')
+ post_body = {
+ 'project_id': project_id,
+ 'default_project_id': default_project_id,
+ 'description': description,
+ 'domain_id': domain_id,
+ 'email': email,
+ 'enabled': en,
+ 'name': user_name,
+ 'password': password
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.post('users', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_user(self, user_id, name, **kwargs):
+ """Updates a user."""
+ body = self.show_user(user_id)['user']
+ email = kwargs.get('email', body['email'])
+ en = kwargs.get('enabled', body['enabled'])
+ project_id = kwargs.get('project_id', body['project_id'])
+ if 'default_project_id' in body.keys():
+ default_project_id = kwargs.get('default_project_id',
+ body['default_project_id'])
+ else:
+ default_project_id = kwargs.get('default_project_id')
+ description = kwargs.get('description', body['description'])
+ domain_id = kwargs.get('domain_id', body['domain_id'])
+ post_body = {
+ 'name': name,
+ 'email': email,
+ 'enabled': en,
+ 'project_id': project_id,
+ 'default_project_id': default_project_id,
+ 'id': user_id,
+ 'domain_id': domain_id,
+ 'description': description
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.patch('users/%s' % user_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_user_password(self, user_id, **kwargs):
+ """Update a user password
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#changeUserPassword
+ """
+ update_user = json.dumps({'user': kwargs})
+ resp, _ = self.post('users/%s/password' % user_id, update_user)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def list_user_projects(self, user_id):
+ """Lists the projects on which a user has roles assigned."""
+ resp, body = self.get('users/%s/projects' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_users(self, params=None):
+ """Get the list of users."""
+ url = 'users'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_user(self, user_id):
+ """Deletes a User."""
+ resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_groups(self, user_id):
+ """Lists groups which a user belongs to."""
+ resp, body = self.get('users/%s/groups' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_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..3f256ec 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -22,36 +22,24 @@
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 import rest_client
+from tempest.lib.common.utils import misc as misc_utils
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
-class ImagesClient(service_client.ServiceClient):
+class ImagesClient(rest_client.RestClient):
- def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
- build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
+ def __init__(self, auth_provider, catalog_type, region, **kwargs):
super(ImagesClient, self).__init__(
- auth_provider,
- catalog_type,
- region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=(
- disable_ssl_certificate_validation),
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, catalog_type, region, **kwargs)
self._http = None
- self.dscv = disable_ssl_certificate_validation
- self.ca_certs = ca_certs
+ self.dscv = kwargs.get("disable_ssl_certificate_validation")
+ self.ca_certs = kwargs.get("ca_certs")
def _image_meta_from_headers(self, headers):
meta = {'properties': {}}
@@ -130,7 +118,7 @@
self._error_checker('POST', '/v1/images', headers, data, resp,
body_iter)
body = json.loads(''.join([c for c in body_iter]))
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _update_with_data(self, image_id, headers, data):
url = '/v1/images/%s' % image_id
@@ -139,7 +127,7 @@
self._error_checker('PUT', url, headers, data,
resp, body_iter)
body = json.loads(''.join([c for c in body_iter]))
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
@property
def http(self):
@@ -158,7 +146,7 @@
resp, body = self.post('v1/images', None, headers)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_image(self, image_id, **kwargs):
headers = {}
@@ -172,13 +160,13 @@
resp, body = self.put(url, None, headers)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_image(self, image_id):
url = 'v1/images/%s' % image_id
resp, body = self.delete(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_images(self, detail=False, **kwargs):
"""Return a list of all images filtered by input parameters.
@@ -208,20 +196,20 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def get_image_meta(self, image_id):
url = 'v1/images/%s' % image_id
resp, __ = self.head(url)
self.expected_success(200, resp.status)
body = self._image_meta_from_headers(resp)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
url = 'v1/images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
def is_resource_deleted(self, id):
try:
@@ -240,7 +228,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_shared_images(self, tenant_id):
"""List shared images with the specified tenant"""
@@ -248,7 +236,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def add_member(self, member_id, image_id, **kwargs):
"""Add a member to an image.
@@ -260,13 +248,13 @@
body = json.dumps({'member': kwargs})
resp, __ = self.put(url, body)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def delete_member(self, member_id, image_id):
url = 'v1/images/%s/members/%s' % (image_id, member_id)
resp, __ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
# NOTE(afazekas): just for the wait function
def _get_image_status(self, image_id):
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index 72b203a..4e037af 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -15,32 +15,20 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class ImagesClientV2(service_client.ServiceClient):
+class ImagesClientV2(rest_client.RestClient):
- def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
- build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
+ def __init__(self, auth_provider, catalog_type, region, **kwargs):
super(ImagesClientV2, self).__init__(
- auth_provider,
- catalog_type,
- region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=(
- disable_ssl_certificate_validation),
- ca_certs=ca_certs,
- trace_requests=trace_requests)
+ auth_provider, catalog_type, region, **kwargs)
self._http = None
- self.dscv = disable_ssl_certificate_validation
- self.ca_certs = ca_certs
+ self.dscv = kwargs.get("disable_ssl_certificate_validation")
+ self.ca_certs = kwargs.get("ca_certs")
def _get_http(self):
return glance_http.HTTPClient(auth_provider=self.auth_provider,
@@ -66,7 +54,7 @@
resp, body = self.patch('v2/images/%s' % image_id, data, headers)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_image(self, **kwargs):
"""Create an image.
@@ -78,25 +66,25 @@
resp, body = self.post('v2/images', data)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def deactivate_image(self, image_id):
url = 'v2/images/%s/actions/deactivate' % image_id
resp, body = self.post(url, None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def reactivate_image(self, image_id):
url = 'v2/images/%s/actions/reactivate' % image_id
resp, body = self.post(url, None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_image(self, image_id):
url = 'v2/images/%s' % image_id
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def list_images(self, params=None):
url = 'v2/images'
@@ -107,14 +95,14 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
url = 'v2/images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
try:
@@ -134,32 +122,32 @@
resp, body = self.http.raw_request('PUT', url, headers=headers,
body=data)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image_file(self, image_id):
url = 'v2/images/%s/file' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
def add_image_tag(self, image_id, tag):
url = 'v2/images/%s/tags/%s' % (image_id, tag)
resp, body = self.put(url, body=None)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_image_tag(self, image_id, tag):
url = 'v2/images/%s/tags/%s' % (image_id, tag)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def list_image_members(self, image_id):
url = 'v2/images/%s/members' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_image_member(self, image_id, **kwargs):
"""Create an image member.
@@ -172,7 +160,7 @@
resp, body = self.post(url, data)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_image_member(self, image_id, member_id, **kwargs):
"""Update an image member.
@@ -185,33 +173,33 @@
resp, body = self.put(url, data)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_image_member(self, image_id, member_id):
url = 'v2/images/%s/members/%s' % (image_id, member_id)
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, json.loads(body))
+ return rest_client.ResponseBody(resp, json.loads(body))
def delete_image_member(self, image_id, member_id):
url = 'v2/images/%s/members/%s' % (image_id, member_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def show_schema(self, schema):
url = 'v2/schemas/%s' % schema
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_resource_types(self):
url = '/v2/metadefs/resource_types'
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_namespace(self, **kwargs):
"""Create a namespace.
@@ -223,14 +211,14 @@
resp, body = self.post('/v2/metadefs/namespaces', data)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_namespace(self, namespace):
url = '/v2/metadefs/namespaces/%s' % namespace
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_namespace(self, namespace, **kwargs):
"""Update a namespace.
@@ -247,10 +235,10 @@
resp, body = self.put(url, body=data)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_namespace(self, namespace):
url = '/v2/metadefs/namespaces/%s' % namespace
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/services/messaging/__init__.py b/tempest/services/messaging/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/messaging/__init__.py
+++ /dev/null
diff --git a/tempest/services/messaging/json/__init__.py b/tempest/services/messaging/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/messaging/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/messaging/json/messaging_client.py b/tempest/services/messaging/json/messaging_client.py
deleted file mode 100644
index 5a43841..0000000
--- a/tempest/services/messaging/json/messaging_client.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright (c) 2014 Rackspace, 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 uuid
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.api_schema.response.messaging.v1 import queues as queues_schema
-from tempest.common import service_client
-
-
-class MessagingClient(service_client.ServiceClient):
-
- def __init__(self, auth_provider, service, region,
- endpoint_type=None, build_interval=None, build_timeout=None,
- disable_ssl_certificate_validation=None, ca_certs=None,
- trace_requests=None):
- dscv = disable_ssl_certificate_validation
- super(MessagingClient, self).__init__(
- auth_provider, service, region,
- endpoint_type=endpoint_type,
- build_interval=build_interval,
- build_timeout=build_timeout,
- disable_ssl_certificate_validation=dscv,
- ca_certs=ca_certs,
- trace_requests=trace_requests)
-
- self.version = '1'
- self.uri_prefix = 'v{0}'.format(self.version)
-
- client_id = uuid.uuid4().hex
- self.headers = {'Client-ID': client_id}
-
- def list_queues(self):
- uri = '{0}/queues'.format(self.uri_prefix)
- resp, body = self.get(uri)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.list_queues, resp, body)
- return resp, body
-
- def create_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.put(uri, body=None)
- self.expected_success(201, resp.status)
- return resp, body
-
- def show_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.get(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def head_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.head(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def delete_queue(self, queue_name):
- uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
- resp, body = self.delete(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def show_queue_stats(self, queue_name):
- uri = '{0}/queues/{1}/stats'.format(self.uri_prefix, queue_name)
- resp, body = self.get(uri)
- body = json.loads(body)
- self.validate_response(queues_schema.queue_stats, resp, body)
- return resp, body
-
- def show_queue_metadata(self, queue_name):
- uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return resp, body
-
- def set_queue_metadata(self, queue_name, rbody):
- uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
- resp, body = self.put(uri, body=json.dumps(rbody))
- self.expected_success(204, resp.status)
- return resp, body
-
- def post_messages(self, queue_name, rbody):
- uri = '{0}/queues/{1}/messages'.format(self.uri_prefix, queue_name)
- resp, body = self.post(uri, body=json.dumps(rbody),
- extra_headers=True,
- headers=self.headers)
-
- body = json.loads(body)
- self.validate_response(queues_schema.post_messages, resp, body)
- return resp, body
-
- def list_messages(self, queue_name):
- uri = '{0}/queues/{1}/messages?echo=True'.format(self.uri_prefix,
- queue_name)
- resp, body = self.get(uri, extra_headers=True, headers=self.headers)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.list_messages, resp, body)
-
- return resp, body
-
- def show_single_message(self, message_uri):
- resp, body = self.get(message_uri, extra_headers=True,
- headers=self.headers)
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.get_single_message, resp,
- body)
- return resp, body
-
- def show_multiple_messages(self, message_uri):
- resp, body = self.get(message_uri, extra_headers=True,
- headers=self.headers)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.get_multiple_messages,
- resp,
- body)
-
- return resp, body
-
- def delete_messages(self, message_uri):
- resp, body = self.delete(message_uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def post_claims(self, queue_name, rbody, url_params=False):
- uri = '{0}/queues/{1}/claims'.format(self.uri_prefix, queue_name)
- if url_params:
- uri += '?%s' % urllib.urlencode(url_params)
-
- resp, body = self.post(uri, body=json.dumps(rbody),
- extra_headers=True,
- headers=self.headers)
-
- body = json.loads(body)
- self.validate_response(queues_schema.claim_messages, resp, body)
- return resp, body
-
- def query_claim(self, claim_uri):
- resp, body = self.get(claim_uri)
-
- if resp['status'] != '204':
- body = json.loads(body)
- self.validate_response(queues_schema.query_claim, resp, body)
- return resp, body
-
- def update_claim(self, claim_uri, rbody):
- resp, body = self.patch(claim_uri, body=json.dumps(rbody))
- self.expected_success(204, resp.status)
- return resp, body
-
- def delete_claim(self, claim_uri):
- resp, body = self.delete(claim_uri)
- self.expected_success(204, resp.status)
- return resp, body
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index c6b22df..bcef36b 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -12,11 +12,10 @@
import time
-from tempest_lib.common.utils import misc
-from tempest_lib import exceptions as lib_exc
-
from tempest import exceptions
-from tempest.services.network.json import base
+from tempest.lib.common.utils import misc
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.network import base
class NetworkClient(base.BaseNetworkClient):
@@ -35,33 +34,6 @@
quotas
"""
- def create_bulk_network(self, **kwargs):
- """create bulk network
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreateNetwork
- """
- uri = '/networks'
- return self.create_resource(uri, kwargs)
-
- def create_bulk_subnet(self, **kwargs):
- """create bulk subnet
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreateSubnet
- """
- uri = '/subnets'
- return self.create_resource(uri, kwargs)
-
- def create_bulk_port(self, **kwargs):
- """create bulk port
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2.html#bulkCreatePorts
- """
- uri = '/ports'
- return self.create_resource(uri, kwargs)
-
def wait_for_resource_deletion(self, resource_type, id, client=None):
"""Waits for a resource to be deleted."""
start_time = int(time.time())
@@ -121,110 +93,10 @@
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
- def create_router(self, name, admin_state_up=True, **kwargs):
- post_body = {'router': kwargs}
- post_body['router']['name'] = name
- post_body['router']['admin_state_up'] = admin_state_up
- uri = '/routers'
- return self.create_resource(uri, post_body)
-
- def _update_router(self, router_id, set_enable_snat, **kwargs):
- uri = '/routers/%s' % router_id
- body = self.show_resource(uri)
- update_body = {}
- update_body['name'] = kwargs.get('name', body['router']['name'])
- update_body['admin_state_up'] = kwargs.get(
- 'admin_state_up', body['router']['admin_state_up'])
- cur_gw_info = body['router']['external_gateway_info']
- if cur_gw_info:
- # TODO(kevinbenton): setting the external gateway info is not
- # allowed for a regular tenant. If the ability to update is also
- # merged, a test case for this will need to be added similar to
- # the SNAT case.
- cur_gw_info.pop('external_fixed_ips', None)
- if not set_enable_snat:
- cur_gw_info.pop('enable_snat', None)
- update_body['external_gateway_info'] = kwargs.get(
- 'external_gateway_info', body['router']['external_gateway_info'])
- if 'distributed' in kwargs:
- update_body['distributed'] = kwargs['distributed']
- update_body = dict(router=update_body)
- return self.update_resource(uri, update_body)
-
- def update_router(self, router_id, **kwargs):
- """Update a router leaving enable_snat to its default value."""
- # If external_gateway_info contains enable_snat the request will fail
- # with 404 unless executed with admin client, and therefore we instruct
- # _update_router to not set this attribute
- # NOTE(salv-orlando): The above applies as long as Neutron's default
- # policy is to restrict enable_snat usage to admins only.
- return self._update_router(router_id, set_enable_snat=False, **kwargs)
-
- def show_router(self, router_id, **fields):
- uri = '/routers/%s' % router_id
- return self.show_resource(uri, **fields)
-
- def delete_router(self, router_id):
- uri = '/routers/%s' % router_id
- return self.delete_resource(uri)
-
- def list_routers(self, **filters):
- uri = '/routers'
- return self.list_resources(uri, **filters)
-
- def update_router_with_snat_gw_info(self, router_id, **kwargs):
- """Update a router passing also the enable_snat attribute.
-
- This method must be execute with admin credentials, otherwise the API
- call will return a 404 error.
- """
- return self._update_router(router_id, set_enable_snat=True, **kwargs)
-
- def add_router_interface(self, router_id, **kwargs):
- """Add router interface.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#addRouterInterface
- """
- uri = '/routers/%s/add_router_interface' % router_id
- return self.update_resource(uri, kwargs)
-
- def remove_router_interface(self, router_id, **kwargs):
- """Remove router interface.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#removeRouterInterface
- """
- uri = '/routers/%s/remove_router_interface' % router_id
- return self.update_resource(uri, kwargs)
-
def list_router_interfaces(self, uuid):
uri = '/ports?device_id=%s' % uuid
return self.list_resources(uri)
- def list_l3_agents_hosting_router(self, router_id):
- uri = '/routers/%s/l3-agents' % router_id
- return self.list_resources(uri)
-
def list_dhcp_agent_hosting_network(self, network_id):
uri = '/networks/%s/dhcp-agents' % network_id
return self.list_resources(uri)
-
- def update_extra_routes(self, router_id, **kwargs):
- """Update Extra routes.
-
- Available params: see http://developer.openstack.org/
- api-ref-networking-v2-ext.html#updateExtraRoutes
- """
- uri = '/routers/%s' % router_id
- put_body = {'router': kwargs}
- return self.update_resource(uri, put_body)
-
- def delete_extra_routes(self, router_id):
- uri = '/routers/%s' % router_id
- put_body = {
- 'router': {
- 'routes': None
- }
- }
- return self.update_resource(uri, put_body)
diff --git a/tempest/services/network/json/routers_client.py b/tempest/services/network/json/routers_client.py
new file mode 100644
index 0000000..18442cf
--- /dev/null
+++ b/tempest/services/network/json/routers_client.py
@@ -0,0 +1,116 @@
+# 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 RoutersClient(base.BaseNetworkClient):
+
+ def create_router(self, name, admin_state_up=True, **kwargs):
+ post_body = {'router': kwargs}
+ post_body['router']['name'] = name
+ post_body['router']['admin_state_up'] = admin_state_up
+ uri = '/routers'
+ return self.create_resource(uri, post_body)
+
+ def _update_router(self, router_id, set_enable_snat, **kwargs):
+ uri = '/routers/%s' % router_id
+ body = self.show_resource(uri)
+ update_body = {}
+ update_body['name'] = kwargs.get('name', body['router']['name'])
+ update_body['admin_state_up'] = kwargs.get(
+ 'admin_state_up', body['router']['admin_state_up'])
+ cur_gw_info = body['router']['external_gateway_info']
+ if cur_gw_info:
+ # TODO(kevinbenton): setting the external gateway info is not
+ # allowed for a regular tenant. If the ability to update is also
+ # merged, a test case for this will need to be added similar to
+ # the SNAT case.
+ cur_gw_info.pop('external_fixed_ips', None)
+ if not set_enable_snat:
+ cur_gw_info.pop('enable_snat', None)
+ update_body['external_gateway_info'] = kwargs.get(
+ 'external_gateway_info', body['router']['external_gateway_info'])
+ if 'distributed' in kwargs:
+ update_body['distributed'] = kwargs['distributed']
+ update_body = dict(router=update_body)
+ return self.update_resource(uri, update_body)
+
+ def update_router(self, router_id, **kwargs):
+ """Update a router leaving enable_snat to its default value."""
+ # If external_gateway_info contains enable_snat the request will fail
+ # with 404 unless executed with admin client, and therefore we instruct
+ # _update_router to not set this attribute
+ # NOTE(salv-orlando): The above applies as long as Neutron's default
+ # policy is to restrict enable_snat usage to admins only.
+ return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+ def show_router(self, router_id, **fields):
+ uri = '/routers/%s' % router_id
+ return self.show_resource(uri, **fields)
+
+ def delete_router(self, router_id):
+ uri = '/routers/%s' % router_id
+ return self.delete_resource(uri)
+
+ def list_routers(self, **filters):
+ uri = '/routers'
+ return self.list_resources(uri, **filters)
+
+ def update_extra_routes(self, router_id, **kwargs):
+ """Update Extra routes.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#updateExtraRoutes
+ """
+ uri = '/routers/%s' % router_id
+ put_body = {'router': kwargs}
+ return self.update_resource(uri, put_body)
+
+ def delete_extra_routes(self, router_id):
+ uri = '/routers/%s' % router_id
+ put_body = {
+ 'router': {
+ 'routes': None
+ }
+ }
+ return self.update_resource(uri, put_body)
+
+ def update_router_with_snat_gw_info(self, router_id, **kwargs):
+ """Update a router passing also the enable_snat attribute.
+
+ This method must be execute with admin credentials, otherwise the API
+ call will return a 404 error.
+ """
+ return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
+ def add_router_interface(self, router_id, **kwargs):
+ """Add router interface.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#addRouterInterface
+ """
+ uri = '/routers/%s/add_router_interface' % router_id
+ return self.update_resource(uri, kwargs)
+
+ def remove_router_interface(self, router_id, **kwargs):
+ """Remove router interface.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-networking-v2-ext.html#removeRouterInterface
+ """
+ uri = '/routers/%s/remove_router_interface' % router_id
+ return self.update_resource(uri, kwargs)
+
+ def list_l3_agents_hosting_router(self, router_id):
+ uri = '/routers/%s/l3-agents' % router_id
+ return self.list_resources(uri)
diff --git a/tempest/services/network/json/security_group_rules_client.py b/tempest/services/network/json/security_group_rules_client.py
index b2ba5b2..944eba6 100644
--- a/tempest/services/network/json/security_group_rules_client.py
+++ b/tempest/services/network/json/security_group_rules_client.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.network.json import base
+from tempest.lib.services.network import base
class SecurityGroupRulesClient(base.BaseNetworkClient):
diff --git a/tempest/services/network/resources.py b/tempest/services/network/resources.py
index 0a7da92..5512075 100644
--- a/tempest/services/network/resources.py
+++ b/tempest/services/network/resources.py
@@ -39,6 +39,7 @@
self.client = kwargs.pop('client', None)
self.network_client = kwargs.pop('network_client', None)
self.networks_client = kwargs.pop('networks_client', None)
+ self.routers_client = kwargs.pop('routers_client', None)
self.subnets_client = kwargs.pop('subnets_client', None)
self.ports_client = kwargs.pop('ports_client', None)
super(DeletableResource, self).__init__(*args, **kwargs)
@@ -89,12 +90,12 @@
def add_to_router(self, router_id):
self._router_ids.add(router_id)
- self.network_client.add_router_interface(router_id,
+ self.routers_client.add_router_interface(router_id,
subnet_id=self.id)
def delete(self):
for router_id in self._router_ids.copy():
- self.network_client.remove_router_interface(router_id,
+ self.routers_client.remove_router_interface(router_id,
subnet_id=self.id)
self._router_ids.remove(router_id)
self.subnets_client.delete_subnet(self.id)
@@ -109,14 +110,14 @@
return self.update(external_gateway_info=dict())
def update(self, *args, **kwargs):
- result = self.client.update_router(self.id,
- *args,
- **kwargs)
+ result = self.routers_client.update_router(self.id,
+ *args,
+ **kwargs)
return super(DeletableRouter, self).update(**result['router'])
def delete(self):
self.unset_gateway()
- self.client.delete_router(self.id)
+ self.routers_client.delete_router(self.id)
class DeletableFloatingIp(DeletableResource):
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index 2c7fe29..6012a92 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -18,10 +18,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class AccountClient(service_client.ServiceClient):
+class AccountClient(rest_client.RestClient):
def create_account(self, data=None,
params=None,
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
index 73c25db..5a26bfc 100644
--- a/tempest/services/object_storage/container_client.py
+++ b/tempest/services/object_storage/container_client.py
@@ -18,10 +18,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ContainerClient(service_client.ServiceClient):
+class ContainerClient(rest_client.RestClient):
def create_container(
self, container_name,
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 5890e33..9ad8c27 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -17,10 +17,10 @@
from six.moves import http_client as httplib
from six.moves.urllib import parse as urlparse
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class ObjectClient(service_client.ServiceClient):
+class ObjectClient(rest_client.RestClient):
def create_object(self, container, object_name, data,
params=None, metadata=None, headers=None):
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 22e53f5..6019cf5 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -18,13 +18,13 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class OrchestrationClient(service_client.ServiceClient):
+class OrchestrationClient(rest_client.RestClient):
def list_stacks(self, params=None):
"""Lists all stacks for a user."""
@@ -36,7 +36,7 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_stack(self, name, disable_rollback=True, parameters=None,
timeout_mins=60, template=None, template_url=None,
@@ -56,7 +56,7 @@
resp, body = self.post(uri, headers=headers, body=body)
self.expected_success(201, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_stack(self, stack_identifier, name, disable_rollback=True,
parameters=None, timeout_mins=60, template=None,
@@ -75,7 +75,7 @@
uri = "stacks/%s" % stack_identifier
resp, body = self.put(uri, headers=headers, body=body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _prepare_update_create(self, name, disable_rollback=True,
parameters=None, timeout_mins=60,
@@ -111,7 +111,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def suspend_stack(self, stack_identifier):
"""Suspend a stack."""
@@ -119,7 +119,7 @@
body = {'suspend': None}
resp, body = self.post(url, json.dumps(body))
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def resume_stack(self, stack_identifier):
"""Resume a stack."""
@@ -127,7 +127,7 @@
body = {'resume': None}
resp, body = self.post(url, json.dumps(body))
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def list_resources(self, stack_identifier):
"""Returns the details of a single resource."""
@@ -135,7 +135,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_resource(self, stack_identifier, resource_name):
"""Returns the details of a single resource."""
@@ -143,13 +143,13 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_stack(self, stack_identifier):
"""Deletes the specified Stack."""
resp, _ = self.delete("stacks/%s" % str(stack_identifier))
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def wait_for_resource_status(self, stack_identifier, resource_name,
status, failure_pattern='^.*_FAILED$'):
@@ -224,7 +224,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_events(self, stack_identifier):
"""Returns list of all events for a stack."""
@@ -232,7 +232,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_resource_events(self, stack_identifier, resource_name):
"""Returns list of all events for a resource from stack."""
@@ -241,7 +241,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_event(self, stack_identifier, resource_name, event_id):
"""Returns the details of a single stack's event."""
@@ -250,7 +250,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_template(self, stack_identifier):
"""Returns the template for the stack."""
@@ -258,7 +258,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _validate_template(self, post_body):
"""Returns the validation request result."""
@@ -266,7 +266,7 @@
resp, body = self.post('validate', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def validate_template(self, template, parameters=None):
"""Returns the validation result for a template with parameters."""
@@ -293,21 +293,21 @@
resp, body = self.get('resource_types')
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_resource_type(self, resource_type_name):
"""Return the schema of a resource type."""
url = 'resource_types/%s' % resource_type_name
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, json.loads(body))
+ return rest_client.ResponseBody(resp, json.loads(body))
def show_resource_type_template(self, resource_type_name):
"""Return the template of a resource type."""
url = 'resource_types/%s/template' % resource_type_name
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, json.loads(body))
+ return rest_client.ResponseBody(resp, json.loads(body))
def create_software_config(self, name=None, config=None, group=None,
inputs=None, outputs=None, options=None):
@@ -318,7 +318,7 @@
resp, body = self.post(url, headers=headers, body=body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_software_config(self, conf_id):
"""Returns a software configuration resource."""
@@ -326,14 +326,14 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_software_config(self, conf_id):
"""Deletes a specific software configuration."""
url = 'software_configs/%s' % str(conf_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def create_software_deploy(self, server_id=None, config_id=None,
action=None, status=None,
@@ -348,7 +348,7 @@
resp, body = self.post(url, headers=headers, body=body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_software_deploy(self, deploy_id=None, server_id=None,
config_id=None, action=None, status=None,
@@ -363,7 +363,7 @@
resp, body = self.put(url, headers=headers, body=body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_software_deployments(self):
"""Returns a list of all deployments."""
@@ -371,7 +371,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_software_deployment(self, deploy_id):
"""Returns a specific software deployment."""
@@ -379,7 +379,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_software_deployment_metadata(self, server_id):
"""Return a config metadata for a specific server."""
@@ -387,14 +387,14 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_software_deploy(self, deploy_id):
"""Deletes a specific software deployment."""
url = 'software_deployments/%s' % str(deploy_id)
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
- return service_client.ResponseBody(resp)
+ return rest_client.ResponseBody(resp)
def _prep_software_config_create(self, name=None, conf=None, group=None,
inputs=None, outputs=None, options=None):
diff --git a/tempest/services/telemetry/json/alarming_client.py b/tempest/services/telemetry/json/alarming_client.py
index ce14211..703efdf 100644
--- a/tempest/services/telemetry/json/alarming_client.py
+++ b/tempest/services/telemetry/json/alarming_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class AlarmingClient(service_client.ServiceClient):
+class AlarmingClient(rest_client.RestClient):
version = '2'
uri_prefix = "v2"
@@ -42,21 +42,21 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
+ return rest_client.ResponseBodyList(resp, body)
def show_alarm(self, alarm_id):
uri = '%s/alarms/%s' % (self.uri_prefix, alarm_id)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_alarm_history(self, alarm_id):
uri = "%s/alarms/%s/history" % (self.uri_prefix, alarm_id)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
+ return rest_client.ResponseBodyList(resp, body)
def delete_alarm(self, alarm_id):
uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
@@ -64,7 +64,7 @@
self.expected_success(204, resp.status)
if body:
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_alarm(self, **kwargs):
uri = "%s/alarms" % self.uri_prefix
@@ -72,7 +72,7 @@
resp, body = self.post(uri, body)
self.expected_success(201, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_alarm(self, alarm_id, **kwargs):
uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
@@ -80,14 +80,14 @@
resp, body = self.put(uri, body)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_alarm_state(self, alarm_id):
uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
def alarm_set_state(self, alarm_id, state):
uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
@@ -95,4 +95,4 @@
resp, body = self.put(uri, body)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyData(resp, body)
+ return rest_client.ResponseBodyData(resp, body)
diff --git a/tempest/services/telemetry/json/telemetry_client.py b/tempest/services/telemetry/json/telemetry_client.py
index abdeba2..df7d916 100644
--- a/tempest/services/telemetry/json/telemetry_client.py
+++ b/tempest/services/telemetry/json/telemetry_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class TelemetryClient(service_client.ServiceClient):
+class TelemetryClient(rest_client.RestClient):
version = '2'
uri_prefix = "v2"
@@ -36,7 +36,7 @@
resp, body = self.post(uri, body)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def _helper_list(self, uri, query=None, period=None):
uri_dict = {}
@@ -51,7 +51,7 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
+ return rest_client.ResponseBodyList(resp, body)
def list_resources(self, query=None):
uri = '%s/resources' % self.uri_prefix
@@ -78,4 +78,4 @@
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_hosts_client.py b/tempest/services/volume/base/admin/base_hosts_client.py
index 074f87f..382e9a8 100644
--- a/tempest/services/volume/base/admin/base_hosts_client.py
+++ b/tempest/services/volume/base/admin/base_hosts_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseHostsClient(service_client.ServiceClient):
+class BaseHostsClient(rest_client.RestClient):
"""Client class to send CRUD Volume Hosts API requests"""
def list_hosts(self, **params):
@@ -32,4 +32,4 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_quotas_client.py b/tempest/services/volume/base/admin/base_quotas_client.py
index e063a31..83816f2 100644
--- a/tempest/services/volume/base/admin/base_quotas_client.py
+++ b/tempest/services/volume/base/admin/base_quotas_client.py
@@ -15,10 +15,10 @@
from oslo_serialization import jsonutils
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseQuotasClient(service_client.ServiceClient):
+class BaseQuotasClient(rest_client.RestClient):
"""Client class to send CRUD Volume Quotas API requests"""
TYPE = "json"
@@ -30,7 +30,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_quota_set(self, tenant_id, params=None):
"""List the quota set for a tenant."""
@@ -42,7 +42,7 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_quota_usage(self, tenant_id):
"""List the quota set for a tenant."""
@@ -60,10 +60,10 @@
resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
- return service_client.ResponseBody(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.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_services_client.py b/tempest/services/volume/base/admin/base_services_client.py
index 3626469..861eb92 100644
--- a/tempest/services/volume/base/admin/base_services_client.py
+++ b/tempest/services/volume/base/admin/base_services_client.py
@@ -16,10 +16,10 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseServicesClient(service_client.ServiceClient):
+class BaseServicesClient(rest_client.RestClient):
def list_services(self, **params):
url = 'os-services'
@@ -29,4 +29,4 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/admin/base_types_client.py b/tempest/services/volume/base/admin/base_types_client.py
index 867273e..95ddff6 100644
--- a/tempest/services/volume/base/admin/base_types_client.py
+++ b/tempest/services/volume/base/admin/base_types_client.py
@@ -15,12 +15,12 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseTypesClient(service_client.ServiceClient):
+class BaseTypesClient(rest_client.RestClient):
"""Client class to send CRUD Volume Types API requests"""
def is_resource_deleted(self, resource):
@@ -56,7 +56,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_type(self, volume_id):
"""Returns the details of a single volume_type."""
@@ -64,7 +64,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_type(self, **kwargs):
"""Create volume type.
@@ -76,13 +76,13 @@
resp, body = self.post('types', post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_type(self, volume_id):
"""Deletes the Specified Volume_type."""
resp, body = self.delete("types/%s" % str(volume_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_volume_types_extra_specs(self, vol_type_id, **params):
"""List all the volume_types extra specs created.
@@ -100,7 +100,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_type_extra_specs(self, vol_type_id, extra_specs_name):
"""Returns the details of a single volume_type extra spec."""
@@ -109,7 +109,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_type_extra_specs(self, vol_type_id, extra_specs):
"""Creates a new Volume_type extra spec.
@@ -122,14 +122,14 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
"""Deletes the Specified Volume_type extra spec."""
resp, body = self.delete("types/%s/extra_specs/%s" % (
(str(vol_id)), str(extra_spec_name)))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name,
extra_specs):
@@ -146,7 +146,7 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_encryption_type(self, vol_type_id):
"""Get the volume encryption type for the specified volume type.
@@ -157,7 +157,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_encryption_type(self, vol_type_id, **kwargs):
"""Create encryption type.
@@ -171,11 +171,11 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_encryption_type(self, vol_type_id):
"""Delete the encryption type for the specified volume-type."""
resp, body = self.delete(
"/types/%s/encryption/provider" % str(vol_type_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_availability_zone_client.py b/tempest/services/volume/base/base_availability_zone_client.py
index b63fdc2..1c2deba 100644
--- a/tempest/services/volume/base/base_availability_zone_client.py
+++ b/tempest/services/volume/base/base_availability_zone_client.py
@@ -15,13 +15,13 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseAvailabilityZoneClient(service_client.ServiceClient):
+class BaseAvailabilityZoneClient(rest_client.RestClient):
def list_availability_zones(self):
resp, body = self.get('os-availability-zone')
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/services/volume/base/base_backups_client.py
index fc9a40a..780da7b 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -16,13 +16,13 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseBackupsClient(service_client.ServiceClient):
+class BaseBackupsClient(rest_client.RestClient):
"""Client class to send CRUD Volume backup API requests"""
def create_backup(self, **kwargs):
@@ -31,7 +31,7 @@
resp, body = self.post('backups', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def restore_backup(self, backup_id, **kwargs):
"""Restore volume from backup."""
@@ -39,13 +39,13 @@
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
resp, body = self.delete('backups/%s' % (str(backup_id)))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
"""Returns the details of a single backup."""
@@ -53,7 +53,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_backups(self, detail=False):
"""Information for all the tenant's backups."""
@@ -63,7 +63,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def export_backup(self, backup_id):
"""Export backup metadata record."""
@@ -71,7 +71,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def import_backup(self, **kwargs):
"""Import backup metadata record."""
@@ -79,7 +79,7 @@
resp, body = self.post("backups/import_record", post_body)
body = json.loads(body)
self.expected_success(201, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def wait_for_backup_status(self, backup_id, status):
"""Waits for a Backup to reach a given status."""
diff --git a/tempest/services/volume/base/base_extensions_client.py b/tempest/services/volume/base/base_extensions_client.py
index afc3f6b..b90fe94 100644
--- a/tempest/services/volume/base/base_extensions_client.py
+++ b/tempest/services/volume/base/base_extensions_client.py
@@ -15,14 +15,14 @@
from oslo_serialization import jsonutils as json
-from tempest.common import service_client
+from tempest.lib.common import rest_client
-class BaseExtensionsClient(service_client.ServiceClient):
+class BaseExtensionsClient(rest_client.RestClient):
def list_extensions(self):
url = 'extensions'
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_qos_client.py b/tempest/services/volume/base/base_qos_client.py
index 697e902..2d9f02a 100644
--- a/tempest/services/volume/base/base_qos_client.py
+++ b/tempest/services/volume/base/base_qos_client.py
@@ -15,13 +15,13 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseQosSpecsClient(service_client.ServiceClient):
+class BaseQosSpecsClient(rest_client.RestClient):
"""Client class to send CRUD QoS API requests"""
def is_resource_deleted(self, qos_id):
@@ -77,14 +77,14 @@
resp, body = self.post('qos-specs', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_qos(self, qos_id, force=False):
"""Delete the specified QoS specification."""
resp, body = self.delete(
"qos-specs/%s?force=%s" % (str(qos_id), force))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_qos(self):
"""List all the QoS specifications created."""
@@ -92,7 +92,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_qos(self, qos_id):
"""Get the specified QoS specification."""
@@ -100,7 +100,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def set_qos_key(self, qos_id, **kwargs):
"""Set the specified keys/values of QoS specification.
@@ -112,7 +112,7 @@
resp, body = self.put('qos-specs/%s' % qos_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def unset_qos_key(self, qos_id, keys):
"""Unset the specified keys of QoS specification.
@@ -124,7 +124,7 @@
put_body = json.dumps({'keys': keys})
resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def associate_qos(self, qos_id, vol_type_id):
"""Associate the specified QoS with specified volume-type."""
@@ -132,7 +132,7 @@
url += "?vol_type_id=%s" % vol_type_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_association_qos(self, qos_id):
"""Get the association of the specified QoS specification."""
@@ -140,7 +140,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def disassociate_qos(self, qos_id, vol_type_id):
"""Disassociate the specified QoS with specified volume-type."""
@@ -148,11 +148,11 @@
url += "?vol_type_id=%s" % vol_type_id
resp, body = self.get(url)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def disassociate_all_qos(self, qos_id):
"""Disassociate the specified QoS with all associations."""
url = "qos-specs/%s/disassociate_all" % str(qos_id)
resp, body = self.get(url)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
index 1388e9c..5e5637a 100644
--- a/tempest/services/volume/base/base_snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -15,16 +15,16 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
-class BaseSnapshotsClient(service_client.ServiceClient):
+class BaseSnapshotsClient(rest_client.RestClient):
"""Base Client class to send CRUD Volume API requests."""
create_resp = 200
@@ -40,7 +40,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot."""
@@ -48,7 +48,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_snapshot(self, **kwargs):
"""Creates a new snapshot.
@@ -60,7 +60,7 @@
resp, body = self.post('snapshots', post_body)
body = json.loads(body)
self.expected_success(self.create_resp, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot(self, snapshot_id, **kwargs):
"""Updates a snapshot."""
@@ -68,7 +68,7 @@
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
# NOTE(afazekas): just for the wait function
def _get_snapshot_status(self, snapshot_id):
@@ -111,7 +111,7 @@
"""Delete Snapshot."""
resp, body = self.delete("snapshots/%s" % str(snapshot_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
try:
@@ -130,7 +130,7 @@
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot_status(self, snapshot_id, **kwargs):
"""Update the specified snapshot's status."""
@@ -143,7 +143,7 @@
url = 'snapshots/%s/action' % str(snapshot_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_snapshot_metadata(self, snapshot_id, metadata):
"""Create metadata for the snapshot."""
@@ -152,7 +152,7 @@
resp, body = self.post(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_snapshot_metadata(self, snapshot_id):
"""Get metadata of the snapshot."""
@@ -160,7 +160,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot_metadata(self, snapshot_id, **kwargs):
"""Update metadata for the snapshot."""
@@ -173,7 +173,7 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
"""Update metadata item for the snapshot."""
@@ -186,18 +186,18 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_snapshot_metadata_item(self, snapshot_id, id):
"""Delete metadata item for the snapshot."""
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
resp, body = self.delete(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def force_delete_snapshot(self, snapshot_id):
"""Force Delete Snapshot."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
index d4435bc..f638bb6 100644
--- a/tempest/services/volume/base/base_volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -16,13 +16,13 @@
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.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class BaseVolumesClient(service_client.ServiceClient):
+class BaseVolumesClient(rest_client.RestClient):
"""Base client class to send CRUD Volume API requests"""
create_resp = 200
@@ -61,7 +61,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume(self, volume_id):
"""Returns the details of a single volume."""
@@ -69,7 +69,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume(self, **kwargs):
"""Creates a new Volume.
@@ -83,7 +83,7 @@
resp, body = self.post('volumes', post_body)
body = json.loads(body)
self.expected_success(self.create_resp, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume(self, volume_id, **kwargs):
"""Updates the Specified Volume."""
@@ -91,13 +91,13 @@
resp, body = self.put('volumes/%s' % volume_id, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
resp, body = self.delete("volumes/%s" % str(volume_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def upload_volume(self, volume_id, **kwargs):
"""Uploads a volume in Glance."""
@@ -106,7 +106,7 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def attach_volume(self, volume_id, **kwargs):
"""Attaches a volume to a given instance on a given mountpoint."""
@@ -114,7 +114,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def set_bootable_volume(self, volume_id, **kwargs):
"""set a bootable flag for a volume - true or false."""
@@ -122,7 +122,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def detach_volume(self, volume_id):
"""Detaches a volume from an instance."""
@@ -130,7 +130,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def reserve_volume(self, volume_id):
"""Reserves a volume."""
@@ -138,7 +138,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def unreserve_volume(self, volume_id):
"""Restore a reserved volume ."""
@@ -146,7 +146,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
@@ -170,14 +170,14 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def reset_volume_status(self, volume_id, **kwargs):
"""Reset the Specified Volume's Status."""
post_body = json.dumps({'os-reset_status': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def volume_begin_detaching(self, volume_id):
"""Volume Begin Detaching."""
@@ -185,7 +185,7 @@
post_body = json.dumps({'os-begin_detaching': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def volume_roll_detaching(self, volume_id):
"""Volume Roll Detaching."""
@@ -193,7 +193,7 @@
post_body = json.dumps({'os-roll_detaching': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_transfer(self, **kwargs):
"""Create a volume transfer."""
@@ -201,7 +201,7 @@
resp, body = self.post('os-volume-transfer', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_transfer(self, transfer_id):
"""Returns the details of a volume transfer."""
@@ -209,7 +209,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def list_volume_transfers(self, **params):
"""List all the volume transfers created."""
@@ -219,13 +219,13 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_transfer(self, transfer_id):
"""Delete a volume transfer."""
resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def accept_volume_transfer(self, transfer_id, **kwargs):
"""Accept a volume transfer."""
@@ -234,7 +234,7 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_readonly(self, volume_id, **kwargs):
"""Update the Specified Volume readonly."""
@@ -242,14 +242,14 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def create_volume_metadata(self, volume_id, metadata):
"""Create metadata for the volume."""
@@ -258,7 +258,7 @@
resp, body = self.post(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def show_volume_metadata(self, volume_id):
"""Get metadata of the volume."""
@@ -266,7 +266,7 @@
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_metadata(self, volume_id, metadata):
"""Update metadata for the volume."""
@@ -275,7 +275,7 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
@@ -284,14 +284,14 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.delete(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
+ return rest_client.ResponseBody(resp, body)
def retype_volume(self, volume_id, **kwargs):
"""Updates volume with new volume type."""
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 1f9df5e..382b851 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 = None
+ roles_client = admin_manager.roles_v3_client
+ 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, roles_client,
- users_client, project_domain_name=domain)
+ identity_client, projects_client, users_client,
+ 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/stress/stressaction.py b/tempest/stress/stressaction.py
index c8bd652..cf0a08a 100644
--- a/tempest/stress/stressaction.py
+++ b/tempest/stress/stressaction.py
@@ -85,8 +85,8 @@
finally:
shared_statistic['runs'] += 1
if self.stop_on_error and (shared_statistic['fails'] > 1):
- self.logger.warn("Stop process due to"
- "\"stop-on-error\" argument")
+ self.logger.warning("Stop process due to"
+ "\"stop-on-error\" argument")
self.tearDown()
sys.exit(1)
diff --git a/tempest/test.py b/tempest/test.py
index 6a0095f..fe3c770 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__)
@@ -180,6 +180,19 @@
return False
+def is_scheduler_filter_enabled(filter_name):
+ """Check the list of enabled compute scheduler filters from config. """
+
+ filters = CONF.compute_feature_enabled.scheduler_available_filters
+ if len(filters) == 0:
+ return False
+ if 'all' in filters:
+ return True
+ if filter_name in filters:
+ return True
+ return False
+
+
at_exit_set = set()
@@ -226,7 +239,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
@@ -234,6 +246,12 @@
log_format = ('%(asctime)s %(process)d %(levelname)-8s '
'[%(name)s] %(message)s')
+ # Client manager class to use in this test case.
+ client_manager = clients.Manager
+
+ # A way to adjust slow test classes
+ TIMEOUT_SCALING_FACTOR = 1
+
@classmethod
def setUpClass(cls):
# It should never be overridden by descendants
@@ -404,7 +422,7 @@
at_exit_set.add(self.__class__)
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
- test_timeout = int(test_timeout)
+ test_timeout = int(test_timeout) * self.TIMEOUT_SCALING_FACTOR
except ValueError:
test_timeout = 0
if test_timeout > 0:
@@ -437,14 +455,16 @@
"""
if CONF.identity.auth_version == 'v2':
client = self.os_admin.identity_client
+ users_client = self.os_admin.users_client
project_client = self.os_admin.tenants_client
roles_client = self.os_admin.roles_client
- users_client = self.os_admin.users_client
+ domains_client = None
else:
client = self.os_admin.identity_v3_client
- project_client = self.os_adm.projects_client
- roles_client = None
- users_client = None
+ users_client = self.os_admin.users_v3_client
+ project_client = self.os_admin.projects_client
+ roles_client = self.os_admin.roles_v3_client
+ domains_client = self.os_admin.domains_client
try:
domain = client.auth_provider.credentials.project_domain_name
@@ -452,8 +472,9 @@
domain = 'Default'
return cred_client.get_creds_client(client, project_client,
- roles_client,
users_client,
+ roles_client,
+ domains_client,
project_domain_name=domain)
@classmethod
@@ -519,8 +540,7 @@
else:
raise exceptions.InvalidCredentials(
"Invalid credentials type %s" % credential_type)
- return clients.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):
@@ -607,8 +627,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..dc7b434 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
@@ -250,7 +250,7 @@
def test_create_router(self):
- self.fake_client.networks.list_routers.return_value = {'routers': []}
+ self.fake_client.routers.list_routers.return_value = {'routers': []}
self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
return_value=self.fake_client))
@@ -260,7 +260,7 @@
mocked_function.assert_called_once_with(self.fake_object['name'])
def test_create_router_existing(self):
- self.fake_client.networks.list_routers.return_value = {
+ self.fake_client.routers.list_routers.return_value = {
'routers': [self.fake_object]}
self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
return_value=self.fake_client))
@@ -405,7 +405,7 @@
javelin.destroy_routers([self.fake_object])
- mocked_function = self.fake_client.networks.delete_router
+ mocked_function = self.fake_client.routers.delete_router
mocked_function.assert_called_once_with(
self.fake_object['router_id'])
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..be4a6ee 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.common import rest_client
+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 \
@@ -31,6 +31,7 @@
from tempest.services.identity.v2.json import users_client as \
json_users_client
from tempest.services.network.json import network_client as json_network_client
+from tempest.services.network.json import routers_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests import fake_http
@@ -74,7 +75,7 @@
user_fix = self.useFixture(mockpatch.PatchObject(
json_users_client.UsersClient,
'create_user',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'user': {'id': id, 'name': name}}))))
return user_fix
@@ -82,7 +83,7 @@
tenant_fix = self.useFixture(mockpatch.PatchObject(
json_tenants_client.TenantsClient,
'create_tenant',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'tenant': {'id': id, 'name': name}}))))
return tenant_fix
@@ -90,7 +91,7 @@
roles_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'list_roles',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200,
{'roles': [{'id': id, 'name': name},
{'id': '1', 'name': 'FakeRole'},
@@ -101,7 +102,7 @@
roles_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'list_roles',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200,
{'roles': [{'id': '1234', 'name': 'role1'},
{'id': '1', 'name': 'FakeRole'},
@@ -112,7 +113,7 @@
tenant_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'assign_user_role',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {}))))
return tenant_fix
@@ -120,7 +121,7 @@
roles_fix = self.useFixture(mockpatch.PatchObject(
json_roles_client.RolesClient,
'list_roles',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'roles': [{'id': '1',
'name': 'FakeRole'}]}))))
return roles_fix
@@ -129,7 +130,7 @@
ec2_creds_fix = self.useFixture(mockpatch.PatchObject(
json_users_client.UsersClient,
'list_user_ec2_credentials',
- return_value=(service_client.ResponseBody
+ return_value=(rest_client.ResponseBody
(200, {'credentials': [{
'access': 'fake_access',
'secret': 'fake_secret',
@@ -154,12 +155,12 @@
def _mock_router_create(self, id, name):
router_fix = self.useFixture(mockpatch.PatchObject(
- json_network_client.NetworkClient,
+ routers_client.RoutersClient,
'create_router',
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 +175,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 +198,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 +227,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 +267,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 +282,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)
@@ -295,7 +296,7 @@
subnet = mock.patch.object(creds.subnets_admin_client,
'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.network_admin_client,
+ router = mock.patch.object(creds.routers_admin_client,
'delete_router')
router_mock = router.start()
@@ -310,7 +311,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()
@@ -321,7 +322,7 @@
self._mock_subnet_create(creds, '1234', 'fake_subnet')
self._mock_router_create('1234', 'fake_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
primary_creds = creds.get_primary_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -335,7 +336,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'],
@@ -353,7 +354,7 @@
self._mock_subnet_create(creds, '1234', 'fake_subnet')
self._mock_router_create('1234', 'fake_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
creds.get_primary_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -386,11 +387,11 @@
subnet = mock.patch.object(creds.subnets_admin_client,
'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.network_admin_client,
+ router = mock.patch.object(creds.routers_admin_client,
'delete_router')
router_mock = router.start()
remove_router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'remove_router_interface')
return_values = ({'status': 200}, {'ports': []})
port_list_mock = mock.patch.object(creds.ports_admin_client,
@@ -406,7 +407,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 +451,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()
@@ -461,7 +462,7 @@
self._mock_subnet_create(creds, '1234', 'fake_alt_subnet')
self._mock_router_create('1234', 'fake_alt_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
alt_creds = creds.get_alt_creds()
router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
@@ -475,7 +476,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()
@@ -485,7 +486,7 @@
self._mock_subnet_create(creds, '1234', 'fake_admin_subnet')
self._mock_router_create('1234', 'fake_admin_router')
router_interface_mock = self.patch(
- 'tempest.services.network.json.network_client.NetworkClient.'
+ 'tempest.services.network.json.routers_client.RoutersClient.'
'add_router_interface')
self._mock_list_roles('123456', 'admin')
admin_creds = creds.get_admin_creds()
@@ -500,7 +501,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,
@@ -521,7 +522,7 @@
subnet = mock.patch.object(creds.subnets_admin_client,
'delete_subnet')
subnet_mock = subnet.start()
- router = mock.patch.object(creds.network_admin_client,
+ router = mock.patch.object(creds.routers_admin_client,
'delete_router')
router_mock = router.start()
@@ -536,7 +537,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 +555,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 +573,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
deleted file mode 100644
index f248957..0000000
--- a/tempest/tests/common/test_service_clients.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# 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 mock
-import random
-import six
-
-from tempest.services.baremetal.v1.json import baremetal_client
-from tempest.services.data_processing.v1_1 import data_processing_client
-from tempest.services.database.json import flavors_client as db_flavor_client
-from tempest.services.database.json import versions_client as db_version_client
-from tempest.services.identity.v2.json import identity_client as \
- identity_v2_identity_client
-from tempest.services.identity.v3.json import credentials_client
-from tempest.services.identity.v3.json import endpoints_client
-from tempest.services.identity.v3.json import identity_client as \
- identity_v3_identity_client
-from tempest.services.identity.v3.json import policies_client
-from tempest.services.identity.v3.json import regions_client
-from tempest.services.identity.v3.json import services_client
-from tempest.services.image.v1.json import images_client
-from tempest.services.image.v2.json import images_client as images_v2_client
-from tempest.services.messaging.json import messaging_client
-from tempest.services.network.json import network_client
-from tempest.services.object_storage import account_client
-from tempest.services.object_storage import container_client
-from tempest.services.object_storage import object_client
-from tempest.services.orchestration.json import orchestration_client
-from tempest.services.telemetry.json import alarming_client
-from tempest.services.telemetry.json import telemetry_client
-from tempest.services.volume.v1.json.admin import hosts_client \
- as volume_hosts_client
-from tempest.services.volume.v1.json.admin import quotas_client \
- as volume_quotas_client
-from tempest.services.volume.v1.json.admin import services_client \
- as volume_services_client
-from tempest.services.volume.v1.json.admin import types_client \
- as volume_types_client
-from tempest.services.volume.v1.json import availability_zone_client \
- as volume_az_client
-from tempest.services.volume.v1.json import backups_client
-from tempest.services.volume.v1.json import extensions_client \
- as volume_extensions_client
-from tempest.services.volume.v1.json import qos_client
-from tempest.services.volume.v1.json import snapshots_client
-from tempest.services.volume.v1.json import volumes_client
-from tempest.services.volume.v2.json.admin import hosts_client \
- as volume_v2_hosts_client
-from tempest.services.volume.v2.json.admin import quotas_client \
- as volume_v2_quotas_client
-from tempest.services.volume.v2.json.admin import services_client \
- as volume_v2_services_client
-from tempest.services.volume.v2.json.admin import types_client \
- as volume_v2_types_client
-from tempest.services.volume.v2.json import availability_zone_client \
- as volume_v2_az_client
-from tempest.services.volume.v2.json import backups_client \
- as volume_v2_backups_client
-from tempest.services.volume.v2.json import extensions_client \
- as volume_v2_extensions_client
-from tempest.services.volume.v2.json import qos_client as volume_v2_qos_client
-from tempest.services.volume.v2.json import snapshots_client \
- as volume_v2_snapshots_client
-from tempest.services.volume.v2.json import volumes_client as \
- volume_v2_volumes_client
-from tempest.tests import base
-
-
-class TestServiceClient(base.TestCase):
-
- @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,
- data_processing_client.DataProcessingClient,
- db_flavor_client.DatabaseFlavorsClient,
- db_version_client.DatabaseVersionsClient,
- messaging_client.MessagingClient,
- network_client.NetworkClient,
- account_client.AccountClient,
- container_client.ContainerClient,
- object_client.ObjectClient,
- orchestration_client.OrchestrationClient,
- telemetry_client.TelemetryClient,
- alarming_client.AlarmingClient,
- qos_client.QosSpecsClient,
- volume_hosts_client.HostsClient,
- volume_quotas_client.QuotasClient,
- volume_services_client.ServicesClient,
- volume_types_client.TypesClient,
- volume_az_client.AvailabilityZoneClient,
- backups_client.BackupsClient,
- volume_extensions_client.ExtensionsClient,
- snapshots_client.SnapshotsClient,
- volumes_client.VolumesClient,
- volume_v2_hosts_client.HostsClient,
- volume_v2_quotas_client.QuotasClient,
- volume_v2_services_client.ServicesClient,
- volume_v2_types_client.TypesClient,
- volume_v2_az_client.AvailabilityZoneClient,
- volume_v2_backups_client.BackupsClient,
- volume_v2_extensions_client.ExtensionsClient,
- volume_v2_qos_client.QosSpecsClient,
- volume_v2_snapshots_client.SnapshotsClient,
- volume_v2_volumes_client.VolumesClient,
- identity_v2_identity_client.IdentityClient,
- credentials_client.CredentialsClient,
- endpoints_client.EndPointClient,
- identity_v3_identity_client.IdentityV3Client,
- policies_client.PoliciesClient,
- regions_client.RegionsClient,
- services_client.ServicesClient,
- images_client.ImagesClient,
- images_v2_client.ImagesClientV2
- ]
-
- for client in test_clients:
- fake_string = six.text_type(random.randint(1, 0x7fffffff))
- auth = 'auth' + fake_string
- service = 'service' + fake_string
- region = 'region' + fake_string
- params = {
- 'endpoint_type': 'URL' + fake_string,
- 'build_interval': random.randint(1, 100),
- 'build_timeout': random.randint(1, 100),
- 'disable_ssl_certificate_validation':
- True if random.randint(0, 1) else False,
- 'ca_certs': None,
- 'trace_requests': 'foo' + fake_string
- }
- client(auth, service, region, **params)
- mock_init.assert_called_once_with(auth, service, region, **params)
- mock_init.reset_mock()
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index c7cc638..492bdca 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -20,6 +20,7 @@
from tempest import exceptions
from tempest.services.volume.base import base_volumes_client
from tempest.tests import base
+import tempest.tests.utils as utils
class TestImageWaiters(base.TestCase):
@@ -37,17 +38,24 @@
# Ensure waiter returns before build_timeout
self.assertTrue((end_time - start_time) < 10)
- def test_wait_for_image_status_timeout(self):
+ @mock.patch('time.sleep')
+ def test_wait_for_image_status_timeout(self, mock_sleep):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
self.client.show_image.return_value = ({'status': 'saving'})
self.assertRaises(exceptions.TimeoutException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
+ mock_sleep.assert_called_once_with(1)
- def test_wait_for_image_status_error_on_image_create(self):
+ @mock.patch('time.sleep')
+ def test_wait_for_image_status_error_on_image_create(self, mock_sleep):
self.client.show_image.return_value = ({'status': 'ERROR'})
self.assertRaises(exceptions.AddImageException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
+ mock_sleep.assert_called_once_with(1)
@mock.patch.object(time, 'sleep')
def test_wait_for_volume_status_error_restoring(self, mock_sleep):
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/tests/lib/cli/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/tests/lib/common/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/common/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/common/utils/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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..8095453
--- /dev/null
+++ b/tempest/tests/lib/fake_auth_provider.py
@@ -0,0 +1,35 @@
+# 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, fake_base_url=None):
+ creds_dict = creds_dict or {}
+ self.credentials = FakeCredentials(creds_dict)
+ self.fake_base_url = fake_base_url
+
+ 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 self.fake_base_url or "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/api/messaging/__init__.py b/tempest/tests/lib/services/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/services/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/services/compute/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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..fc6c1d2
--- /dev/null
+++ b/tempest/tests/lib/services/compute/test_versions_client.py
@@ -0,0 +1,135 @@
+# 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)
+
+ def _test_get_base_version_url(self, url, expected_base_url):
+ auth = fake_auth_provider.FakeAuthProvider(fake_base_url=url)
+ client = versions_client.VersionsClient(auth, 'compute', 'regionOne')
+ self.assertEqual(expected_base_url, client._get_base_version_url())
+
+ def test_get_base_version_url(self):
+ self._test_get_base_version_url('https://bar.org/v2/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v2.1/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v2.15/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v22.2/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/v22/123',
+ 'https://bar.org/')
+
+ def test_get_base_version_url_app_name(self):
+ self._test_get_base_version_url('https://bar.org/compute/v2/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v2.1/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v2.15/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v22.2/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute/v22/123',
+ 'https://bar.org/compute/')
+
+ def test_get_base_version_url_double_slash(self):
+ self._test_get_base_version_url('https://bar.org//v2/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org//v2.1/123',
+ 'https://bar.org/')
+ self._test_get_base_version_url('https://bar.org/compute//v2/123',
+ 'https://bar.org/compute/')
+ self._test_get_base_version_url('https://bar.org/compute//v2.1/123',
+ 'https://bar.org/compute/')
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/api/messaging/__init__.py b/tempest/tests/lib/services/identity/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to tempest/tests/lib/services/identity/__init__.py
diff --git a/tempest/api/messaging/__init__.py b/tempest/tests/lib/services/identity/v2/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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/api/messaging/__init__.py b/tempest/tests/lib/services/identity/v3/__init__.py
similarity index 100%
copy from tempest/api/messaging/__init__.py
copy to 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..ebcfe82
--- /dev/null
+++ b/tempest/tests/lib/test_auth.py
@@ -0,0 +1,532 @@
+# 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_base_url_with_unversioned_endpoint(self):
+ auth_data = {
+ 'serviceCatalog': [
+ {
+ 'type': 'identity',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'publicURL': 'http://fake_url'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v2.0'
+ }
+
+ expected = 'http://fake_url/v2.0'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
+ 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)
+
+ # Overwrites v2 test
+ def test_base_url_with_unversioned_endpoint(self):
+ auth_data = {
+ 'catalog': [
+ {
+ 'type': 'identity',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'url': 'http://fake_url',
+ 'interface': 'public'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v3'
+ }
+
+ expected = 'http://fake_url/v3'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
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..87af455
--- /dev/null
+++ b/tempest/tests/lib/test_rest_client.py
@@ -0,0 +1,1075 @@
+# 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
+import tempest.tests.utils as utils
+
+
+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
+ timeout = 1
+ self.rest_client.build_timeout = timeout
+
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(timeout)
+
+ self.assertRaises(exceptions.TimeoutException,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
+
+ # time.time() should be called twice, first to start the timer
+ # and then to compute the timedelta
+ self.assertEqual(2, time_mock.call_count)
+
+ 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..f6efd47
--- /dev/null
+++ b/tempest/tests/lib/test_ssh.py
@@ -0,0 +1,260 @@
+# 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 mock
+import six
+import testtools
+
+from tempest.lib.common import ssh
+from tempest.lib import exceptions
+from tempest.tests.lib import base
+import tempest.tests.utils as utils
+
+
+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)
+
+ @mock.patch('time.sleep')
+ def test_get_ssh_connection_two_attemps(self, sleep_mock):
+ 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)
+ client._get_ssh_connection(sleep=1)
+ # We slept 2 seconds: because sleep is "1" and backoff is "1" too
+ sleep_mock.assert_called_once_with(2)
+ self.assertEqual(2, client_mock.connect.call_count)
+
+ def test_get_ssh_connection_timeout(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+
+ timeout = 2
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(timeout + 1)
+
+ c_mock.return_value = client_mock
+ client_mock.connect.side_effect = [
+ socket.error,
+ socket.error,
+ socket.error,
+ ]
+
+ client = ssh.Client('localhost', 'root', timeout=timeout)
+ # We need to mock LOG here because LOG.info() calls time.time()
+ # in order to preprend a timestamp.
+ with mock.patch.object(ssh, 'LOG'):
+ self.assertRaises(exceptions.SSHTimeout,
+ client._get_ssh_connection)
+
+ # time.time() should be called twice, first to start the timer
+ # and then to compute the timedelta
+ self.assertEqual(2, time_mock.call_count)
+
+ @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/tests/utils.py b/tempest/tests/utils.py
new file mode 100644
index 0000000..9c3049d
--- /dev/null
+++ b/tempest/tests/utils.py
@@ -0,0 +1,29 @@
+# Copyright 2016 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.
+#
+
+
+def generate_timeout_series(timeout):
+ """Generate a series of times that exceeds the given timeout.
+
+ Yields a series of fake time.time() floating point numbers
+ such that the difference between each pair in the series just
+ exceeds the timeout value that is passed in. Useful for
+ mocking time.time() in methods that otherwise wait for timeout
+ seconds.
+ """
+ iteration = 0
+ while True:
+ iteration += 1
+ yield (iteration * timeout) + iteration
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..be3a4f1 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..28dfe8b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -70,14 +70,6 @@
find . -type f -name "*.pyc" -delete
bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
-[testenv:large-ops]
-sitepackages = {[tempestenv]sitepackages}
-setenv = {[tempestenv]setenv}
-deps = {[tempestenv]deps}
-commands =
- find . -type f -name "*.pyc" -delete
- python setup.py testr --slowest --testr-args='tempest.scenario.test_large_ops {posargs}'
-
[testenv:smoke]
sitepackages = {[tempestenv]sitepackages}
setenv = {[tempestenv]setenv}
@@ -132,3 +124,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