tempest-17.2.0/0000775000175100017510000000000013207045130013301 5ustar zuulzuul00000000000000tempest-17.2.0/bindep.txt0000666000175100017510000000060213207044712015310 0ustar zuulzuul00000000000000# This file contains runtime (non-python) dependencies # More info at: https://docs.openstack.org/infra/bindep/readme.html libffi-dev [platform:dpkg] libffi-devel [platform:rpm] gcc [platform:rpm] gcc [platform:dpkg] python-dev [platform:dpkg] python-devel [platform:rpm] python3-dev [platform:dpkg] python3-devel [platform:rpm] openssl-devel [platform:rpm] libssl-dev [platform:dpkg] tempest-17.2.0/README.rst0000666000175100017510000002603513207044725015011 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/tempest.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Tempest - The OpenStack Integration Test Suite ============================================== The documentation for Tempest is officially hosted at: https://docs.openstack.org/tempest/latest/ This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ----------------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node DevStack install, a 20 node LXC cloud, or a 1000 node KVM cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public OpenStack APIs. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there are some features of OpenStack that are not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self-testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. Where the configuration file lives and how you interact with it depends on how you'll be running Tempest. There are 2 methods of using Tempest. The first, which is a newer and recommended workflow treats Tempest as a system installed program. The second older method is to run Tempest assuming your working dir is the actually Tempest source repo, and there are a number of assumptions related to that. For this section we'll only cover the newer method as it is simpler, and quicker to work with. #. You first need to install Tempest. This is done with pip after you check out the Tempest repo:: $ git clone https://git.openstack.org/openstack/tempest $ pip install tempest/ This can be done within a venv, but the assumption for this guide is that the Tempest CLI entry point will be in your shell's PATH. #. Installing Tempest may create a ``/etc/tempest dir``, however if one isn't created you can create one or use ``~/.tempest/etc`` or ``~/.config/tempest`` in place of ``/etc/tempest``. If none of these dirs are created Tempest will create ``~/.tempest/etc`` when it's needed. The contents of this dir will always automatically be copied to all ``etc/`` dirs in local workspaces as an initial setup step. So if there is any common configuration you'd like to be shared between local Tempest workspaces it's recommended that you pre-populate it before running ``tempest init``. #. Setup a local Tempest workspace. This is done by using the tempest init command:: $ tempest init cloud-01 which also works the same as:: $ mkdir cloud-01 && cd cloud-01 && tempest init This will create a new directory for running a single Tempest configuration. If you'd like to run Tempest against multiple OpenStack deployments the idea is that you'll create a new working directory for each to maintain separate configuration files and local artifact storage for each. #. Then ``cd`` into the newly created working dir and also modify the local config files located in the ``etc/`` subdir created by the ``tempest init`` command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a sample exists you must rename or copy it to tempest.conf before making any changes to it otherwise Tempest will not know how to load it. For details on configuring Tempest refer to the :ref:`tempest-configuration`. #. Once the configuration is done you're now ready to run Tempest. This can be done using the :ref:`tempest_run` command. This can be done by either running:: $ tempest run from the Tempest workspace directory. Or you can use the ``--workspace`` argument to run in the workspace you created regardless of your current working directory. For example:: $ tempest run --workspace cloud-01 There is also the option to use testr directly, or any `testr`_ based test runner, like `ostestr`_. For example, from the workspace dir run:: $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' will run the same set of tests as the default gate jobs. .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html .. _ostestr: https://docs.openstack.org/os-testr/latest/ 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 Release Notes `_ shows what changes have been released on each version. 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 ------------- Detailed configuration of Tempest is beyond the scope of this document see :ref:`tempest-configuration` for more details on configuring Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting version of the configuration. You can generate a new sample tempest.conf file, run the following command from the top level of the Tempest directory:: $ tox -e genconfig The most important pieces that are needed are the user ids, OpenStack endpoints, and basic flavors and images needed to run tests. Unit Tests ---------- Tempest also has a set of unit tests which test the Tempest code itself. These tests can be run by specifying the test discovery path:: $ stestr --test-path ./tempest/tests run By setting ``--test-path`` option to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of ``test_path`` is ``test_path=./tempest/test_discover`` which will only run test discover on the Tempest suite. Alternatively, there are the py27 and py35 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Starting in the Kilo release the OpenStack services dropped all support for python 2.6. This change has been mirrored in Tempest, starting after the tempest-2 tag. This means that proposed changes to Tempest which only fix python 2.6 compatibility will be rejected, and moving forward more features not present in python 2.6 will be used. If you're running your OpenStack services on an earlier release with python 2.6 you can easily run Tempest against it from a remote system running python 2.7. (or deploy a cloud guest in your cloud that has python 2.7) Python 3.x ---------- Starting during the Pike cycle Tempest has a gating CI job that runs Tempest with Python 3. Any Tempest release after 15.0.0 should fully support running under Python 3 as well as Python 2.7. Legacy run method ----------------- The legacy method of running Tempest is to just treat the Tempest source code as a python unittest repository and run directly from the source repo. When running in this way you still start with a Tempest config file and the steps are basically the same except that it expects you know where the Tempest code lives on your system and requires a bit more manual interaction to get Tempest running. For example, when running Tempest this way things like a lock file directory do not get generated automatically and the burden is on the user to create and configure that. To start you need to create a configuration file. The easiest way to create a configuration file is to generate a sample in the ``etc/`` directory :: $ cd $TEMPEST_ROOT_DIR $ oslo-config-generator --config-file \ tempest/cmd/config-generator.tempest.conf \ --output-file etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running DevStack environment, Tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your DevStack installation. Tempest is not tied to any single test runner, but `testr`_ is the most commonly used tool. Also, the nosetests test runner is **not** recommended to run Tempest. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $ testr run --parallel To run one single test serially :: $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Tox also contains several existing job configurations. For example:: $ tox -e full which will run the same set of tests as the OpenStack gate. (it's exactly how the gate invokes Tempest) Or:: $ tox -e smoke to run the tests tagged as smoke. tempest-17.2.0/.testr.conf0000666000175100017510000000073113207044712015377 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \ OS_TEST_LOCK_PATH=${OS_TEST_LOCK_PATH:-${TMPDIR:-'/tmp'}} \ ${PYTHON:-python} -m subunit.run discover -t ${OS_TOP_LEVEL:-./} ${OS_TEST_PATH:-./tempest/test_discover} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list group_regex=([^\.]*\.)* tempest-17.2.0/requirements.txt0000666000175100017510000000155713207044712016604 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 jsonschema<3.0.0,>=2.6.0 # MIT testtools>=2.2.0 # MIT paramiko>=2.0.0 # LGPLv2.1+ netaddr>=0.7.18 # BSD testrepository>=0.0.18 # Apache-2.0/BSD oslo.concurrency>=3.20.0 # Apache-2.0 oslo.config>=4.6.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.31.0 # Apache-2.0 six>=1.10.0 # MIT fixtures>=3.0.0 # Apache-2.0/BSD PyYAML>=3.10 # MIT python-subunit>=1.0.0 # Apache-2.0/BSD stevedore>=1.20.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD os-testr>=1.0.0 # Apache-2.0 urllib3>=1.21.1 # MIT debtcollector>=1.2.0 # Apache-2.0 unittest2>=1.1.0 # BSD tempest-17.2.0/PKG-INFO0000664000175100017510000003364413207045130014410 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: tempest Version: 17.2.0 Summary: OpenStack Integration Testing Home-page: https://docs.openstack.org/tempest/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/tempest.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Tempest - The OpenStack Integration Test Suite ============================================== The documentation for Tempest is officially hosted at: https://docs.openstack.org/tempest/latest/ This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ----------------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node DevStack install, a 20 node LXC cloud, or a 1000 node KVM cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public OpenStack APIs. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there are some features of OpenStack that are not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self-testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. Where the configuration file lives and how you interact with it depends on how you'll be running Tempest. There are 2 methods of using Tempest. The first, which is a newer and recommended workflow treats Tempest as a system installed program. The second older method is to run Tempest assuming your working dir is the actually Tempest source repo, and there are a number of assumptions related to that. For this section we'll only cover the newer method as it is simpler, and quicker to work with. #. You first need to install Tempest. This is done with pip after you check out the Tempest repo:: $ git clone https://git.openstack.org/openstack/tempest $ pip install tempest/ This can be done within a venv, but the assumption for this guide is that the Tempest CLI entry point will be in your shell's PATH. #. Installing Tempest may create a ``/etc/tempest dir``, however if one isn't created you can create one or use ``~/.tempest/etc`` or ``~/.config/tempest`` in place of ``/etc/tempest``. If none of these dirs are created Tempest will create ``~/.tempest/etc`` when it's needed. The contents of this dir will always automatically be copied to all ``etc/`` dirs in local workspaces as an initial setup step. So if there is any common configuration you'd like to be shared between local Tempest workspaces it's recommended that you pre-populate it before running ``tempest init``. #. Setup a local Tempest workspace. This is done by using the tempest init command:: $ tempest init cloud-01 which also works the same as:: $ mkdir cloud-01 && cd cloud-01 && tempest init This will create a new directory for running a single Tempest configuration. If you'd like to run Tempest against multiple OpenStack deployments the idea is that you'll create a new working directory for each to maintain separate configuration files and local artifact storage for each. #. Then ``cd`` into the newly created working dir and also modify the local config files located in the ``etc/`` subdir created by the ``tempest init`` command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a sample exists you must rename or copy it to tempest.conf before making any changes to it otherwise Tempest will not know how to load it. For details on configuring Tempest refer to the :ref:`tempest-configuration`. #. Once the configuration is done you're now ready to run Tempest. This can be done using the :ref:`tempest_run` command. This can be done by either running:: $ tempest run from the Tempest workspace directory. Or you can use the ``--workspace`` argument to run in the workspace you created regardless of your current working directory. For example:: $ tempest run --workspace cloud-01 There is also the option to use testr directly, or any `testr`_ based test runner, like `ostestr`_. For example, from the workspace dir run:: $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' will run the same set of tests as the default gate jobs. .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html .. _ostestr: https://docs.openstack.org/os-testr/latest/ 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 Release Notes `_ shows what changes have been released on each version. 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 ------------- Detailed configuration of Tempest is beyond the scope of this document see :ref:`tempest-configuration` for more details on configuring Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting version of the configuration. You can generate a new sample tempest.conf file, run the following command from the top level of the Tempest directory:: $ tox -e genconfig The most important pieces that are needed are the user ids, OpenStack endpoints, and basic flavors and images needed to run tests. Unit Tests ---------- Tempest also has a set of unit tests which test the Tempest code itself. These tests can be run by specifying the test discovery path:: $ stestr --test-path ./tempest/tests run By setting ``--test-path`` option to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of ``test_path`` is ``test_path=./tempest/test_discover`` which will only run test discover on the Tempest suite. Alternatively, there are the py27 and py35 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Starting in the Kilo release the OpenStack services dropped all support for python 2.6. This change has been mirrored in Tempest, starting after the tempest-2 tag. This means that proposed changes to Tempest which only fix python 2.6 compatibility will be rejected, and moving forward more features not present in python 2.6 will be used. If you're running your OpenStack services on an earlier release with python 2.6 you can easily run Tempest against it from a remote system running python 2.7. (or deploy a cloud guest in your cloud that has python 2.7) Python 3.x ---------- Starting during the Pike cycle Tempest has a gating CI job that runs Tempest with Python 3. Any Tempest release after 15.0.0 should fully support running under Python 3 as well as Python 2.7. Legacy run method ----------------- The legacy method of running Tempest is to just treat the Tempest source code as a python unittest repository and run directly from the source repo. When running in this way you still start with a Tempest config file and the steps are basically the same except that it expects you know where the Tempest code lives on your system and requires a bit more manual interaction to get Tempest running. For example, when running Tempest this way things like a lock file directory do not get generated automatically and the burden is on the user to create and configure that. To start you need to create a configuration file. The easiest way to create a configuration file is to generate a sample in the ``etc/`` directory :: $ cd $TEMPEST_ROOT_DIR $ oslo-config-generator --config-file \ tempest/cmd/config-generator.tempest.conf \ --output-file etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running DevStack environment, Tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your DevStack installation. Tempest is not tied to any single test runner, but `testr`_ is the most commonly used tool. Also, the nosetests test runner is **not** recommended to run Tempest. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $ testr run --parallel To run one single test serially :: $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Tox also contains several existing job configurations. For example:: $ tox -e full which will run the same set of tests as the OpenStack gate. (it's exactly how the gate invokes Tempest) Or:: $ tox -e smoke to run the tests tagged as smoke. Platform: UNKNOWN Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 tempest-17.2.0/tox.ini0000666000175100017510000001402413207044712014624 0ustar zuulzuul00000000000000[tox] envlist = pep8,py35,py27,pip-check-reqs minversion = 2.3.1 skipsdist = True [tempestenv] sitepackages = False setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./tempest/test_discover BRANCH_NAME=master CLIENT_NAME=tempest deps = -r{toxinidir}/requirements.txt [testenv] setenv = VIRTUAL_ENV={envdir} OS_LOG_CAPTURE=1 PYTHONWARNINGS=default::DeprecationWarning BRANCH_NAME=master CLIENT_NAME=tempest passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST usedevelop = True install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} whitelist_externals = * deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete stestr --test-path ./tempest/tests run {posargs} [testenv:genconfig] commands = oslo-config-generator --config-file tempest/cmd/config-generator.tempest.conf [testenv:cover] commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}' [testenv:all] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} # 'all' includes slow tests setenv = {[tempestenv]setenv} OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200} deps = {[tempestenv]deps} commands = find . -type f -name "*.pyc" -delete tempest run --regex {posargs} [testenv:ostestr] sitepackages = {[tempestenv]sitepackages} # 'all' includes slow tests setenv = {[tempestenv]setenv} OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200} deps = {[tempestenv]deps} commands = find . -type f -name "*.pyc" -delete ostestr {posargs} [testenv:all-plugin] sitepackages = True # 'all' includes slow tests setenv = {[tempestenv]setenv} OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:1200} deps = {[tempestenv]deps} commands = find . -type f -name "*.pyc" -delete tempest run --regex {posargs} [testenv:full] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # The regex below is used to select which tests to run and exclude the slow tag: # See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610 commands = find . -type f -name "*.pyc" -delete tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs} tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' {posargs} [testenv:full-serial] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # The regex below is used to select which tests to run and exclude the slow tag: # See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610 commands = find . -type f -name "*.pyc" -delete tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs} [testenv:scenario] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # The regex below is used to select all scenario tests commands = find . -type f -name "*.pyc" -delete tempest run --serial --regex '(^tempest\.scenario)' {posargs} [testenv:smoke] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} commands = find . -type f -name "*.pyc" -delete tempest run --regex '\[.*\bsmoke\b.*\]' {posargs} [testenv:smoke-serial] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} # This is still serial because neutron doesn't work with parallel. See: # https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke # job would fail if we moved it to parallel. commands = find . -type f -name "*.pyc" -delete tempest run --serial --regex '\[.*\bsmoke\b.*\]' {posargs} [testenv:venv] commands = {posargs} [testenv:venv-tempest] envdir = .tox/tempest sitepackages = {[tempestenv]sitepackages} setenv = {[tempestenv]setenv} deps = {[tempestenv]deps} commands = {posargs} [testenv:docs] commands = rm -rf doc/build python setup.py build_sphinx {posargs} [testenv:pep8] commands = flake8 {posargs} check-uuid [testenv:uuidgen] commands = check-uuid --fix [hacking] local-check-factory = tempest.hacking.checks.factory import_exceptions = tempest.services [flake8] # E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/ # E123 skipped because it is ignored by default in the default pep8 # E129 skipped because it is too limiting when combined with other rules ignore = E125,E123,E129 show-source = True exclude = .git,.venv,.tox,dist,doc,*egg,build enable-extensions = H106,H203,H904 import-order-style = pep8 [testenv:releasenotes] commands = rm -rf releasenotes/build sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:pip-check-reqs] # Do not install test-requirements as that will pollute the virtualenv for # determining missing packages. # This also means that pip-check-reqs must be installed separately, outside # of the requirements.txt files deps = pip_check_reqs -r{toxinidir}/requirements.txt commands= pip-extra-reqs -d --ignore-file=tempest/tests/* tempest pip-missing-reqs -d --ignore-file=tempest/tests/* tempest [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files. deps = bindep commands = bindep test [testenv:plugin-sanity-check] # perform tempest plugin sanity whitelist_externals = bash commands = bash tools/tempest-plugin-sanity.sh tempest-17.2.0/etc/0000775000175100017510000000000013207045130014054 5ustar zuulzuul00000000000000tempest-17.2.0/etc/whitelist.yaml0000666000175100017510000000000013207044712016751 0ustar zuulzuul00000000000000tempest-17.2.0/etc/logging.conf.sample0000666000175100017510000000107013207044712017636 0ustar zuulzuul00000000000000[loggers] keys=root [handlers] keys=file,devel,syslog [formatters] keys=simple,tests [logger_root] level=DEBUG handlers=file [handler_file] class=FileHandler level=DEBUG args=('tempest.log', 'w+') formatter=tests [handler_syslog] class=handlers.SysLogHandler level=ERROR args = ('/dev/log', handlers.SysLogHandler.LOG_USER) [handler_devel] class=StreamHandler level=DEBUG args=(sys.stdout,) formatter=simple [formatter_tests] class = oslo_log.formatters.ContextFormatter [formatter_simple] format=%(asctime)s.%(msecs)03d %(process)d %(levelname)s: %(message)s tempest-17.2.0/etc/accounts.yaml.sample0000666000175100017510000000370413207044712020052 0ustar zuulzuul00000000000000# The number of accounts required can be estimated as CONCURRENCY x 2 # It is expected that each user provided here will be in a different tenant. # This is required to provide isolation between test for running in parallel # # Valid fields for credentials are defined in the descendants of # lib.auth.Credentials - see KeystoneV[2|3]Credentials.ATTRIBUTES # # The fields in KeystoneV3Credentials behave as follows: # # tenant_[id|name] also sets project_[id|name]. # # project_[id|name] also sets tenant_[id|name]. # # Providing distinct values for both tenant_[id|name] and project_[id|name] # will result in an InvalidCredentials exception. # # The value of project_domain_[id|name] is used for user_domain_[id|name] if # the latter is not specified. # # The value of user_domain_[id|name] is used for project_domain_[id|name] if # the latter is not specified. # # The value of domain_[id|name] is used for project_domain_[id|name] if not # specified and user_domain_[id|name] if not specified. - username: 'user_1' tenant_name: 'test_tenant_1' password: 'test_password' - username: 'user_2' tenant_name: 'test_tenant_2' password: 'test_password' # To specify which roles a user has list them under the roles field - username: 'multi_role_user' tenant_name: 'test_tenant_42' password: 'test_password' roles: - 'fun_role' - 'not_an_admin' - 'an_admin' # To specify a user has a role specified in the config file you can use the # type field to specify it, valid values are admin, operator, and reseller_admin - username: 'swift_pseudo_admin_user_1' tenant_name: 'admin_tenant_1' password: 'test_password' types: - 'reseller_admin' - 'operator' # Networks can be specified to tell tempest which network it should use when # creating servers with an account - username: 'admin_user_1' tenant_name: 'admin_tenant_1' password: 'test_password' types: - 'admin' resources: network: 'public' router: 'admin_tenant_1-router' tempest-17.2.0/setup.cfg0000666000175100017510000000362213207045130015127 0ustar zuulzuul00000000000000[metadata] name = tempest summary = OpenStack Integration Testing description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/tempest/latest/ classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = tempest data_files = etc/tempest = etc/* [entry_points] console_scripts = verify-tempest-config = tempest.cmd.verify_tempest_config: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 subunit-describe-calls = tempest.cmd.subunit_describe_calls:entry_point tempest.cm = account-generator = tempest.cmd.account_generator:TempestAccountGenerator init = tempest.cmd.init:TempestInit cleanup = tempest.cmd.cleanup:TempestCleanup list-plugins = tempest.cmd.list_plugins:TempestListPlugins verify-config = tempest.cmd.verify_tempest_config:TempestVerifyConfig workspace_register = tempest.cmd.workspace:TempestWorkspaceRegister workspace_rename = tempest.cmd.workspace:TempestWorkspaceRename workspace_move = tempest.cmd.workspace:TempestWorkspaceMove workspace_remove = tempest.cmd.workspace:TempestWorkspaceRemove workspace_list = tempest.cmd.workspace:TempestWorkspaceList run = tempest.cmd.run:TempestRun oslo.config.opts = tempest.config = tempest.config:list_opts [build_sphinx] all-files = 1 warning-is-error = 1 build-dir = doc/build source-dir = doc/source [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tempest-17.2.0/tools/0000775000175100017510000000000013207045130014441 5ustar zuulzuul00000000000000tempest-17.2.0/tools/skip_tracker.py0000777000175100017510000000174313207044712017513 0ustar zuulzuul00000000000000#!/usr/bin/env python # 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 """ from tempest.lib.cmd import skip_tracker if __name__ == '__main__': print("DEPRECATED: `skip_tracker.py` is already deprecated, " "use `skip-tracker` command instead.") skip_tracker.main() tempest-17.2.0/tools/generate-tempest-plugins-list.py0000666000175100017510000000566413207044712022736 0ustar zuulzuul00000000000000#! /usr/bin/env python # Copyright 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. # This script is intended to be run as part of a periodic proposal bot # job in OpenStack infrastructure. # # In order to function correctly, the environment in which the # script runs must have # * network access to the review.openstack.org Gerrit API # working directory # * network access to https://git.openstack.org/cgit import json import re try: # For Python 3.0 and later from urllib.error import HTTPError as HTTPError import urllib.request as urllib except ImportError: # Fall back to Python 2's urllib2 import urllib2 as urllib from urllib2 import HTTPError as HTTPError url = 'https://review.openstack.org/projects/' # This is what a project looks like ''' "openstack-attic/akanda": { "id": "openstack-attic%2Fakanda", "state": "READ_ONLY" }, ''' def is_in_openstack_namespace(proj): return proj.startswith('openstack/') # Rather than returning a 404 for a nonexistent file, cgit delivers a # 0-byte response to a GET request. It also does not provide a # Content-Length in a HEAD response, so the way we tell if a file exists # is to check the length of the entire GET response body. def has_tempest_plugin(proj): try: r = urllib.urlopen( "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj) except HTTPError as err: if err.code == 404: return False p = re.compile('^tempest\.test_plugins', re.M) if p.findall(r.read().decode('utf-8')): return True else: False r = urllib.urlopen(url) # Gerrit prepends 4 garbage octets to the JSON, in order to counter # cross-site scripting attacks. Therefore we must discard it so the # json library won't choke. projects = sorted(filter(is_in_openstack_namespace, json.loads(r.read()[4:]))) # Retrieve projects having no deb, ui or spec namespace as those namespaces # do not contains tempest plugins. projects_list = [i for i in projects if not (i.startswith('openstack/deb-') or i.endswith('-ui') or i.endswith('-specs'))] found_plugins = list(filter(has_tempest_plugin, projects_list)) # Every element of the found_plugins list begins with "openstack/". # We drop those initial 10 octets when printing the list. for project in found_plugins: print(project[10:]) tempest-17.2.0/tools/generate-tempest-plugins-list.sh0000777000175100017510000000451013207044712022710 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Copyright 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. # This script is intended to be run as a periodic proposal bot job # in OpenStack infrastructure, though you can run it as a one-off. # # In order to function correctly, the environment in which the # script runs must have # * a writable doc/source directory relative to the current # working directory # AND ( ( # * git # * all git repos meant to be searched for plugins cloned and # at the desired level of up-to-datedness # * the environment variable git_dir pointing to the location # * of said git repositories # ) OR ( # * network access to the review.openstack.org Gerrit API # working directory # * network access to https://git.openstack.org/cgit # )) # # If a file named doc/source/data/tempest-plugins-registry.header or # doc/source/data/tempest-plugins-registry.footer is found relative to the # current working directory, it will be prepended or appended to # the generated reStructuredText plugins table respectively. set -ex ( declare -A plugins if [[ -r doc/source/data/tempest-plugins-registry.header ]]; then cat doc/source/data/tempest-plugins-registry.header fi sorted_plugins=$(python tools/generate-tempest-plugins-list.py) for k in ${sorted_plugins}; do project=${k:0:28} giturl="git://git.openstack.org/openstack/${k:0:26}" printf "|%-28s|%-73s|\n" "${project}" "${giturl}" printf "+----------------------------+-------------------------------------------------------------------------+\n" done if [[ -r doc/source/data/tempest-plugins-registry.footer ]]; then cat doc/source/data/tempest-plugins-registry.footer fi ) > doc/source/plugin-registry.rst if [[ -n ${1} ]]; then cp doc/source/plugin-registry.rst ${1}/doc/source/plugin-registry.rst fi tempest-17.2.0/tools/tox_install.sh0000777000175100017510000000201213207044712017342 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Client constraint file contains this client version pin that is in conflict # with installing the client from source. We should remove the version pin in # the constraints file before applying it for from-source installation. CONSTRAINTS_FILE=$1 shift 1 set -e # NOTE(tonyb): Place this in the tox enviroment's log dir so it will get # published to logs.openstack.org for easy debugging. localfile="$VIRTUAL_ENV/log/upper-constraints.txt" if [[ $CONSTRAINTS_FILE != http* ]]; then CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE fi # NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile pip install -c$localfile openstack-requirements # This is the main purpose of the script: Allow local installation of # the current repo. It is listed in constraints file and thus any # install will be constrained and we need to unconstrain it. edit-constraints $localfile -- $CLIENT_NAME pip install -c$localfile -U $* exit $? tempest-17.2.0/tools/check_logs.py0000777000175100017510000001407113207044712017131 0ustar zuulzuul00000000000000#!/usr/bin/env python # Copyright 2013 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import gzip import os import re import sys import six import six.moves.urllib.request as urlreq import yaml # DEVSTACK_GATE_GRENADE is either unset if grenade is not running # or a string describing what type of grenade run to perform. is_grenade = os.environ.get('DEVSTACK_GATE_GRENADE') is not None dump_all_errors = True # As logs are made clean, remove from this set allowed_dirty = set([ 'c-api', 'ceilometer-acentral', 'ceilometer-acompute', 'ceilometer-alarm-evaluator', 'ceilometer-anotification', 'ceilometer-api', 'ceilometer-collector', 'c-vol', 'g-api', 'h-api', 'h-eng', 'ir-cond', 'n-api', 'n-cpu', 'n-net', 'q-agt', 'q-dhcp', 'q-lbaas', 'q-meta', 'q-metering', 'q-svc', 's-proxy']) def process_files(file_specs, url_specs, whitelists): regexp = re.compile(r"^.* (ERROR|CRITICAL|TRACE) .*\[.*\-.*\]") logs_with_errors = [] for (name, filename) in file_specs: whitelist = whitelists.get(name, []) with open(filename) as content: if scan_content(name, content, regexp, whitelist): logs_with_errors.append(name) for (name, url) in url_specs: whitelist = whitelists.get(name, []) req = urlreq.Request(url) req.add_header('Accept-Encoding', 'gzip') page = urlreq.urlopen(req) buf = six.StringIO(page.read()) f = gzip.GzipFile(fileobj=buf) if scan_content(name, f.read().splitlines(), regexp, whitelist): logs_with_errors.append(name) return logs_with_errors def scan_content(name, content, regexp, whitelist): had_errors = False for line in content: if not line.startswith("Stderr:") and regexp.match(line): whitelisted = False for w in whitelist: pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'), w['message']) if re.match(pat, line): whitelisted = True break if not whitelisted or dump_all_errors: if not whitelisted: had_errors = True return had_errors def collect_url_logs(url): page = urlreq.urlopen(url) content = page.read() logs = re.findall('(screen-[\w-]+\.txt\.gz)', content) return logs def main(opts): if opts.directory and opts.url or not (opts.directory or opts.url): print("Must provide exactly one of -d or -u") return 1 print("Checking logs...") WHITELIST_FILE = os.path.join( os.path.abspath(os.path.dirname(os.path.dirname(__file__))), "etc", "whitelist.yaml") file_matcher = re.compile(r".*screen-([\w-]+)\.log") files = [] if opts.directory: d = opts.directory for f in os.listdir(d): files.append(os.path.join(d, f)) files_to_process = [] for f in files: m = file_matcher.match(f) if m: files_to_process.append((m.group(1), f)) url_matcher = re.compile(r".*screen-([\w-]+)\.txt\.gz") urls = [] if opts.url: for logfile in collect_url_logs(opts.url): urls.append("%s/%s" % (opts.url, logfile)) urls_to_process = [] for u in urls: m = url_matcher.match(u) if m: urls_to_process.append((m.group(1), u)) whitelists = {} with open(WHITELIST_FILE) as stream: loaded = yaml.safe_load(stream) if loaded: for (name, l) in six.iteritems(loaded): for w in l: assert 'module' in w, 'no module in %s' % name assert 'message' in w, 'no message in %s' % name whitelists = loaded logs_with_errors = process_files(files_to_process, urls_to_process, whitelists) failed = False if logs_with_errors: log_files = set(logs_with_errors) for log in log_files: msg = '%s log file has errors' % log if log not in allowed_dirty: msg += ' and is not allowed to have them' failed = True print(msg) print("\nPlease check the respective log files to see the errors") if failed: if is_grenade: print("Currently not failing grenade runs with errors") return 0 return 1 print("ok") return 0 usage = """ Find non-white-listed log errors in log files from a devstack-gate run. Log files will be searched for ERROR or CRITICAL messages. If any error messages do not match any of the whitelist entries contained in etc/whitelist.yaml, those messages will be printed to the console and failure will be returned. A file directory containing logs or a url to the log files of an OpenStack gate job can be provided. The whitelist yaml looks like: log-name: - module: "a.b.c" message: "regexp" - module: "a.b.c" message: "regexp" repeated for each log file with a whitelist. """ parser = argparse.ArgumentParser(description=usage) parser.add_argument('-d', '--directory', help="Directory containing log files") parser.add_argument('-u', '--url', help="url containing logs from an OpenStack gate job") if __name__ == "__main__": try: sys.exit(main(parser.parse_args())) except Exception as e: print("Failure in script: %s" % e) # Don't fail if there is a problem with the script. sys.exit(0) tempest-17.2.0/tools/find_stack_traces.py0000777000175100017510000001015713207044712020477 0ustar zuulzuul00000000000000#!/usr/bin/env python # 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 gzip import pprint import re import sys import six import six.moves.urllib.request as urlreq pp = pprint.PrettyPrinter() NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d" NOVA_REGEX = r"(?P%s) (?P\d+ )?(?P(ERROR|TRACE)) " \ "(?P[\w\.]+) (?P.*)" % (NOVA_TIMESTAMP) class StackTrace(object): timestamp = None pid = None level = "" module = "" msg = "" def __init__(self, timestamp=None, pid=None, level="", module="", msg=""): self.timestamp = timestamp self.pid = pid self.level = level self.module = module self.msg = msg def append(self, msg): self.msg = self.msg + msg def is_same(self, data): return (data['timestamp'] == self.timestamp and data['level'] == self.level) def not_none(self): return self.timestamp is not None def __str__(self): buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module) for line in self.msg.splitlines(): buff = buff + line + "\n" return buff def hunt_for_stacktrace(url): """Return TRACE or ERROR lines out of logs.""" req = urlreq.Request(url) req.add_header('Accept-Encoding', 'gzip') page = urlreq.urlopen(req) buf = six.StringIO(page.read()) f = gzip.GzipFile(fileobj=buf) content = f.read() traces = [] trace = StackTrace() for line in content.splitlines(): m = re.match(NOVA_REGEX, line) if m: data = m.groupdict() if trace.not_none() and trace.is_same(data): trace.append(data['msg'] + "\n") else: trace = StackTrace( timestamp=data.get('timestamp'), pid=data.get('pid'), level=data.get('level'), module=data.get('module'), msg=data.get('msg')) else: if trace.not_none(): traces.append(trace) trace = StackTrace() # once more at the end to pick up any stragglers if trace.not_none(): traces.append(trace) return traces def log_url(url, log): return "%s/%s" % (url, log) def collect_logs(url): page = urlreq.urlopen(url) content = page.read() logs = re.findall('(screen-[\w-]+\.txt\.gz)', content) return logs def usage(): print(""" Usage: find_stack_traces.py Hunts for stack traces in a devstack run. Must provide it a base log url from a tempest devstack run. Should start with http and end with /logs/. Returns a report listing stack traces out of the various files where they are found. """) sys.exit(0) def print_stats(items, fname, verbose=False): errors = len([x for x in items if x.level == "ERROR"]) traces = len([x for x in items if x.level == "TRACE"]) print("%d ERRORS found in %s" % (errors, fname)) print("%d TRACES found in %s" % (traces, fname)) if verbose: for item in items: print(item) print("\n\n") def main(): if len(sys.argv) == 2: url = sys.argv[1] loglist = collect_logs(url) # probably wrong base url if not loglist: usage() for log in loglist: logurl = log_url(url, log) traces = hunt_for_stacktrace(logurl) if traces: print_stats(traces, log, verbose=True) else: usage() if __name__ == '__main__': main() tempest-17.2.0/tools/tempest-plugin-sanity.sh0000666000175100017510000001016213207044712021266 0ustar zuulzuul00000000000000#!/usr/bin/env bash # Copyright 2017 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This script is intended to check the sanity of tempest plugins against # tempest master. # What it does: # * Creates the virtualenv # * Install tempest # * Retrieve the project lists having tempest plugin if project name is # given. # * For each project in a list, It does: # * Clone the Project # * Install the Project and also installs dependencies from # test-requirements.txt. # * Create Tempest workspace # * List tempest plugins # * List tempest plugins tests # * Uninstall the project and its dependencies # * Again Install tempest # * Again repeat the step from cloning project # # If one of the step fails, The script will exit with failure. if [ "$1" == "-h" ]; then echo -e "This script performs the sanity of tempest plugins to find configuration and dependency issues with the tempest.\n Usage: sh ./tools/tempest-plugin-sanity.sh [Run sanity on tempest plugins]" exit 0 fi set -ex # retrieve a list of projects having tempest plugins PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)" # List of projects having tempest plugin stale or unmaintained from long time BLACKLIST="trio2o" # Function to clone project using zuul-cloner or from git function clone_project() { if [ -e /usr/zuul-env/bin/zuul-cloner ]; then /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ git://git.openstack.org \ openstack/"$1" elif [ -e /usr/bin/git ]; then /usr/bin/git clone git://git.openstack.org/openstack/"$1" \ openstack/"$1" fi } # Create virtualenv to perform sanity operation SANITY_DIR=$(pwd) virtualenv "$SANITY_DIR"/.venv export TVENV="$SANITY_DIR/tools/with_venv.sh" cd "$SANITY_DIR" # Install tempest in a venv "$TVENV" pip install . # Function to install project function install_project() { "$TVENV" pip install "$SANITY_DIR"/openstack/"$1" # Check for test-requirements.txt file in a project then install it. if [ -e "$SANITY_DIR"/openstack/"$1"/test-requirements.txt ]; then "$TVENV" pip install -r "$SANITY_DIR"/openstack/"$1"/test-requirements.txt fi } # Function to perform sanity checking on Tempest plugin function tempest_sanity() { "$TVENV" tempest init "$SANITY_DIR"/tempest_sanity cd "$SANITY_DIR"/tempest_sanity "$TVENV" tempest list-plugins "$TVENV" tempest run -l # Delete tempest workspace "$TVENV" tempest workspace remove --name tempest_sanity --rmdir cd "$SANITY_DIR" } # Function to uninstall project function uninstall_project() { "$TVENV" pip uninstall -y "$SANITY_DIR"/openstack/"$1" # Check for *requirements.txt file in a project then uninstall it. if [ -e "$SANITY_DIR"/openstack/"$1"/*requirements.txt ]; then "$TVENV" pip uninstall -y -r "$SANITY_DIR"/openstack/"$1"/*requirements.txt fi # Remove the project directory after sanity run rm -fr "$SANITY_DIR"/openstack/"$1" } # Function to run sanity check on each project function plugin_sanity_check() { clone_project "$1" && install_project "$1" && tempest_sanity "$1" \ && uninstall_project "$1" && "$TVENV" pip install . } # Log status passed_plugin='' failed_plugin='' # Perform sanity on all tempest plugin projects for project in $PROJECT_LIST; do # Remove blacklisted tempest plugins if ! [[ `echo $BLACKLIST | grep -c $project ` -gt 0 ]]; then plugin_sanity_check $project && passed_plugin+=", $project" || \ failed_plugin+=", $project" fi done # Check for failed status if [[ -n $failed_plugin ]]; then exit 1 fi tempest-17.2.0/tools/with_venv.sh0000777000175100017510000000031613207044712017020 0ustar zuulzuul00000000000000#!/usr/bin/env bash TOOLS_PATH=${TOOLS_PATH:-$(dirname $0)/../} VENV_PATH=${VENV_PATH:-${TOOLS_PATH}} VENV_DIR=${VENV_DIR:-/.venv} VENV=${VENV:-${VENV_PATH}/${VENV_DIR}} source ${VENV}/bin/activate && "$@" tempest-17.2.0/HACKING.rst0000666000175100017510000004303613207044712015114 0ustar zuulzuul00000000000000Tempest Coding Guide ==================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on Tempest Specific Commandments ------------------------------ - [T102] Cannot import OpenStack python clients in tempest/api & tempest/scenario tests - [T104] Scenario tests require a services decorator - [T105] Tests cannot use setUpClass/tearDownClass - [T106] vim configuration should not be kept in source files. - [T107] Check that a service tag isn't in the module path - [T108] Check no hyphen at the end of rand_name() argument - [T109] Cannot use testtools.skip decorator; instead use decorators.skip_because from tempest.lib - [T110] Check that service client names of GET should be consistent - [T111] Check that service client names of DELETE should be consistent - [T112] Check that tempest.lib should not import local tempest code - [T113] Check that tests use data_utils.rand_uuid() instead of uuid.uuid4() - [T114] Check that tempest.lib does not use tempest config - [T115] Check that admin tests should exist under admin path - [N322] Method's default argument shouldn't be mutable - [T116] Unsupported 'message' Exception attribute in PY3 Test Data/Configuration ----------------------- - Assume nothing about existing test data - Tests should be self contained (provide their own data) - Clean up test data at the completion of each test - Use configuration files for values that will vary by environment Exception Handling ------------------ According to the ``The Zen of Python`` the ``Errors should never pass silently.`` Tempest usually runs in special environment (jenkins gate jobs), in every error or failure situation we should provide as much error related information as possible, because we usually do not have the chance to investigate the situation after the issue happened. In every test case the abnormal situations must be very verbosely explained, by the exception and the log. In most cases the very first issue is the most important information. Try to avoid using ``try`` blocks in the test cases, as both the ``except`` and ``finally`` blocks could replace the original exception, when the additional operations leads to another exception. Just letting an exception to propagate, is not a bad idea in a test case, at all. Try to avoid using any exception handling construct which can hide the errors origin. If you really need to use a ``try`` block, please ensure the original exception at least logged. When the exception is logged you usually need to ``raise`` the same or a different exception anyway. Use of ``self.addCleanup`` is often a good way to avoid having to catch exceptions and still ensure resources are correctly cleaned up if the test fails part way through. Use the ``self.assert*`` methods provided by the unit test framework. This signals the failures early on. Avoid using the ``self.fail`` alone, its stack trace will signal the ``self.fail`` line as the origin of the error. Avoid constructing complex boolean expressions for assertion. The ``self.assertTrue`` or ``self.assertFalse`` without a ``msg`` argument, will just tell you the single boolean value, and you will not know anything about the values used in the formula, the ``msg`` argument might be good enough for providing more information. Most other assert method can include more information by default. For example ``self.assertIn`` can include the whole set. It is recommended to use testtools `matcher`_ for the more tricky assertions. You can implement your own specific `matcher`_ as well. .. _matcher: https://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers If the test case fails you can see the related logs and the information carried by the exception (exception class, backtrack and exception info). This and the service logs are your only guide to finding the root cause of flaky issues. Test cases are independent -------------------------- Every ``test_method`` must be callable individually and MUST NOT depends on, any other ``test_method`` or ``test_method`` ordering. Test cases MAY depend on commonly initialized resources/facilities, like credentials management, testresources and so on. These facilities, MUST be able to work even if just one ``test_method`` is selected for execution. Service Tagging --------------- Service tagging is used to specify which services are exercised by a particular test method. You specify the services with the ``tempest.common.utils.services`` decorator. For example: @utils.services('compute', 'image') Valid service tag names are the same as the list of directories in tempest.api that have tests. For scenario tests having a service tag is required. For the API tests service tags are only needed if the test method makes an API call (either directly or indirectly through another service) that differs from the parent directory name. For example, any test that make an API call to a service other than Nova in ``tempest.api.compute`` would require a service tag for those services, however they do not need to be tagged as ``compute``. Test fixtures and resources --------------------------- Test level resources should be cleaned-up after the test execution. Clean-up is best scheduled using `addCleanup` which ensures that the resource cleanup code is always invoked, and in reverse order with respect to the creation order. Test class level resources should be defined in the `resource_setup` method of the test class, except for any credential obtained from the credentials provider, which should be set-up in the `setup_credentials` method. Cleanup is best scheduled using `addClassResourceCleanup` which ensures that the cleanup code is always invoked, and in reverse order with respect to the creation order. In both cases - test level and class level cleanups - a wait loop should be scheduled before the actual delete of resources with an asynchronous delete. The test base class `BaseTestCase` defines Tempest framework for class level fixtures. `setUpClass` and `tearDownClass` are defined here and cannot be overwritten by subclasses (enforced via hacking rule T105). Set-up is split in a series of steps (setup stages), which can be overwritten by test classes. Set-up stages are: - `skip_checks` - `setup_credentials` - `setup_clients` - `resource_setup` Tear-down is also split in a series of steps (teardown stages), which are stacked for execution only if the corresponding setup stage had been reached during the setup phase. Tear-down stages are: - `clear_credentials` (defined in the base test class) - `resource_cleanup` Skipping Tests -------------- Skipping tests should be based on configuration only. If that is not possible, it is likely that either a configuration flag is missing, or the test should fail rather than be skipped. Using discovery for skipping tests is generally discouraged. When running a test that requires a certain "feature" in the target cloud, if that feature is missing we should fail, because either the test configuration is invalid, or the cloud is broken and the expected "feature" is not there even if the cloud was configured with it. Negative Tests -------------- Error handling is an important aspect of API design and usage. Negative tests are a way to ensure that an application can gracefully handle invalid or unexpected input. However, as a black box integration test suite, Tempest is not suitable for handling all negative test cases, as the wide variety and complexity of negative tests can lead to long test runs and knowledge of internal implementation details. The bulk of negative testing should be handled with project function tests. All negative tests should be based on `API-WG guideline`_ . Such negative tests can block any changes from accurate failure code to invalid one. .. _API-WG guideline: https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications If facing some gray area which is not clarified on the above guideline, propose a new guideline to the API-WG. With a proposal to the API-WG we will be able to build a consensus across all OpenStack projects and improve the quality and consistency of all the APIs. In addition, we have some guidelines for additional negative tests. - About BadRequest(HTTP400) case: We can add a single negative tests of BadRequest for each resource and method(POST, PUT). Please don't implement more negative tests on the same combination of resource and method even if API request parameters are different from the existing test. - About NotFound(HTTP404) case: We can add a single negative tests of NotFound for each resource and method(GET, PUT, DELETE, HEAD). Please don't implement more negative tests on the same combination of resource and method. The above guidelines don't cover all cases and we will grow these guidelines organically over time. Patches outside of the above guidelines are left up to the reviewers' discretion and if we face some conflicts between reviewers, we will expand the guideline based on our discussion and experience. Test skips because of Known Bugs -------------------------------- If a test is broken because of a bug it is appropriate to skip the test until bug has been fixed. You should use the ``skip_because`` decorator so that Tempest's skip tracking tool can watch the bug status. Example:: @skip_because(bug="980688") def test_this_and_that(self): ... Guidelines ---------- - Do not submit changesets with only testcases which are skipped as they will not be merged. - Consistently check the status code of responses in testcases. The earlier a problem is detected the easier it is to debug, especially where there is complicated setup required. Parallel Test Execution ----------------------- Tempest by default runs its tests in parallel this creates the possibility for interesting interactions between tests which can cause unexpected failures. Dynamic credentials provides protection from most of the potential race conditions between tests outside the same class. But there are still a few of things to watch out for to try to avoid issues when running your tests in parallel. - Resources outside of a project scope still have the potential to conflict. This is a larger concern for the admin tests since most resources and actions that require admin privileges are outside of projects. - Races between methods in the same class are not a problem because parallelization in Tempest is at the test class level, but if there is a json and xml version of the same test class there could still be a race between methods. - The rand_name() function from tempest.lib.common.utils.data_utils should be used anywhere a resource is created with a name. Static naming should be avoided to prevent resource conflicts. - If the execution of a set of tests is required to be serialized then locking can be used to perform this. See usage of ``LockFixture`` for examples of using locking. Sample Configuration File ------------------------- The sample config file is autogenerated using a script. If any changes are made to the config variables in tempest/config.py then the sample config file must be regenerated. This can be done running:: tox -e genconfig Unit Tests ---------- Unit tests are a separate class of tests in Tempest. They verify Tempest itself, and thus have a different set of guidelines around them: 1. They can not require anything running externally. All you should need to run the unit tests is the git tree, python and the dependencies installed. This includes running services, a config file, etc. 2. The unit tests cannot use setUpClass, instead fixtures and testresources should be used for shared state between tests. .. _TestDocumentation: Test Documentation ------------------ For tests being added we need to require inline documentation in the form of docstrings to explain what is being tested. In API tests for a new API a class level docstring should be added to an API reference doc. If one doesn't exist a TODO comment should be put indicating that the reference needs to be added. For individual API test cases a method level docstring should be used to explain the functionality being tested if the test name isn't descriptive enough. For example:: def test_get_role_by_id(self): """Get a role by its id.""" the docstring there is superfluous and shouldn't be added. but for a method like:: def test_volume_backup_create_get_detailed_list_restore_delete(self): pass a docstring would be useful because while the test title is fairly descriptive the operations being performed are complex enough that a bit more explanation will help people figure out the intent of the test. For scenario tests a class level docstring describing the steps in the scenario is required. If there is more than one test case in the class individual docstrings for the workflow in each test methods can be used instead. A good example of this would be:: class TestVolumeBootPattern(manager.ScenarioTest): """ This test case attempts to reproduce the following steps: * Create in Cinder some bootable volume importing a Glance image * Boot an instance from the bootable volume * Write content to the volume * Delete an instance and Boot a new instance from the volume * Check written content in the instance * Create a volume snapshot while the instance is running * Boot an additional instance from the new snapshot based volume * Check written content in the instance booted from snapshot """ Test Identification with Idempotent ID -------------------------------------- Every function that provides a test must have an ``idempotent_id`` decorator that is a unique ``uuid-4`` instance. This ID is used to complement the fully qualified test name and track test functionality through refactoring. The format of the metadata looks like:: @decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997') def test_list_servers_with_detail(self): # The created server should be in the detailed list of all servers ... Tempest.lib includes a ``check-uuid`` tool that will test for the existence and uniqueness of idempotent_id metadata for every test. If you have Tempest installed you run the tool against Tempest by calling from the Tempest repo:: check-uuid It can be invoked against any test suite by passing a package name:: check-uuid --package Tests without an ``idempotent_id`` can be automatically fixed by running the command with the ``--fix`` flag, which will modify the source package by inserting randomly generated uuids for every test that does not have one:: check-uuid --fix The ``check-uuid`` tool is used as part of the Tempest gate job to ensure that all tests have an ``idempotent_id`` decorator. Branchless Tempest Considerations --------------------------------- Starting with the OpenStack Icehouse release Tempest no longer has any stable branches. This is to better ensure API consistency between releases because the API behavior should not change between releases. This means that the stable branches are also gated by the Tempest master branch, which also means that proposed commits to Tempest must work against both the master and all the currently supported stable branches of the projects. As such there are a few special considerations that have to be accounted for when pushing new changes to Tempest. 1. New Tests for new features ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When adding tests for new features that were not in previous releases of the projects the new test has to be properly skipped with a feature flag. Whether this is just as simple as using the @utils.requires_ext() decorator to check if the required extension (or discoverable optional API) is enabled or adding a new config option to the appropriate section. If there isn't a method of selecting the new **feature** from the config file then there won't be a mechanism to disable the test with older stable releases and the new test won't be able to merge. 2. Bug fix on core project needing Tempest changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When trying to land a bug fix which changes a tested API you'll have to use the following procedure:: 1. Propose change to the project, get a +2 on the change even with failing 2. Propose skip on Tempest which will only be approved after the corresponding change in the project has a +2 on change 3. Land project change in master and all open stable branches (if required) 4. Land changed test in Tempest Otherwise the bug fix won't be able to land in the project. Handily, `Zuul’s cross-repository dependencies `_. can be leveraged to do without step 2 and to have steps 3 and 4 happen "atomically". To do that, make the patch written in step 1 to depend (refer to Zuul's documentation above) on the patch written in step 4. The commit message for the Tempest change should have a link to the Gerrit review that justifies that change. 3. New Tests for existing features ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If a test is being added for a feature that exists in all the current releases of the projects then the only concern is that the API behavior is the same across all the versions of the project being tested. If the behavior is not consistent the test will not be able to merge. API Stability ------------- For new tests being added to Tempest the assumption is that the API being tested is considered stable and adheres to the OpenStack API stability guidelines. If an API is still considered experimental or in development then it should not be tested by Tempest until it is considered stable. tempest-17.2.0/roles/0000775000175100017510000000000013207045130014425 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-data-dir/0000775000175100017510000000000013207045130020727 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-data-dir/README.rst0000666000175100017510000000051613207044712022427 0ustar zuulzuul00000000000000Setup the `tempest` user as owner of Tempest's data folder. Tempest's devstack plugin creates the data folder, but it has no knowledge of the `tempest` user, so we need a role to fix ownership on the data folder. **Role Variables** .. zuul:rolevar:: devstack_data_dir :default: /opt/stack/data The devstack data directory. tempest-17.2.0/roles/setup-tempest-data-dir/defaults/0000775000175100017510000000000013207045130022536 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-data-dir/defaults/main.yaml0000666000175100017510000000004313207044712024352 0ustar zuulzuul00000000000000devstack_data_dir: /opt/stack/data tempest-17.2.0/roles/setup-tempest-data-dir/tasks/0000775000175100017510000000000013207045130022054 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-data-dir/tasks/main.yaml0000666000175100017510000000025113207044712023671 0ustar zuulzuul00000000000000- name: Set tempest as owner of Tempest data folder file: path: "{{devstack_data_dir}}/tempest" owner: tempest group: stack recurse: yes become: yes tempest-17.2.0/roles/process-stackviz/0000775000175100017510000000000013207045130017737 5ustar zuulzuul00000000000000tempest-17.2.0/roles/process-stackviz/README.rst0000666000175100017510000000101313207044712021430 0ustar zuulzuul00000000000000Generate stackviz report. Generate stackviz report using subunit and dstat data, using the stackviz archive embedded in test images. **Role Variables** .. zuul:rolevar:: devstack_base_dir :default: /opt/stack The devstack base directory. .. zuul:rolevar:: stage_dir :default: /opt/stack/logs The stage directory where the input data can be found and the output will be produced. .. zuul:rolevar:: test_results_stage_name :default: test_results The name of the subunit file to be used as input. tempest-17.2.0/roles/process-stackviz/defaults/0000775000175100017510000000000013207045130021546 5ustar zuulzuul00000000000000tempest-17.2.0/roles/process-stackviz/defaults/main.yaml0000666000175100017510000000013313207044712023362 0ustar zuulzuul00000000000000devstack_base_dir: /opt/stack stage_dir: /opt/stack/ test_results_stage_name: test_results tempest-17.2.0/roles/process-stackviz/tasks/0000775000175100017510000000000013207045130021064 5ustar zuulzuul00000000000000tempest-17.2.0/roles/process-stackviz/tasks/main.yaml0000666000175100017510000000343013207044712022703 0ustar zuulzuul00000000000000- name: Check if stackviz archive exists stat: path: "/opt/cache/files/stackviz-latest.tar.gz" register: stackviz_archive - debug: msg: "Stackviz archive could not be found in /opt/cache/files/stackviz-latest.tar.gz" when: not stackviz_archive.stat.exists - name: Check if subunit data exists stat: path: "{{ stage_dir }}/{{ test_results_stage_name }}.subunit" register: subunit_input - debug: msg: "Subunit file could not be found at {{ stage_dir }}/{{ test_results_stage_name }}.subunit" when: not subunit_input.stat.exists - name: Install stackviz pip: name: "file://{{ stackviz_archive.stat.path }}" virtualenv: /tmp/stackviz extra_args: -U when: - stackviz_archive.stat.exists - subunit_input.stat.exists - name: Deploy stackviz static html+js command: cp -pR /tmp/stackviz/share/stackviz-html {{ stage_dir }}/stackviz when: - stackviz_archive.stat.exists - subunit_input.stat.exists - name: Check if dstat data exists stat: path: "{{ devstack_base_dir }}/logs/dstat-csv.log" register: dstat_input when: - stackviz_archive.stat.exists - subunit_input.stat.exists - name: Run stackviz with dstat shell: | cat {{ subunit_input.stat.path }} | \ /tmp/stackviz/bin/stackviz-export \ --dstat "{{ devstack_base_dir }}/logs/dstat-csv.log" \ --env --stdin \ {{ stage_dir }}/stackviz/data when: - stackviz_archive.stat.exists - subunit_input.stat.exists - dstat_input.stat.exists - name: Run stackviz without dstat shell: | cat {{ subunit_input.stat.path }} | \ /tmp/stackviz/bin/stackviz-export \ --env --stdin \ {{ stage_dir }}/stackviz/data when: - stackviz_archive.stat.exists - subunit_input.stat.exists - not dstat_input.stat.exists tempest-17.2.0/roles/run-tempest/0000775000175100017510000000000013207045130016710 5ustar zuulzuul00000000000000tempest-17.2.0/roles/run-tempest/README.rst0000666000175100017510000000077413207044712020416 0ustar zuulzuul00000000000000Run Tempest **Role Variables** .. zuul:rolevar:: devstack_base_dir :default: /opt/stack The devstack base directory. .. zuul:rolevar:: tempest_concurrency :default: 0 The number of parallel test processes. .. zuul:rolevar:: tempest_test_regex :default: '' A regular expression used to select the tests. It works only when used with some specific tox environments ('all', 'all-plugin'.) .. zuul:rolevar:: tox_venvlist :default: smoke The Tempest tox environment to run. tempest-17.2.0/roles/run-tempest/defaults/0000775000175100017510000000000013207045130020517 5ustar zuulzuul00000000000000tempest-17.2.0/roles/run-tempest/defaults/main.yaml0000666000175100017510000000011113207044712022327 0ustar zuulzuul00000000000000devstack_base_dir: /opt/stack tempest_test_regex: '' tox_venvlist: smoke tempest-17.2.0/roles/run-tempest/tasks/0000775000175100017510000000000013207045130020035 5ustar zuulzuul00000000000000tempest-17.2.0/roles/run-tempest/tasks/main.yaml0000666000175100017510000000175013207044712021657 0ustar zuulzuul00000000000000# NOTE(andreaf) The number of vcpus is not available on all systems. # See https://github.com/ansible/ansible/issues/30688 # When not available, we fall back to ansible_processor_cores - name: Get hw.logicalcpu from sysctl shell: sysctl hw.logicalcpu | cut -d' ' -f2 register: sysctl_hw_logicalcpu when: ansible_processor_vcpus is not defined - name: Number of cores set_fact: num_cores: "{{ansible_processor_vcpus|default(sysctl_hw_logicalcpu.stdout)}}" - name: Set concurrency for cores == 3 or less set_fact: default_concurrency: "{{ num_cores }}" when: num_cores|int <= 3 - name: Limit max concurrency when more than 3 vcpus are available set_fact: default_concurrency: "{{ num_cores|int // 2 }}" when: num_cores|int > 3 - name: Run Tempest command: tox -e {{tox_venvlist}} -- {{tempest_test_regex|quote}} --concurrency={{tempest_concurrency|default(default_concurrency)}} args: chdir: "{{devstack_base_dir}}/tempest" become: true become_user: tempest tempest-17.2.0/roles/setup-tempest-run-dir/0000775000175100017510000000000013207045130020622 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-run-dir/README.rst0000666000175100017510000000060313207044712022317 0ustar zuulzuul00000000000000Setup Tempest run folder. To support isolation between multiple runs, separate run folders are required. Set `tempest` as owner of Tempest's current run folder. There is an implicit assumption here of a one to one relationship between devstack versions and Tempest runs. **Role Variables** .. zuul:rolevar:: devstack_base_dir :default: /opt/stack The devstack base directory. tempest-17.2.0/roles/setup-tempest-run-dir/defaults/0000775000175100017510000000000013207045130022431 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-run-dir/defaults/main.yaml0000666000175100017510000000003613207044712024247 0ustar zuulzuul00000000000000devstack_base_dir: /opt/stack tempest-17.2.0/roles/setup-tempest-run-dir/tasks/0000775000175100017510000000000013207045130021747 5ustar zuulzuul00000000000000tempest-17.2.0/roles/setup-tempest-run-dir/tasks/main.yaml0000666000175100017510000000025013207044712023563 0ustar zuulzuul00000000000000- name: Set tempest as owner of Tempest run folder file: path: "{{devstack_base_dir}}/tempest" owner: tempest group: stack recurse: yes become: yes tempest-17.2.0/roles/acl-devstack-files/0000775000175100017510000000000013207045130020066 5ustar zuulzuul00000000000000tempest-17.2.0/roles/acl-devstack-files/README.rst0000666000175100017510000000037013207044712021564 0ustar zuulzuul00000000000000Grant global read access to devstack `files` folder. This is handy to grant the `tempest` user access to VM images for testing. **Role Variables** .. zuul:rolevar:: devstack_data_dir :default: /opt/stack/data The devstack data directory. tempest-17.2.0/roles/acl-devstack-files/defaults/0000775000175100017510000000000013207045130021675 5ustar zuulzuul00000000000000tempest-17.2.0/roles/acl-devstack-files/defaults/main.yaml0000666000175100017510000000004313207044712023511 0ustar zuulzuul00000000000000devstack_data_dir: /opt/stack/data tempest-17.2.0/roles/acl-devstack-files/tasks/0000775000175100017510000000000013207045130021213 5ustar zuulzuul00000000000000tempest-17.2.0/roles/acl-devstack-files/tasks/main.yaml0000666000175100017510000000022313207044712023027 0ustar zuulzuul00000000000000- name: Grant global read access to devstack files file: path: "{{devstack_data_dir}}/files" mode: "o+rx" recurse: yes become: yes tempest-17.2.0/doc/0000775000175100017510000000000013207045130014046 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/0000775000175100017510000000000013207045130015346 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/microversion_testing.rst0000666000175100017510000003164413207044712022373 0ustar zuulzuul00000000000000================================= Microversion Testing With Tempest ================================= Many OpenStack Services provide their APIs with `microversion`_ support and want to test them in Tempest. .. _microversion: http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html This document covers how to test microversions for each project and whether tests should live in Tempest or on project side. Tempest Scope For Microversion Testing """""""""""""""""""""""""""""""""""""" APIs microversions for any OpenStack service grow rapidly and testing each and every microversion in Tempest is not feasible and efficient way. Also not every API microversion changes the complete system behavior and many of them only change the API or DB layer to accept and return more data on API. Tempest is an integration test suite, but not all API microversion testing fall under this category. As a result, Tempest mainly covers integration test cases for microversions, Other testing coverage for microversion should be hosted on project side as functional tests or via Tempest plugin as per project guidelines. .. note:: Integration tests are those tests which involve more than one service to verify the expected behavior by single or combination of API requests. If a test is just to verify the API behavior as success and failure cases or verify its expected response object, then it does not fall under integration tests. Tempest will cover only integration testing of applicable microversions with below exceptions: #. Test covers a feature which is important for interoperability. This covers tests requirement from Defcore. #. Test needed to fill Schema gaps. Tempest validates API responses with defined JSON schema. API responses can be different on each microversion and the JSON schemas need to be defined separately for the microversion. While implementing new integration tests for a specific microversion, there may be a gap in the JSON schemas (caused by previous microversions) implemented in Tempest. Filling that gap while implementing the new integration test cases is not efficient due to many reasons: * Hard to review * Sync between multiple integration tests patches which try to fill the same schema gap at same time * Might delay the microversion change on project side where project team wants Tempest tests to verify the results. Tempest will allow to fill the schema gaps at the end of each cycle, or more often if required. Schema gap can be filled with testing those with a minimal set of tests. Those tests might not be integration tests and might be already covered on project side also. This exception is needed because: * Allow to create microversion response schema in Tempest at the same time that projects are implementing their API microversions. This will make implementation easier for adding required tests before a new microversion change can be merged in the corresponding project and hence accelerate the development of microversions. * New schema must be verified by at least one test case which exercises such schema. For example: If any projects implemented 4 API microversion say- v2.3, v2.4, v2.5, v2.6 Assume microversion v2.3, v2.4, v2.6 change the API Response which means Tempest needs to add JSON schema for v2.3, v2.4, v2.6. In that case if only 1 or 2 tests can verify all new schemas then we do not need separate tests for each new schemas. In worst case, we have to add 3 separate tests. #. Test covers service behavior at large scale with involvement of more deep layer like hypervisor etc not just API/DB layer. This type of tests will be added case by case basis and with project team consultation about why it cannot be covered on project side and worth to test in Tempest. Project Scope For Microversion Testing """""""""""""""""""""""""""""""""""""" All microversions testing which are not covered under Tempest as per above section, should be tested on project side as functional tests or as Tempest plugin as per project decision. Configuration options for Microversion """""""""""""""""""""""""""""""""""""" * Add configuration options for specifying test target Microversions. We need to specify test target Microversions because the supported Microversions may be different between OpenStack clouds. For operating multiple Microversion tests in a single Tempest operation, configuration options should represent the range of test target Microversions. New configuration options are: * min_microversion * max_microversion Those should be defined under respective section of each service. For example: .. code-block:: ini [compute] min_microversion = None max_microversion = latest How To Implement Microversion Tests """"""""""""""""""""""""""""""""""" Tempest provides stable interfaces to test API Microversion. For Details, see: `API Microversion testing Framework`_ This document explains how to implement Microversion tests using those interfaces. .. _API Microversion testing Framework: https://docs.openstack.org/tempest/latest/library/api_microversion_testing.html Step1: Add skip logic based on configured Microversion range '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Add logic to skip the tests based on Tests class and configured Microversion range. api_version_utils.check_skip_with_microversion function can be used to automatically skip the tests which do not fall under configured Microversion range. For example: .. code-block:: python class BaseTestCase1(api_version_utils.BaseMicroversionTest): [..] @classmethod def skip_checks(cls): super(BaseTestCase1, cls).skip_checks() api_version_utils.check_skip_with_microversion(cls.min_microversion, cls.max_microversion, CONF.compute.min_microversion, CONF.compute.max_microversion) Skip logic can be added in tests base class or any specific test class depends on tests class structure. Step2: Selected API request microversion '''''''''''''''''''''''''''''''''''''''' Select appropriate Microversion which needs to be used to send with API request. api_version_utils.select_request_microversion function can be used to select the appropriate Microversion which will be used for API request. For example: .. code-block:: python @classmethod def resource_setup(cls): super(BaseTestCase1, cls).resource_setup() cls.request_microversion = ( api_version_utils.select_request_microversion( cls.min_microversion, CONF.compute.min_microversion)) Step3: Set Microversion on Service Clients '''''''''''''''''''''''''''''''''''''''''' Microversion selected by Test Class in previous step needs to be set on service clients so that APIs can be requested with selected Microversion. Microversion can be defined as global variable on service clients which can be set using fixture. Also Microversion header name needs to be defined on service clients which should be constant because it is not supposed to be changed by project as per API contract. For example: .. code-block:: python COMPUTE_MICROVERSION = None class BaseClient1(rest_client.RestClient): api_microversion_header_name = 'X-OpenStack-Nova-API-Version' Now test class can set the selected Microversion on required service clients using fixture which can take care of resetting the same once tests is completed. For example: .. code-block:: python def setUp(self): super(BaseTestCase1, self).setUp() self.useFixture(api_microversion_fixture.APIMicroversionFixture( self.request_microversion)) Service clients needs to add set Microversion in API request header which can be done by overriding the get_headers() method of rest_client. For example: .. code-block:: python COMPUTE_MICROVERSION = None class BaseClient1(rest_client.RestClient): api_microversion_header_name = 'X-OpenStack-Nova-API-Version' def get_headers(self): headers = super(BaseClient1, self).get_headers() if COMPUTE_MICROVERSION: headers[self.api_microversion_header_name] = COMPUTE_MICROVERSION return headers Step4: Separate Test classes for each Microversion '''''''''''''''''''''''''''''''''''''''''''''''''' This is last step to implement Microversion test class. For any Microversion tests, basically we need to implement a separate test class. In addition, each test class defines its Microversion range with class variable like min_microversion and max_microversion. Tests will be valid for that defined range. If that range is out of configured Microversion range then, test will be skipped. .. note:: Microversion testing is supported at test class level not at individual test case level. For example: Below test is applicable for Microversion from 2.2 till 2.9: .. code-block:: python class BaseTestCase1(api_version_utils.BaseMicroversionTest, tempest.test.BaseTestCase): [..] class Test1(BaseTestCase1): min_microversion = '2.2' max_microversion = '2.9' [..] Below test is applicable for Microversion from 2.10 till latest: .. code-block:: python class Test2(BaseTestCase1): min_microversion = '2.10' max_microversion = 'latest' [..] Notes about Compute Microversion Tests """""""""""""""""""""""""""""""""""""" Some of the compute Microversion tests have been already implemented with the Microversion testing framework. So for further tests only step 4 is needed. Along with that JSON response schema might need versioning if needed. Compute service clients strictly validate the response against defined JSON schema and does not allow additional elements in response. So if that Microversion changed the API response then schema needs to be versioned. New JSON schema file needs to be defined with new response attributes and service client methods will select the schema based on requested microversion. If Microversion tests are implemented randomly meaning not in sequence order(v2.20 tests added and previous Microversion tests are not yet added) then, still schema might need to be version for older Microversion if they changed the response. This is because Nova Microversion includes all the previous Microversions behavior. For Example: Implementing the v2.20 Microversion tests before v2.9 and 2.19- v2.20 API request will respond as latest behavior of Nova till v2.20, and in v2.9 and 2.19, server response has been changed so response schema needs to be versioned accordingly. That can be done by using the get_schema method in below module: The base_compute_client module '''''''''''''''''''''''''''''' .. automodule:: tempest.lib.services.compute.base_compute_client :members: Microversion tests implemented in Tempest """"""""""""""""""""""""""""""""""""""""" * Compute * `2.1`_ .. _2.1: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id1 * `2.2`_ .. _2.2: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id2 * `2.10`_ .. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9 * `2.20`_ .. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18 * `2.25`_ .. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka * `2.32`_ .. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29 * `2.37`_ .. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34 * `2.42`_ .. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata * `2.47`_ .. _2.47: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id42 * `2.48`_ .. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43 * Volume * `3.3`_ .. _3.3: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id3 * `3.9`_ .. _3.9: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id9 * `3.11`_ .. _3.11: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id11 * `3.12`_ .. _3.12: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id12 * `3.14`_ .. _3.14: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id14 * `3.19`_ .. _3.19: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id18 * `3.20`_ .. _3.20: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html#id19 tempest-17.2.0/doc/source/run.rst0000666000175100017510000000012713207044712016713 0ustar zuulzuul00000000000000.. _tempest_run: ----------- Tempest Run ----------- .. automodule:: tempest.cmd.run tempest-17.2.0/doc/source/conf.py0000666000175100017510000001520513207044712016657 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 documentation build configuration file, created by # sphinx-quickstart on Tue May 21 17:43:32 2013. # # 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. import os import subprocess # Build the plugin registry def build_plugin_registry(app): root_dir = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) subprocess.call(['tools/generate-tempest-plugins-list.sh'], cwd=root_dir) def setup(app): if os.getenv('GENERATE_TEMPEST_PLUGIN_LIST', 'true').lower() == 'true': app.connect('builder-inited', build_plugin_registry) # 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 = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'openstackdocstheme', 'oslo_config.sphinxconfiggen', ] config_generator_config_file = '../../tempest/cmd/config-generator.tempest.conf' sample_config_basename = '_static/tempest' todo_include_todos = True # openstackdocstheme options repository_name = 'openstack/tempest' bug_project = 'tempest' bug_tag = '' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # 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' copyright = u'2013, OpenStack QA Team' # 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 = ['_build'] # 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 = False # 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 = ['tempest.'] # -- 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 = 'openstackdocs' # 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 # " v 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 paths that contain "extra" files, such as .htaccess or # robots.txt. html_extra_path = ['_extra'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # 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 = False # If false, no index is generated. html_use_index = False # 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 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 # A list of warning types to suppress arbitrary warning messages. suppress_warnings = ['image.nonlocal_uri'] tempest-17.2.0/doc/source/library/0000775000175100017510000000000013207045130017012 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/library/validation_resources.rst0000666000175100017510000000035413207044712024001 0ustar zuulzuul00000000000000.. _validation_resources: Validation Resources ==================== ------------------------------- The validation_resources module ------------------------------- .. automodule:: tempest.lib.common.validation_resources :members: tempest-17.2.0/doc/source/library/credential_providers.rst0000666000175100017510000001501213207044712023761 0ustar zuulzuul00000000000000.. _cred_providers: Credential Providers ==================== These library interfaces are used to deal with allocating credentials on demand either dynamically by calling keystone to allocate new credentials, or from a list of preprovisioned credentials. These 2 modules are implementations of the same abstract credential providers class and can be used interchangeably. However, each implementation has some additional parameters that are used to influence the behavior of the modules. The API reference at the bottom of this doc shows the interface definitions for both modules, however that may be a bit opaque. You can see some examples of how to leverage this interface below. Initialization Example ---------------------- This example is from Tempest itself (from tempest/common/credentials_factory.py just modified slightly) and is how it initializes the credential provider based on config:: from tempest import config from tempest.lib.common import dynamic_creds from tempest.lib.common import preprov_creds CONF = config.CONF def get_credentials_provider(name, network_resources=None, force_tenant_isolation=False, identity_version=None): # If a test requires a new account to work, it can have it via forcing # dynamic credentials. A new account will be produced only for that test. # In case admin credentials are not available for the account creation, # the test should be skipped else it would fail. identity_version = identity_version or CONF.identity.auth_version if CONF.auth.use_dynamic_credentials or force_tenant_isolation: admin_creds = get_configured_admin_credentials( fill_in=True, identity_version=identity_version) return dynamic_creds.DynamicCredentialProvider( name=name, network_resources=network_resources, identity_version=identity_version, admin_creds=admin_creds, identity_admin_domain_scope=CONF.identity.admin_domain_scope, identity_admin_role=CONF.identity.admin_role, extra_roles=CONF.auth.tempest_roles, neutron_available=CONF.service_available.neutron, project_network_cidr=CONF.network.project_network_cidr, project_network_mask_bits=CONF.network.project_network_mask_bits, public_network_id=CONF.network.public_network_id, create_networks=(CONF.auth.create_isolated_networks and not CONF.network.shared_physical_network), resource_prefix=CONF.resources_prefix, credentials_domain=CONF.auth.default_credentials_domain_name, admin_role=CONF.identity.admin_role, identity_uri=CONF.identity.uri_v3, identity_admin_endpoint_type=CONF.identity.v3_endpoint_type) else: if CONF.auth.test_accounts_file: # Most params are not relevant for pre-created accounts return preprov_creds.PreProvisionedCredentialProvider( name=name, identity_version=identity_version, accounts_lock_dir=lockutils.get_lock_path(CONF), test_accounts_file=CONF.auth.test_accounts_file, object_storage_operator_role=CONF.object_storage.operator_role, object_storage_reseller_admin_role=reseller_admin_role, credentials_domain=CONF.auth.default_credentials_domain_name, admin_role=CONF.identity.admin_role, identity_uri=CONF.identity.uri_v3, identity_admin_endpoint_type=CONF.identity.v3_endpoint_type) else: raise exceptions.InvalidConfiguration( 'A valid credential provider is needed') This function just returns an initialized credential provider class based on the config file. The consumer of this function treats the output as the same regardless of whether it's a dynamic or preprovisioned provider object. Dealing with Credentials ------------------------ Once you have a credential provider object created the access patterns for allocating and removing credentials are the same across both the dynamic and preprovisioned credentials. These are defined in the abstract CredentialProvider class. At a high level the credentials provider enables you to get 3 basic types of credentials at once (per object): a primary, alt, and admin. You're also able to allocate a credential by role. These credentials are tracked by the provider object and delete must manually be called otherwise the created resources will not be deleted (or returned to the pool in the case of preprovisioned creds) Examples '''''''' Continuing from the example above, to allocate credentials by the 3 basic types you can do the following:: provider = get_credentials_provider('my_tests') primary_creds = provider.get_primary_creds() alt_creds = provider.get_alt_creds() admin_creds = provider.get_admin_creds() # Make sure to delete the credentials when you're finished provider.clear_creds() To create and interact with credentials by role you can do the following:: provider = get_credentials_provider('my_tests') my_role_creds = provider.get_creds_by_role({'roles': ['my_role']}) # provider.clear_creds() will clear all creds including those allocated by # role provider.clear_creds() When multiple roles are specified a set of creds with all the roles assigned will be allocated:: provider = get_credentials_provider('my_tests') my_role_creds = provider.get_creds_by_role({'roles': ['my_role', 'my_other_role']}) # provider.clear_creds() will clear all creds including those allocated by # role provider.clear_creds() If you need multiple sets of credentials with the same roles you can also do this by leveraging the ``force_new`` kwarg:: provider = get_credentials_provider('my_tests') my_role_creds = provider.get_creds_by_role({'roles': ['my_role']}) my_role_other_creds = provider.get_creds_by_role({'roles': ['my_role']}, force_new=True) # provider.clear_creds() will clear all creds including those allocated by # role provider.clear_creds() API Reference ------------- The dynamic credentials module '''''''''''''''''''''''''''''' .. automodule:: tempest.lib.common.dynamic_creds :members: The pre-provisioned credentials module '''''''''''''''''''''''''''''''''''''' .. automodule:: tempest.lib.common.preprov_creds :members: tempest-17.2.0/doc/source/library/service_clients/0000775000175100017510000000000013207045130022173 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/library/service_clients/compute_clients.rst0000666000175100017510000000021113207044712026123 0ustar zuulzuul00000000000000.. _servers_client: Compute Client Usage ==================== .. automodule:: tempest.lib.services.compute.servers_client :members: tempest-17.2.0/doc/source/library/cli.rst0000666000175100017510000000050613207044712020323 0ustar zuulzuul00000000000000.. _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: tempest-17.2.0/doc/source/library/auth.rst0000666000175100017510000000025113207044712020512 0ustar zuulzuul00000000000000.. _auth: Authentication Framework Usage ============================== --------------- The auth module --------------- .. automodule:: tempest.lib.auth :members: tempest-17.2.0/doc/source/library/clients.rst0000666000175100017510000000203113207044712021210 0ustar zuulzuul00000000000000.. _clients: Service Clients Usage ===================== Tests make requests against APIs using service clients. Service clients are specializations of the ``RestClient`` class. The service clients that cover the APIs exposed by a service should be grouped in a service clients module. A service clients module is python module where all service clients are defined. If major API versions are available, submodules should be defined, one for each version. The ``ClientsFactory`` class helps initializing all clients of a specific service client module from a set of shared parameters. The ``ServiceClients`` class provides a convenient way to get access to all available service clients initialized with a provided set of credentials. ----------------------------- The clients management module ----------------------------- .. automodule:: tempest.lib.services.clients :members: ------------------------------ Compute service client modules ------------------------------ .. toctree:: :maxdepth: 2 service_clients/compute_clients tempest-17.2.0/doc/source/library/decorators.rst0000666000175100017510000000027113207044712021720 0ustar zuulzuul00000000000000.. _decorators: Decorators Usage Guide ====================== --------------------- The decorators module --------------------- .. automodule:: tempest.lib.decorators :members: tempest-17.2.0/doc/source/library/api_microversion_testing.rst0000666000175100017510000000171413207044712024663 0ustar zuulzuul00000000000000.. _api_microversion_testing: API Microversion Testing Support in Tempest =========================================== --------------------------------------------- Framework to support API Microversion testing --------------------------------------------- Many of the OpenStack components have implemented API microversions. It is important to test those microversions in Tempest or external plugins. Tempest now provides stable interfaces to support to test the API microversions. Based on the microversion range coming from the combination of both configuration and each test case, APIs request will be made with selected microversion. This document explains the interfaces needed for microversion testing. The api_version_request module """""""""""""""""""""""""""""" .. automodule:: tempest.lib.common.api_version_request :members: The api_version_utils module """""""""""""""""""""""""""" .. automodule:: tempest.lib.common.api_version_utils :members: tempest-17.2.0/doc/source/library/rest_client.rst0000666000175100017510000000027113207044712022066 0ustar zuulzuul00000000000000.. _rest_client: Rest Client Usage ================= ---------------------- The rest_client module ---------------------- .. automodule:: tempest.lib.common.rest_client :members: tempest-17.2.0/doc/source/library/utils.rst0000666000175100017510000000022113207044712020706 0ustar zuulzuul00000000000000.. _utils: Utils Usage =========== --------------- The misc module --------------- .. automodule:: tempest.lib.common.utils.misc :members: tempest-17.2.0/doc/source/_static/0000775000175100017510000000000013207045130016774 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/_static/.keep0000666000175100017510000000000013207044712017716 0ustar zuulzuul00000000000000tempest-17.2.0/doc/source/index.rst0000666000175100017510000000213313207044712017215 0ustar zuulzuul00000000000000======================= Tempest Testing Project ======================= Overview ======== .. toctree:: :maxdepth: 2 overview Field Guides ============ Tempest contains tests of many different types, the field guides attempt to explain these in a way that makes it easy to understand where your test contributions should go. .. toctree:: :maxdepth: 1 field_guide/index field_guide/api field_guide/scenario field_guide/unit_tests Users Guide =========== Tempest Configuration Guide --------------------------- .. toctree:: :maxdepth: 2 configuration sampleconf Command Documentation --------------------- .. toctree:: :maxdepth: 1 account_generator cleanup subunit_describe_calls workspace run Developers Guide ================ Development ----------- .. toctree:: :maxdepth: 2 HACKING REVIEWING microversion_testing test_removal write_tests Plugins ------- .. toctree:: :maxdepth: 2 plugin plugin-registry Library ------- .. toctree:: :maxdepth: 2 library Indices and tables ================== * :ref:`search` tempest-17.2.0/doc/source/write_tests.rst0000666000175100017510000003223513207044712020470 0ustar zuulzuul00000000000000.. _tempest_test_writing: Tempest Test Writing Guide ########################## This guide serves as a starting point for developers working on writing new Tempest tests. At a high level tests in Tempest are just tests that conform to the standard python `unit test`_ framework. But there are several aspects of that are unique to Tempest and its role as an integration test suite running against a real cloud. .. _unit test: https://docs.python.org/3.6/library/unittest.html .. note:: This guide is for writing tests in the Tempest repository. While many parts of this guide are also applicable to Tempest plugins, not all the APIs mentioned are considered stable or recommended for use in plugins. Please refer to :ref:`tempest_plugin` for details about writing plugins Adding a New TestCase ===================== The base unit of testing in Tempest is the `TestCase`_ (also called the test class). Each TestCase contains test methods which are the individual tests that will be executed by the test runner. But, the TestCase is the smallest self contained unit for tests from the Tempest perspective. It's also the level at which Tempest is parallel safe. In other words, multiple TestCases can be executed in parallel, but individual test methods in the same TestCase can not. Also, all test methods within a TestCase are assumed to be executed serially. As such you can use the test case to store variables that are shared between methods. .. _TestCase: https://docs.python.org/3.6/library/unittest.html#unittest.TestCase In standard unittest the lifecycle of a TestCase can be described in the following phases: #. setUpClass #. setUp #. Test Execution #. tearDown #. doCleanups #. tearDownClass setUpClass ---------- The setUpClass phase is the first phase executed by the test runner and is used to perform any setup required for all the test methods to be executed. In Tempest this is a very important step and will automatically do the necessary setup for interacting with the configured cloud. To accomplish this you do **not** define a setUpClass function, instead there are a number of predefined phases to setUpClass that are used. The phases are: * skip_checks * setup_credentials * setup_clients * resource_setup which is executed in that order. Cleanup of resources provisioned during the resource_setup must be scheduled right after provisioning using the addClassResourceCleanp helper. The resource cleanups stacked this way are executed in reverse order during tearDownClass, before the cleanup of test credentials takes place. An example of a TestCase which defines all of these would be:: from tempest.common import waiters from tempest import config from tempest.lib.common.utils import test_utils from tempest import test CONF = config.CONF class TestExampleCase(test.BaseTestCase): @classmethod def skip_checks(cls): """This section is used to evaluate config early and skip all test methods based on these checks """ super(TestExampleCase, cls).skip_checks() if not CONF.section.foo cls.skip('A helpful message') @classmethod def setup_credentials(cls): """This section is used to do any manual credential allocation and also in the case of dynamic credentials to override the default network resource creation/auto allocation """ # This call is used to tell the credential allocator to not create any # network resources for this test case. It also enables selective # creation of other neutron resources. NOTE: it must go before the # super call cls.set_network_resources() super(TestExampleCase, cls).setup_credentials() @classmethod def setup_clients(cls): """This section is used to setup client aliases from the manager object or to initialize any additional clients. Except in a few very specific situations you should not need to use this. """ super(TestExampleCase, cls).setup_clients() cls.servers_client = cls.os_primary.servers_client @classmethod def resource_setup(cls): """This section is used to create any resources or objects which are going to be used and shared by **all** test methods in the TestCase. Note then anything created in this section must also be destroyed in the corresponding resource_cleanup() method (which will be run during tearDownClass()) """ super(TestExampleCase, cls).resource_setup() cls.shared_server = cls.servers_client.create_server(...) cls.addClassResourceCleanup(waiters.wait_for_server_termination, cls.servers_client, cls.shared_server['id']) cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc( cls.servers_client.delete_server, cls.shared_server['id'])) .. _credentials: Allocating Credentials '''''''''''''''''''''' Since Tempest tests are all about testing a running cloud, every test will need credentials to be able to make API requests against the cloud. Since this is critical to operation and, when running in parallel, easy to make a mistake, the base TestCase class will automatically allocate a regular user for each TestCase during the setup_credentials() phase. During this process it will also initialize a client manager object using those credentials, which will be your entry point into interacting with the cloud. For more details on how credentials are allocated the :ref:`tempest_cred_provider_conf` section of the Tempest Configuration Guide provides more details on the operation of this. There are some cases when you need more than a single set of credentials, or credentials with a more specialized set of roles. To accomplish this you have to set a class variable ``credentials`` on the TestCase directly. For example:: from tempest import test class TestExampleAdmin(test.BaseTestCase): credentials = ['primary', 'admin'] @classmethod def skip_checks(cls): ... In this example the ``TestExampleAdmin`` TestCase will allocate 2 sets of credentials, one regular user and one admin user. The corresponding manager objects will be set as class variables ``cls.os_primary`` and ``cls.os_admin`` respectively. You can also allocate a second user by putting **'alt'** in the list too. A set of alt credentials are the same as primary but can be used for tests cases that need a second user/project. You can also specify credentials with specific roles assigned. This is useful for cases where there are specific RBAC requirements hard coded into an API. The canonical example of this are swift tests which often want to test swift's concepts of operator and reseller_admin. An actual example from Tempest on how to do this is:: class PublicObjectTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] @classmethod def setup_credentials(cls): super(PublicObjectTest, cls).setup_credentials() ... In this case the manager objects will be set to ``cls.os_roles_operator`` and ``cls.os_roles_operator_alt`` respectively. There is no limit to how many credentials you can allocate in this manner, however in almost every case you should **not** need more than 3 sets of credentials per test case. To figure out the mapping of manager objects set on the TestCase and the requested credentials you can reference: +-------------------+---------------------+ | Credentials Entry | Manager Variable | +===================+=====================+ | primary | cls.os_primary | +-------------------+---------------------+ | admin | cls.os_admin | +-------------------+---------------------+ | alt | cls.os_alt | +-------------------+---------------------+ | [$label, $role] | cls.os_roles_$label | +-------------------+---------------------+ By default cls.os_primary is available since it is allocated in the base Tempest test class (located in tempest/test.py). If your TestCase inherits from a different direct parent class (it'll still inherit from the BaseTestCase, just not directly) be sure to check if that class overrides allocated credentials. Dealing with Network Allocation ''''''''''''''''''''''''''''''' When Neutron is enabled and a testing requires networking this isn't normally automatically setup when a tenant is created. Since Tempest needs isolated tenants to function properly it also needs to handle network allocation. By default the base test class will allocate a network, subnet, and router automatically (this depends on the configured credential provider, for more details see: :ref:`tempest_conf_network_allocation`). However, there are situations where you do no need all of these resources allocated (or your TestCase inherits from a class that overrides the default in tempest/test.py). There is a class level mechanism to override this allocation and specify which resources you need. To do this you need to call `cls.set_network_resources()` in the `setup_credentials()` method before the `super()`. For example:: from tempest import test class TestExampleCase(test.BaseTestCase): @classmethod def setup_credentials(cls): cls.set_network_resources(network=True, subnet=True, router=False) super(TestExampleCase, cls).setup_credentials() There are 2 quirks with the usage here. First for the set_network_resources function to work properly it **must be called before super()**. This is so that children classes' settings are always used instead of a parent classes'. The other quirk here is that if you do not want to allocate any network resources for your test class simply call `set_network_resources()` without any arguments. For example:: from tempest import test class TestExampleCase(test.BaseTestCase): @classmethod def setup_credentials(cls): cls.set_network_resources() super(TestExampleCase, cls).setup_credentials() This will not allocate any networking resources. This is because by default all the arguments default to False. It's also worth pointing out that it is common for base test classes for different services (and scenario tests) to override this setting. When inheriting from classes other than the base TestCase in tempest/test.py it is worth checking the immediate parent for what is set to determine if your class needs to override that setting. Interacting with Credentials and Clients ======================================== Once you have your basic TestCase setup you'll want to start writing tests. To do that you need to interact with an OpenStack deployment. This section will cover how credentials and clients are used inside of Tempest tests. Manager Objects --------------- The primary interface with which you interact with both credentials and API clients is the client manager object. These objects are created automatically by the base test class as part of credential setup (for more details see the previous :ref:`credentials` section). Each manager object is initialized with a set of credentials and has each client object already setup to use that set of credentials for making all the API requests. Each client is accessible as a top level attribute on the manager object. So to start making API requests you just access the client's method for making that call and the credentials are already setup for you. For example if you wanted to make an API call to create a server in Nova:: from tempest import test class TestExampleCase(test.BaseTestCase): def test_example_create_server(self): self.os_primary.servers_client.create_server(...) is all you need to do. As described previously, in the above example the ``self.os_primary`` is created automatically because the base test class sets the ``credentials`` attribute to allocate a primary credential set and initializes the client manager as ``self.os_primary``. This same access pattern can be used for all of the clients in Tempest. Credentials Objects ------------------- In certain cases you need direct access to the credentials (the most common use case would be an API request that takes a user or project id in the request body). If you're in a situation where you need to access this you'll need to access the ``credentials`` object which is allocated from the configured credential provider in the base test class. This is accessible from the manager object via the manager's ``credentials`` attribute. For example:: from tempest import test class TestExampleCase(test.BaseTestCase): def test_example_create_server(self): credentials = self.os_primary.credentials The credentials object provides access to all of the credential information you would need to make API requests. For example, building off the previous example:: from tempest import test class TestExampleCase(test.BaseTestCase): def test_example_create_server(self): credentials = self.os_primary.credentials username = credentials.username user_id = credentials.user_id password = credentials.password tenant_id = credentials.tenant_id tempest-17.2.0/doc/source/HACKING.rst0000666000175100017510000004303613207044712017161 0ustar zuulzuul00000000000000Tempest Coding Guide ==================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on Tempest Specific Commandments ------------------------------ - [T102] Cannot import OpenStack python clients in tempest/api & tempest/scenario tests - [T104] Scenario tests require a services decorator - [T105] Tests cannot use setUpClass/tearDownClass - [T106] vim configuration should not be kept in source files. - [T107] Check that a service tag isn't in the module path - [T108] Check no hyphen at the end of rand_name() argument - [T109] Cannot use testtools.skip decorator; instead use decorators.skip_because from tempest.lib - [T110] Check that service client names of GET should be consistent - [T111] Check that service client names of DELETE should be consistent - [T112] Check that tempest.lib should not import local tempest code - [T113] Check that tests use data_utils.rand_uuid() instead of uuid.uuid4() - [T114] Check that tempest.lib does not use tempest config - [T115] Check that admin tests should exist under admin path - [N322] Method's default argument shouldn't be mutable - [T116] Unsupported 'message' Exception attribute in PY3 Test Data/Configuration ----------------------- - Assume nothing about existing test data - Tests should be self contained (provide their own data) - Clean up test data at the completion of each test - Use configuration files for values that will vary by environment Exception Handling ------------------ According to the ``The Zen of Python`` the ``Errors should never pass silently.`` Tempest usually runs in special environment (jenkins gate jobs), in every error or failure situation we should provide as much error related information as possible, because we usually do not have the chance to investigate the situation after the issue happened. In every test case the abnormal situations must be very verbosely explained, by the exception and the log. In most cases the very first issue is the most important information. Try to avoid using ``try`` blocks in the test cases, as both the ``except`` and ``finally`` blocks could replace the original exception, when the additional operations leads to another exception. Just letting an exception to propagate, is not a bad idea in a test case, at all. Try to avoid using any exception handling construct which can hide the errors origin. If you really need to use a ``try`` block, please ensure the original exception at least logged. When the exception is logged you usually need to ``raise`` the same or a different exception anyway. Use of ``self.addCleanup`` is often a good way to avoid having to catch exceptions and still ensure resources are correctly cleaned up if the test fails part way through. Use the ``self.assert*`` methods provided by the unit test framework. This signals the failures early on. Avoid using the ``self.fail`` alone, its stack trace will signal the ``self.fail`` line as the origin of the error. Avoid constructing complex boolean expressions for assertion. The ``self.assertTrue`` or ``self.assertFalse`` without a ``msg`` argument, will just tell you the single boolean value, and you will not know anything about the values used in the formula, the ``msg`` argument might be good enough for providing more information. Most other assert method can include more information by default. For example ``self.assertIn`` can include the whole set. It is recommended to use testtools `matcher`_ for the more tricky assertions. You can implement your own specific `matcher`_ as well. .. _matcher: https://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers If the test case fails you can see the related logs and the information carried by the exception (exception class, backtrack and exception info). This and the service logs are your only guide to finding the root cause of flaky issues. Test cases are independent -------------------------- Every ``test_method`` must be callable individually and MUST NOT depends on, any other ``test_method`` or ``test_method`` ordering. Test cases MAY depend on commonly initialized resources/facilities, like credentials management, testresources and so on. These facilities, MUST be able to work even if just one ``test_method`` is selected for execution. Service Tagging --------------- Service tagging is used to specify which services are exercised by a particular test method. You specify the services with the ``tempest.common.utils.services`` decorator. For example: @utils.services('compute', 'image') Valid service tag names are the same as the list of directories in tempest.api that have tests. For scenario tests having a service tag is required. For the API tests service tags are only needed if the test method makes an API call (either directly or indirectly through another service) that differs from the parent directory name. For example, any test that make an API call to a service other than Nova in ``tempest.api.compute`` would require a service tag for those services, however they do not need to be tagged as ``compute``. Test fixtures and resources --------------------------- Test level resources should be cleaned-up after the test execution. Clean-up is best scheduled using `addCleanup` which ensures that the resource cleanup code is always invoked, and in reverse order with respect to the creation order. Test class level resources should be defined in the `resource_setup` method of the test class, except for any credential obtained from the credentials provider, which should be set-up in the `setup_credentials` method. Cleanup is best scheduled using `addClassResourceCleanup` which ensures that the cleanup code is always invoked, and in reverse order with respect to the creation order. In both cases - test level and class level cleanups - a wait loop should be scheduled before the actual delete of resources with an asynchronous delete. The test base class `BaseTestCase` defines Tempest framework for class level fixtures. `setUpClass` and `tearDownClass` are defined here and cannot be overwritten by subclasses (enforced via hacking rule T105). Set-up is split in a series of steps (setup stages), which can be overwritten by test classes. Set-up stages are: - `skip_checks` - `setup_credentials` - `setup_clients` - `resource_setup` Tear-down is also split in a series of steps (teardown stages), which are stacked for execution only if the corresponding setup stage had been reached during the setup phase. Tear-down stages are: - `clear_credentials` (defined in the base test class) - `resource_cleanup` Skipping Tests -------------- Skipping tests should be based on configuration only. If that is not possible, it is likely that either a configuration flag is missing, or the test should fail rather than be skipped. Using discovery for skipping tests is generally discouraged. When running a test that requires a certain "feature" in the target cloud, if that feature is missing we should fail, because either the test configuration is invalid, or the cloud is broken and the expected "feature" is not there even if the cloud was configured with it. Negative Tests -------------- Error handling is an important aspect of API design and usage. Negative tests are a way to ensure that an application can gracefully handle invalid or unexpected input. However, as a black box integration test suite, Tempest is not suitable for handling all negative test cases, as the wide variety and complexity of negative tests can lead to long test runs and knowledge of internal implementation details. The bulk of negative testing should be handled with project function tests. All negative tests should be based on `API-WG guideline`_ . Such negative tests can block any changes from accurate failure code to invalid one. .. _API-WG guideline: https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications If facing some gray area which is not clarified on the above guideline, propose a new guideline to the API-WG. With a proposal to the API-WG we will be able to build a consensus across all OpenStack projects and improve the quality and consistency of all the APIs. In addition, we have some guidelines for additional negative tests. - About BadRequest(HTTP400) case: We can add a single negative tests of BadRequest for each resource and method(POST, PUT). Please don't implement more negative tests on the same combination of resource and method even if API request parameters are different from the existing test. - About NotFound(HTTP404) case: We can add a single negative tests of NotFound for each resource and method(GET, PUT, DELETE, HEAD). Please don't implement more negative tests on the same combination of resource and method. The above guidelines don't cover all cases and we will grow these guidelines organically over time. Patches outside of the above guidelines are left up to the reviewers' discretion and if we face some conflicts between reviewers, we will expand the guideline based on our discussion and experience. Test skips because of Known Bugs -------------------------------- If a test is broken because of a bug it is appropriate to skip the test until bug has been fixed. You should use the ``skip_because`` decorator so that Tempest's skip tracking tool can watch the bug status. Example:: @skip_because(bug="980688") def test_this_and_that(self): ... Guidelines ---------- - Do not submit changesets with only testcases which are skipped as they will not be merged. - Consistently check the status code of responses in testcases. The earlier a problem is detected the easier it is to debug, especially where there is complicated setup required. Parallel Test Execution ----------------------- Tempest by default runs its tests in parallel this creates the possibility for interesting interactions between tests which can cause unexpected failures. Dynamic credentials provides protection from most of the potential race conditions between tests outside the same class. But there are still a few of things to watch out for to try to avoid issues when running your tests in parallel. - Resources outside of a project scope still have the potential to conflict. This is a larger concern for the admin tests since most resources and actions that require admin privileges are outside of projects. - Races between methods in the same class are not a problem because parallelization in Tempest is at the test class level, but if there is a json and xml version of the same test class there could still be a race between methods. - The rand_name() function from tempest.lib.common.utils.data_utils should be used anywhere a resource is created with a name. Static naming should be avoided to prevent resource conflicts. - If the execution of a set of tests is required to be serialized then locking can be used to perform this. See usage of ``LockFixture`` for examples of using locking. Sample Configuration File ------------------------- The sample config file is autogenerated using a script. If any changes are made to the config variables in tempest/config.py then the sample config file must be regenerated. This can be done running:: tox -e genconfig Unit Tests ---------- Unit tests are a separate class of tests in Tempest. They verify Tempest itself, and thus have a different set of guidelines around them: 1. They can not require anything running externally. All you should need to run the unit tests is the git tree, python and the dependencies installed. This includes running services, a config file, etc. 2. The unit tests cannot use setUpClass, instead fixtures and testresources should be used for shared state between tests. .. _TestDocumentation: Test Documentation ------------------ For tests being added we need to require inline documentation in the form of docstrings to explain what is being tested. In API tests for a new API a class level docstring should be added to an API reference doc. If one doesn't exist a TODO comment should be put indicating that the reference needs to be added. For individual API test cases a method level docstring should be used to explain the functionality being tested if the test name isn't descriptive enough. For example:: def test_get_role_by_id(self): """Get a role by its id.""" the docstring there is superfluous and shouldn't be added. but for a method like:: def test_volume_backup_create_get_detailed_list_restore_delete(self): pass a docstring would be useful because while the test title is fairly descriptive the operations being performed are complex enough that a bit more explanation will help people figure out the intent of the test. For scenario tests a class level docstring describing the steps in the scenario is required. If there is more than one test case in the class individual docstrings for the workflow in each test methods can be used instead. A good example of this would be:: class TestVolumeBootPattern(manager.ScenarioTest): """ This test case attempts to reproduce the following steps: * Create in Cinder some bootable volume importing a Glance image * Boot an instance from the bootable volume * Write content to the volume * Delete an instance and Boot a new instance from the volume * Check written content in the instance * Create a volume snapshot while the instance is running * Boot an additional instance from the new snapshot based volume * Check written content in the instance booted from snapshot """ Test Identification with Idempotent ID -------------------------------------- Every function that provides a test must have an ``idempotent_id`` decorator that is a unique ``uuid-4`` instance. This ID is used to complement the fully qualified test name and track test functionality through refactoring. The format of the metadata looks like:: @decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997') def test_list_servers_with_detail(self): # The created server should be in the detailed list of all servers ... Tempest.lib includes a ``check-uuid`` tool that will test for the existence and uniqueness of idempotent_id metadata for every test. If you have Tempest installed you run the tool against Tempest by calling from the Tempest repo:: check-uuid It can be invoked against any test suite by passing a package name:: check-uuid --package Tests without an ``idempotent_id`` can be automatically fixed by running the command with the ``--fix`` flag, which will modify the source package by inserting randomly generated uuids for every test that does not have one:: check-uuid --fix The ``check-uuid`` tool is used as part of the Tempest gate job to ensure that all tests have an ``idempotent_id`` decorator. Branchless Tempest Considerations --------------------------------- Starting with the OpenStack Icehouse release Tempest no longer has any stable branches. This is to better ensure API consistency between releases because the API behavior should not change between releases. This means that the stable branches are also gated by the Tempest master branch, which also means that proposed commits to Tempest must work against both the master and all the currently supported stable branches of the projects. As such there are a few special considerations that have to be accounted for when pushing new changes to Tempest. 1. New Tests for new features ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When adding tests for new features that were not in previous releases of the projects the new test has to be properly skipped with a feature flag. Whether this is just as simple as using the @utils.requires_ext() decorator to check if the required extension (or discoverable optional API) is enabled or adding a new config option to the appropriate section. If there isn't a method of selecting the new **feature** from the config file then there won't be a mechanism to disable the test with older stable releases and the new test won't be able to merge. 2. Bug fix on core project needing Tempest changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When trying to land a bug fix which changes a tested API you'll have to use the following procedure:: 1. Propose change to the project, get a +2 on the change even with failing 2. Propose skip on Tempest which will only be approved after the corresponding change in the project has a +2 on change 3. Land project change in master and all open stable branches (if required) 4. Land changed test in Tempest Otherwise the bug fix won't be able to land in the project. Handily, `Zuul’s cross-repository dependencies `_. can be leveraged to do without step 2 and to have steps 3 and 4 happen "atomically". To do that, make the patch written in step 1 to depend (refer to Zuul's documentation above) on the patch written in step 4. The commit message for the Tempest change should have a link to the Gerrit review that justifies that change. 3. New Tests for existing features ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If a test is being added for a feature that exists in all the current releases of the projects then the only concern is that the API behavior is the same across all the versions of the project being tested. If the behavior is not consistent the test will not be able to merge. API Stability ------------- For new tests being added to Tempest the assumption is that the API being tested is considered stable and adheres to the OpenStack API stability guidelines. If an API is still considered experimental or in development then it should not be tested by Tempest until it is considered stable. tempest-17.2.0/doc/source/overview.rst0000666000175100017510000002603513207044725017767 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/tempest.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Tempest - The OpenStack Integration Test Suite ============================================== The documentation for Tempest is officially hosted at: https://docs.openstack.org/tempest/latest/ This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ----------------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node DevStack install, a 20 node LXC cloud, or a 1000 node KVM cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public OpenStack APIs. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there are some features of OpenStack that are not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self-testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. Where the configuration file lives and how you interact with it depends on how you'll be running Tempest. There are 2 methods of using Tempest. The first, which is a newer and recommended workflow treats Tempest as a system installed program. The second older method is to run Tempest assuming your working dir is the actually Tempest source repo, and there are a number of assumptions related to that. For this section we'll only cover the newer method as it is simpler, and quicker to work with. #. You first need to install Tempest. This is done with pip after you check out the Tempest repo:: $ git clone https://git.openstack.org/openstack/tempest $ pip install tempest/ This can be done within a venv, but the assumption for this guide is that the Tempest CLI entry point will be in your shell's PATH. #. Installing Tempest may create a ``/etc/tempest dir``, however if one isn't created you can create one or use ``~/.tempest/etc`` or ``~/.config/tempest`` in place of ``/etc/tempest``. If none of these dirs are created Tempest will create ``~/.tempest/etc`` when it's needed. The contents of this dir will always automatically be copied to all ``etc/`` dirs in local workspaces as an initial setup step. So if there is any common configuration you'd like to be shared between local Tempest workspaces it's recommended that you pre-populate it before running ``tempest init``. #. Setup a local Tempest workspace. This is done by using the tempest init command:: $ tempest init cloud-01 which also works the same as:: $ mkdir cloud-01 && cd cloud-01 && tempest init This will create a new directory for running a single Tempest configuration. If you'd like to run Tempest against multiple OpenStack deployments the idea is that you'll create a new working directory for each to maintain separate configuration files and local artifact storage for each. #. Then ``cd`` into the newly created working dir and also modify the local config files located in the ``etc/`` subdir created by the ``tempest init`` command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a sample exists you must rename or copy it to tempest.conf before making any changes to it otherwise Tempest will not know how to load it. For details on configuring Tempest refer to the :ref:`tempest-configuration`. #. Once the configuration is done you're now ready to run Tempest. This can be done using the :ref:`tempest_run` command. This can be done by either running:: $ tempest run from the Tempest workspace directory. Or you can use the ``--workspace`` argument to run in the workspace you created regardless of your current working directory. For example:: $ tempest run --workspace cloud-01 There is also the option to use testr directly, or any `testr`_ based test runner, like `ostestr`_. For example, from the workspace dir run:: $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' will run the same set of tests as the default gate jobs. .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html .. _ostestr: https://docs.openstack.org/os-testr/latest/ 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 Release Notes `_ shows what changes have been released on each version. 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 ------------- Detailed configuration of Tempest is beyond the scope of this document see :ref:`tempest-configuration` for more details on configuring Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting version of the configuration. You can generate a new sample tempest.conf file, run the following command from the top level of the Tempest directory:: $ tox -e genconfig The most important pieces that are needed are the user ids, OpenStack endpoints, and basic flavors and images needed to run tests. Unit Tests ---------- Tempest also has a set of unit tests which test the Tempest code itself. These tests can be run by specifying the test discovery path:: $ stestr --test-path ./tempest/tests run By setting ``--test-path`` option to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of ``test_path`` is ``test_path=./tempest/test_discover`` which will only run test discover on the Tempest suite. Alternatively, there are the py27 and py35 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Starting in the Kilo release the OpenStack services dropped all support for python 2.6. This change has been mirrored in Tempest, starting after the tempest-2 tag. This means that proposed changes to Tempest which only fix python 2.6 compatibility will be rejected, and moving forward more features not present in python 2.6 will be used. If you're running your OpenStack services on an earlier release with python 2.6 you can easily run Tempest against it from a remote system running python 2.7. (or deploy a cloud guest in your cloud that has python 2.7) Python 3.x ---------- Starting during the Pike cycle Tempest has a gating CI job that runs Tempest with Python 3. Any Tempest release after 15.0.0 should fully support running under Python 3 as well as Python 2.7. Legacy run method ----------------- The legacy method of running Tempest is to just treat the Tempest source code as a python unittest repository and run directly from the source repo. When running in this way you still start with a Tempest config file and the steps are basically the same except that it expects you know where the Tempest code lives on your system and requires a bit more manual interaction to get Tempest running. For example, when running Tempest this way things like a lock file directory do not get generated automatically and the burden is on the user to create and configure that. To start you need to create a configuration file. The easiest way to create a configuration file is to generate a sample in the ``etc/`` directory :: $ cd $TEMPEST_ROOT_DIR $ oslo-config-generator --config-file \ tempest/cmd/config-generator.tempest.conf \ --output-file etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running DevStack environment, Tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your DevStack installation. Tempest is not tied to any single test runner, but `testr`_ is the most commonly used tool. Also, the nosetests test runner is **not** recommended to run Tempest. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $ testr run --parallel To run one single test serially :: $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Tox also contains several existing job configurations. For example:: $ tox -e full which will run the same set of tests as the OpenStack gate. (it's exactly how the gate invokes Tempest) Or:: $ tox -e smoke to run the tests tagged as smoke. tempest-17.2.0/doc/source/_extra/0000775000175100017510000000000013207045130016630 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/_extra/.htaccess0000666000175100017510000000007613207044712020440 0ustar zuulzuul00000000000000redirectmatch 301 ^/developer/tempest/(.*) /tempest/latest/$1 tempest-17.2.0/doc/source/sampleconf.rst0000666000175100017510000000074313207044712020242 0ustar zuulzuul00000000000000.. _tempest-sampleconf: Sample Configuration File ========================== The following is a sample Tempest configuration for adaptation and use. It is auto-generated from Tempest when this documentation is built, so if you are having issues with an option, please compare your version of Tempest with the version of this documentation. The sample configuration can also be viewed in `file form <_static/tempest.conf.sample>`_. .. literalinclude:: _static/tempest.conf.sample tempest-17.2.0/doc/source/data/0000775000175100017510000000000013207045130016257 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/data/tempest-plugins-registry.header0000666000175100017510000000164713207044712024456 0ustar zuulzuul00000000000000.. Note to patch submitters: this file is covered by a periodic proposal job. You should edit the files data/tempest-plugins-registry.footer and data/tempest-plugins-registry.header instead of this one. ========================== Tempest Plugin Registry ========================== Since we've created the external plugin mechanism, it's gotten used by a lot of projects. The following is a list of plugins that currently exist. Detected Plugins ================ The following are plugins that a script has found in the openstack/ namespace, which includes but is not limited to official OpenStack projects. +----------------------------+-------------------------------------------------------------------------+ |Plugin Name |URL | +----------------------------+-------------------------------------------------------------------------+ tempest-17.2.0/doc/source/field_guide/0000775000175100017510000000000013207045130017606 5ustar zuulzuul00000000000000tempest-17.2.0/doc/source/field_guide/scenario.rst0000666000175100017510000000303513207044712022153 0ustar zuulzuul00000000000000.. _scenario_field_guide: Tempest Field Guide to Scenario tests ===================================== What are these tests? --------------------- Scenario tests are "through path" tests of OpenStack function. Complicated setups where one part might depend on completion of a previous part. They ideally involve the integration between multiple OpenStack services to exercise the touch points between them. Any scenario test should have a real-life use case. An example would be: - "As operator I want to start with a blank environment": 1. upload a glance image 2. deploy a vm from it 3. ssh to the guest 4. create a snapshot of the vm Why are these tests in Tempest? ------------------------------- This is one of Tempest's core purposes, testing the integration between projects. Scope of these tests -------------------- Scenario tests should always use the Tempest implementation of the OpenStack API, as we want to ensure that bugs aren't hidden by the official clients. Tests should be tagged with which services they exercise, as determined by which client libraries are used directly by the test. Example of a good test ---------------------- While we are looking for interaction of 2 or more services, be specific in your interactions. A giant "this is my data center" smoke test is hard to debug when it goes wrong. A flow of interactions between Glance and Nova, like in the introduction, is a good example. Especially if it involves a repeated interaction when a resource is setup, modified, detached, and then reused later again. tempest-17.2.0/doc/source/field_guide/index.rst0000666000175100017510000000377613207044712021473 0ustar zuulzuul00000000000000============================ Tempest Field Guide Overview ============================ Tempest is designed to be useful for a large number of different environments. This includes being useful for gating commits to OpenStack core projects, being used to validate OpenStack cloud implementations for both correctness, as well as a burn in tool for OpenStack clouds. As such Tempest tests come in many flavors, each with their own rules and guidelines. Below is the overview of the Tempest respository structure to make this clear. | tempest/ | api/ - API tests | scenario/ - complex scenario tests | tests/ - unit tests for Tempest internals Each of these directories contains different types of tests. What belongs in each directory, the rules and examples for good tests, are documented in a README.rst file in the directory. :ref:`api_field_guide` ---------------------- API tests are validation tests for the OpenStack API. They should not use the existing Python clients for OpenStack, but should instead use the Tempest implementations of clients. Having raw clients let us pass invalid JSON to the APIs and see the results, something we could not get with the native clients. When it makes sense, API testing should be moved closer to the projects themselves, possibly as functional tests in their unit test frameworks. :ref:`scenario_field_guide` --------------------------- Scenario tests are complex "through path" tests for OpenStack functionality. They are typically a series of steps where complicated state requiring multiple services is set up exercised, and torn down. Scenario tests should not use the existing Python clients for OpenStack, but should instead use the Tempest implementations of clients. :ref:`unit_tests_field_guide` ----------------------------- Unit tests are the self checks for Tempest. They provide functional verification and regression checking for the internal components of Tempest. They should be used to just verify that the individual pieces of Tempest are working as expected. tempest-17.2.0/doc/source/field_guide/api.rst0000666000175100017510000000351513207044712021124 0ustar zuulzuul00000000000000.. _api_field_guide: Tempest Field Guide to API tests ================================ What are these tests? --------------------- One of Tempest's prime function is to ensure that your OpenStack cloud works with the OpenStack API as documented. The current largest portion of Tempest code is devoted to test cases that do exactly this. It's also important to test not only the expected positive path on APIs, but also to provide them with invalid data to ensure they fail in expected and documented ways. The latter type of tests is called ``negative tests`` in Tempest source code. Over the course of the OpenStack project Tempest has discovered many fundamental bugs by doing just this. In order for some APIs to return meaningful results, there must be enough data in the system. This means these tests might start by spinning up a server, image, etc, then operating on it. Why are these tests in Tempest? ------------------------------- This is one of the core missions for the Tempest project, and where it started. Many people use this bit of function in Tempest to ensure their clouds haven't broken the OpenStack API. It could be argued that some of the negative testing could be done back in the projects themselves, and we might evolve there over time, but currently in the OpenStack gate this is a fundamentally important place to keep things. Scope of these tests -------------------- API tests should always use the Tempest implementation of the OpenStack API, as we want to ensure that bugs aren't hidden by the official clients. They should test specific API calls, and can build up complex state if it's needed for the API call to be meaningful. They should send not only good data, but bad data at the API and look for error codes. They should all be able to be run on their own, not depending on the state created by a previous test. tempest-17.2.0/doc/source/field_guide/unit_tests.rst0000666000175100017510000000203713207044712022552 0ustar zuulzuul00000000000000.. _unit_tests_field_guide: Tempest Field Guide to Unit tests ================================= What are these tests? --------------------- Unit tests are the self checks for Tempest. They provide functional verification and regression checking for the internal components of Tempest. They should be used to just verify that the individual pieces of Tempest are working as expected. They should not require an external service to be running and should be able to run solely from the Tempest tree. Why are these tests in Tempest? ------------------------------- These tests exist to make sure that the mechanisms that we use inside of Tempest are valid and remain functional. They are only here for self validation of Tempest. Scope of these tests -------------------- Unit tests should not require an external service to be running or any extra configuration to run. Any state that is required for a test should either be mocked out or created in a temporary test directory. (see test_wrappers.py for an example of using a temporary test directory) tempest-17.2.0/doc/source/workspace.rst0000666000175100017510000000013513207044712020104 0ustar zuulzuul00000000000000----------------- Tempest Workspace ----------------- .. automodule:: tempest.cmd.workspace tempest-17.2.0/doc/source/plugin.rst0000666000175100017510000003323413207044712017412 0ustar zuulzuul00000000000000.. _tempest_plugin: ============================= Tempest Test Plugin Interface ============================= Tempest has an external test plugin interface which enables anyone to integrate an external test suite as part of a tempest run. This will let any project leverage being run with the rest of the tempest suite while not requiring the tests live in the tempest tree. Creating a plugin ================= Creating a plugin is fairly straightforward and doesn't require much additional effort on top of creating a test suite using tempest.lib. One thing to note with doing this is that the interfaces exposed by tempest are not considered stable (with the exception of configuration variables which ever effort goes into ensuring backwards compatibility). You should not need to import anything from tempest itself except where explicitly noted. Stable Tempest APIs plugins may use ----------------------------------- As noted above, several tempest APIs are acceptable to use from plugins, while others are not. A list of stable APIs available to plugins is provided below: * tempest.lib.* * tempest.config * tempest.test_discover.plugins * tempest.common.credentials_factory * tempest.clients * tempest.test If there is an interface from tempest that you need to rely on in your plugin which is not listed above, it likely needs to be migrated to tempest.lib. In that situation, file a bug, push a migration patch, etc. to expedite providing the interface in a reliable manner. Plugin Cookiecutter ------------------- In order to create the basic structure with base classes and test directories you can use the tempest-plugin-cookiecutter project:: > pip install -U cookiecutter && cookiecutter https://git.openstack.org/openstack/tempest-plugin-cookiecutter Cloning into 'tempest-plugin-cookiecutter'... remote: Counting objects: 17, done. remote: Compressing objects: 100% (13/13), done. remote: Total 17 (delta 1), reused 14 (delta 1) Unpacking objects: 100% (17/17), done. Checking connectivity... done. project (default is "sample")? foo testclass (default is "SampleTempestPlugin")? FooTempestPlugin This would create a folder called ``foo_tempest_plugin/`` with all necessary basic classes. You only need to move/create your test in ``foo_tempest_plugin/tests``. Entry Point ----------- Once you've created your plugin class you need to add an entry point to your project to enable tempest to find the plugin. The entry point must be added to the "tempest.test_plugins" namespace. If you are using pbr this is fairly straightforward, in the setup.cfg just add something like the following: .. code-block:: ini [entry_points] tempest.test_plugins = plugin_name = module.path:PluginClass Standalone Plugin vs In-repo Plugin ----------------------------------- Since all that's required for a plugin to be detected by tempest is a valid setuptools entry point in the proper namespace there is no difference from the tempest perspective on either creating a separate python package to house the plugin or adding the code to an existing python project. However, there are tradeoffs to consider when deciding which approach to take when creating a new plugin. If you create a separate python project for your plugin this makes a lot of things much easier. Firstly it makes packaging and versioning much simpler, you can easily decouple the requirements for the plugin from the requirements for the other project. It lets you version the plugin independently and maintain a single version of the test code across project release boundaries (see the `Branchless Tempest Spec`_ for more details on this). It also greatly simplifies the install time story for external users. Instead of having to install the right version of a project in the same python namespace as tempest they simply need to pip install the plugin in that namespace. It also means that users don't have to worry about inadvertently installing a tempest plugin when they install another package. .. _Branchless Tempest Spec: http://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html The sole advantage to integrating a plugin into an existing python project is that it enables you to land code changes at the same time you land test changes in the plugin. This reduces some of the burden on contributors by not having to land 2 changes to add a new API feature and then test it and doing it as a single combined commit. Plugin Class ============ To provide tempest with all the required information it needs to be able to run your plugin you need to create a plugin class which tempest will load and call to get information when it needs. To simplify creating this tempest provides an abstract class that should be used as the parent for your plugin. To use this you would do something like the following: .. code-block:: python from tempest.test_discover import plugins class MyPlugin(plugins.TempestPlugin): Then you need to ensure you locally define all of the mandatory methods in the abstract class, you can refer to the api doc below for a reference of what that entails. Abstract Plugin Class --------------------- .. autoclass:: tempest.test_discover.plugins.TempestPlugin :members: Plugin Structure ================ While there are no hard and fast rules for the structure a plugin, there are basically no constraints on what the plugin looks like as long as the 2 steps above are done. However, there are some recommended patterns to follow to make it easy for people to contribute and work with your plugin. For example, if you create a directory structure with something like:: plugin_dir/ config.py plugin.py tests/ api/ scenario/ services/ client.py That will mirror what people expect from tempest. The file * **config.py**: contains any plugin specific configuration variables * **plugin.py**: contains the plugin class used for the entry point * **tests**: the directory where test discovery will be run, all tests should be under this dir * **services**: where the plugin specific service clients are Additionally, when you're creating the plugin you likely want to follow all of the tempest developer and reviewer documentation to ensure that the tests being added in the plugin act and behave like the rest of tempest. Dealing with configuration options ---------------------------------- Historically Tempest didn't provide external guarantees on its configuration options. However, with the introduction of the plugin interface this is no longer the case. An external plugin can rely on using any configuration option coming from Tempest, there will be at least a full deprecation cycle for any option before it's removed. However, just the options provided by Tempest may not be sufficient for the plugin. If you need to add any plugin specific configuration options you should use the ``register_opts`` and ``get_opt_lists`` methods to pass them to Tempest when the plugin is loaded. When adding configuration options the ``register_opts`` method gets passed the CONF object from tempest. This enables the plugin to add options to both existing sections and also create new configuration sections for new options. Service Clients --------------- If a plugin defines a service client, it is beneficial for it to implement the ``get_service_clients`` method in the plugin class. All service clients which are exposed via this interface will be automatically configured and be available in any instance of the service clients class, defined in ``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are installed, all service clients from all plugins will be registered, making it easy to write tests which rely on multiple APIs whose service clients are in different plugins. Example implementation of ``get_service_clients``: .. code-block:: python def get_service_clients(self): # Example implementation with two service clients my_service1_config = config.service_client_config('my_service') params_my_service1 = { 'name': 'my_service_v1', 'service_version': 'my_service.v1', 'module_path': 'plugin_tempest_tests.services.my_service.v1', 'client_names': ['API1Client', 'API2Client'], } params_my_service1.update(my_service_config) my_service2_config = config.service_client_config('my_service') params_my_service2 = { 'name': 'my_service_v2', 'service_version': 'my_service.v2', 'module_path': 'plugin_tempest_tests.services.my_service.v2', 'client_names': ['API1Client', 'API2Client'], } params_my_service2.update(my_service2_config) return [params_my_service1, params_my_service2] Parameters: * **name**: Name of the attribute used to access the ``ClientsFactory`` from the ``ServiceClients`` instance. See example below. * **service_version**: Tempest enforces a single implementation for each service client. Available service clients are held in a ``ClientsRegistry`` singleton, and registered with ``service_version``, which means that ``service_version`` must be unique and it should represent the service API and version implemented by the service client. * **module_path**: Relative to the service client module, from the root of the plugin. * **client_names**: Name of the classes that implement service clients in the service clients module. Example usage of the service clients in tests: .. code-block:: python # my_creds is instance of tempest.lib.auth.Credentials # identity_uri is v2 or v3 depending on the configuration from tempest.lib.services import clients my_clients = clients.ServiceClients(my_creds, identity_uri) my_service1_api1_client = my_clients.my_service_v1.API1Client() my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any') Automatic configuration and registration of service clients imposes some extra constraints on the structure of the configuration options exposed by the plugin. First ``service_version`` should be in the format `service_config[.version]`. The `.version` part is optional, and should only be used if there are multiple versions of the same API available. The `service_config` must match the name of a configuration options group defined by the plugin. Different versions of one API must share the same configuration group. Second the configuration options group `service_config` must contain the following options: * `catalog_type`: corresponds to `service` in the catalog * `endpoint_type` The following options will be honoured if defined, but they are not mandatory, as they do not necessarily apply to all service clients. * `region`: default to identity.region * `build_timeout` : default to compute.build_timeout * `build_interval`: default to compute.build_interval Third the service client classes should inherit from ``RestClient``, should accept generic keyword arguments, and should pass those arguments to the ``__init__`` method of ``RestClient``. Extra arguments can be added. For instance: .. code-block:: python class MyAPIClient(rest_client.RestClient): def __init__(self, auth_provider, service, region, my_arg, my_arg2=True, **kwargs): super(MyAPIClient, self).__init__( auth_provider, service, region, **kwargs) self.my_arg = my_arg self.my_args2 = my_arg Finally the service client should be structured in a python module, so that all service client classes are importable from it. Each major API version should have its own module. The following folder and module structure is recommended for a single major API version:: plugin_dir/ services/ __init__.py client_api_1.py client_api_2.py The content of __init__.py module should be: .. code-block:: python from client_api_1.py import API1Client from client_api_2.py import API2Client __all__ = ['API1Client', 'API2Client'] The following folder and module structure is recommended for multiple major API version:: plugin_dir/ services/ v1/ __init__.py client_api_1.py client_api_2.py v2/ __init__.py client_api_1.py client_api_2.py The content each of __init__.py module under vN should be: .. code-block:: python from client_api_1.py import API1Client from client_api_2.py import API2Client __all__ = ['API1Client', 'API2Client'] Using Plugins ============= Tempest will automatically discover any installed plugins when it is run. So by just installing the python packages which contain your plugin you'll be using them with tempest, nothing else is really required. However, you should take care when installing plugins. By their very nature there are no guarantees when running tempest with plugins enabled about the quality of the plugin. Additionally, while there is no limitation on running with multiple plugins it's worth noting that poorly written plugins might not properly isolate their tests which could cause unexpected cross interactions between plugins. Notes for using plugins with virtualenvs ---------------------------------------- When using a tempest inside a virtualenv (like when running under tox) you have to ensure that the package that contains your plugin is either installed in the venv too or that you have system site-packages enabled. The virtualenv will isolate the tempest install from the rest of your system so just installing the plugin package on your system and then running tempest inside a venv will not work. Tempest also exposes a tox job, all-plugin, which will setup a tox virtualenv with system site-packages enabled. This will let you leverage tox without requiring to manually install plugins in the tox venv before running tests. tempest-17.2.0/doc/source/test_removal.rst0000666000175100017510000002110013207044712020605 0ustar zuulzuul00000000000000Tempest Test Removal Procedure ============================== Historically tempest was the only way of doing functional testing and integration testing in OpenStack. This was mostly only an artifact of tempest being the only proven pattern for doing this, not an artifact of a design decision. However, moving forward as functional testing is being spun up in each individual project we really only want tempest to be the integration test suite it was intended to be; testing the high level interactions between projects through REST API requests. In this model there are probably existing tests that aren't the best fit living in tempest. However, since tempest is largely still the only gating test suite in this space we can't carelessly rip out everything from the tree. This document outlines the procedure which was developed to ensure we minimize the risk for removing something of value from the tempest tree. This procedure might seem overly conservative and slow paced, but this is by design to try and ensure we don't remove something that is actually providing value. Having potential duplication between testing is not a big deal especially compared to the alternative of removing something which is actually providing value and is actively catching bugs, or blocking incorrect patches from landing. Proposing a test removal ------------------------ 3 prong rule for removal ^^^^^^^^^^^^^^^^^^^^^^^^ In the proposal etherpad we'll be looking for answers to 3 questions #. The tests proposed for removal must have equiv. coverage in a different project's test suite (whether this is another gating test project, or an in tree functional test suite). For API tests preferably the other project will have a similar source of friction in place to prevent breaking api changes so that we don't regress and let breaking api changes slip through the gate. #. The test proposed for removal has a failure rate < 0.50% in the gate over the past release (the value and interval will likely be adjusted in the future) .. _`prong #3`: #. There must not be an external user/consumer of tempest that depends on the test proposed for removal The answers to 1 and 2 are easy to verify. For 1 just provide a link to the new test location. If you are linking to the tempest removal patch please also put a Depends-On in the commit message for the commit which moved the test into another repo. For prong 2 you can use OpenStack-Health: Using OpenStack-Health """""""""""""""""""""" Go to: http://status.openstack.org/openstack-health and then navigate to a per test page for six months. You'll end up with a page that will graph the success and failure rates on the bottom graph. For example, something like `this URL`_. .. _this URL: http://status.openstack.org/openstack-health/#/test/tempest.scenario.test_volume_boot_pattern.TestVolumeBootPatternV2.test_volume_boot_pattern?groupKey=project&resolutionKey=day&duration=P6M The Old Way using subunit2sql directly """""""""""""""""""""""""""""""""""""" SELECT * from tests where test_id like "%test_id%"; (where $test_id is the full test_id, but truncated to the class because of setUpClass or tearDownClass failures) You can access the infra mysql subunit2sql db w/ read-only permissions with: * hostname: logstash.openstack.org * username: query * password: query * db_name: subunit2sql For example if you were trying to remove the test with the id: tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON.test_get_flavor_details_for_deleted_flavor you would run the following: #. run: "mysql -u query -p -h logstash.openstack.org subunit2sql" to connect to the subunit2sql db #. run the query: MySQL [subunit2sql]> select * from tests where test_id like "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%"; which will return a table of all the tests in the class (but it will also catch failures in setUpClass and tearDownClass) #. paste the output table with numbers and the mysql command you ran to generate it into the etherpad. Eventually a cli interface will be created to make that a bit more friendly. Also a dashboard is in the works so we don't need to manually run the command. The intent of the 2nd prong is to verify that moving the test into a project specific testing is preventing bugs (assuming the tempest tests were catching issues) from bubbling up a layer into tempest jobs. If we're seeing failure rates above a certain threshold in the gate checks that means the functional testing isn't really being effective in catching that bug (and therefore blocking it from landing) and having the testing run in tempest still has value. However for the 3rd prong verification is a bit more subjective. The original intent of this prong was mostly for refstack/defcore and also for things that running on the stable branches. We don't want to remove any tests if that would break our api consistency checking between releases, or something that defcore/refstack is depending on being in tempest. It's worth pointing out that if a test is used in defcore as part of interop testing then it will probably have continuing value being in tempest as part of the integration/integrated tests in general. This is one area where some overlap is expected between testing in projects and tempest, which is not a bad thing. Discussing the 3rd prong """""""""""""""""""""""" There are 2 approaches to addressing the 3rd prong. Either it can be raised during a qa meeting during the tempest discussion. Please put it on the agenda well ahead of the scheduled meeting. Since the meeting time will be well known ahead of time anyone who depends on the tests will have ample time beforehand to outline any concerns on the before the meeting. To give ample time for people to respond to removal proposals please add things to the agenda by the Monday before the meeting. The other option is to raise the removal on the openstack-dev mailing list. (for example see: http://lists.openstack.org/pipermail/openstack-dev/2016-February/086218.html ) This will raise the issue to the wider community and attract at least the same (most likely more) attention than discussing it during the irc meeting. The only downside is that it might take more time to get a response, given the nature of ML. Exceptions to this procedure ---------------------------- For the most part all tempest test removals have to go through this procedure there are a couple of exceptions though: #. The class of testing has been decided to be outside the scope of tempest. #. A revert for a patch which added a broken test, or testing which didn't actually run in the gate (basically any revert for something which shouldn't have been added) #. Tests that would become out of scope as a consequence of an API change, as described in `API Compatibility`_. Such tests cannot live in Tempest because of the branchless nature of Tempest. Such test must still honor `prong #3`_. For the first exception type the only types of testing in tree which have been declared out of scope at this point are: * The CLI tests (which should be completely removed at this point) * Neutron Adv. Services testing (which should be completely removed at this point) * XML API Tests (which should be completely removed at this point) * EC2 API/boto tests (which should be completely removed at this point) For tests that fit into this category the only criteria for removal is that there is equivalent testing elsewhere. Tempest Scope ^^^^^^^^^^^^^ Starting in the liberty cycle tempest has defined a set of projects which are defined as in scope for direct testing in tempest. As of today that list is: * Keystone * Nova * Glance * Cinder * Neutron * Swift anything that lives in tempest which doesn't test one of these projects can be removed assuming there is equivalent testing elsewhere. Preferably using the `tempest plugin mechanism`_ to maintain continuity after migrating the tests out of tempest. .. _tempest plugin mechanism: https://docs.openstack.org/tempest/latest/plugin.html API Compatibility """"""""""""""""" If an API introduces a non-discoverable, backward incompatible change, and such change is not backported to all versions supported by Tempest, tests for that API cannot live in Tempest anymore. This is because tests would not be able to know or control which API response to expect, and thus would not be able to enforce a specific behavior. If a test exists in Tempest that would meet this criteria as consequence of a change, the test must be removed according to the procedure discussed into this document. The API change should not be merged until all conditions required for test removal can be met. tempest-17.2.0/doc/source/REVIEWING.rst0000666000175100017510000001305713207044712017454 0ustar zuulzuul00000000000000Reviewing Tempest Code ====================== To start read the `OpenStack Common Review Checklist `_ Ensuring code is executed ------------------------- For any new or change to a test it has to be verified in the gate. This means that the first thing to check with any change is that a gate job actually runs it. Tests which aren't executed either because of configuration or skips should not be accepted. If a new test is added that depends on a new config option (like a feature flag), the commit message must reference a change in DevStack or DevStack-Gate that enables the execution of this newly introduced test. This reference could either be a `Cross-Repository Dependency `_ or a simple link to a Gerrit review. Execution time -------------- While checking in the job logs that a new test is actually executed, also pay attention to the execution time of that test. Keep in mind that each test is going to be executed hundreds of time each day, because Tempest tests run in many OpenStack projects. It's worth considering how important/critical the feature under test is with how costly the new test is. Unit Tests ---------- For any change that adds new functionality to either common functionality or an out-of-band tool unit tests are required. This is to ensure we don't introduce future regressions and to test conditions which we may not hit in the gate runs. Tests, and service clients aren't required to have unit tests since they should be self verifying by running them in the gate. API Stability ------------- Tests should only be added for a published stable APIs. If a patch contains tests for an API which hasn't been marked as stable or for an API that which doesn't conform to the `API stability guidelines `_ then it should not be approved. Reject Copy and Paste Test Code ------------------------------- When creating new tests that are similar to existing tests it is tempting to simply copy the code and make a few modifications. This increases code size and the maintenance burden. Such changes should not be approved if it is easy to abstract the duplicated code into a function or method. Tests overlap ------------- When a new test is being proposed, question whether this feature is not already tested with Tempest. Tempest has more than 1200 tests, spread amongst many directories, so it's easy to introduce test duplication. For example, testing volume attachment to a server could be a compute test or a volume test, depending on how you see it. So one must look carefully in the entire code base for possible overlap. As a rule of thumb, the older a feature is, the more likely it's already tested. Being explicit -------------- When tests are being added that depend on a configurable feature or extension, polling the API to discover that it is enabled should not be done. This will just result in bugs being masked because the test can be skipped automatically. Instead the config file should be used to determine whether a test should be skipped or not. Do not approve changes that depend on an API call to determine whether to skip or not. Configuration Options --------------------- With the introduction of the Tempest external test plugin interface we needed to provide a stable contract for Tempest's configuration options. This means we can no longer simply remove a configuration option when it's no longer used. Patches proposed that remove options without a deprecation cycle should not be approved. Similarly when changing default values with configuration we need to similarly be careful that we don't break existing functionality. Also, when adding options, just as before, we need to weigh the benefit of adding an additional option against the complexity and maintenance overhead having it costs. Test Documentation ------------------ When a new test is being added refer to the :ref:`TestDocumentation` section in hacking to see if the requirements are being met. With the exception of a class level docstring linking to the API ref doc in the API tests and a docstring for scenario tests this is up to the reviewers discretion whether a docstring is required or not. Release Notes ------------- Release notes are how we indicate to users and other consumers of Tempest what has changed in a given release. Since Tempest 10.0.0 we've been using `reno`_ to manage and build the release notes. There are certain types of changes that require release notes and we should not approve them without including a release note. These include but aren't limited to, any addition, deprecation or removal from the lib interface, any change to configuration options (including deprecation), CLI additions or deprecations, major feature additions, and anything backwards incompatible or would require a user to take note or do something extra. .. _reno: https://docs.openstack.org/reno/latest/ Deprecated Code --------------- Sometimes we have some bugs in deprecated code. Basically, we leave it. Because we don't need to maintain it. However, if the bug is critical, we might need to fix it. When it will happen, we will deal with it on a case-by-case basis. When to approve --------------- * Every patch needs two +2s before being approved. * Its ok to hold off on an approval until a subject matter expert reviews it * If a patch has already been approved but requires a trivial rebase to merge, you do not have to wait for a second +2, since the patch has already had two +2s. tempest-17.2.0/doc/source/account_generator.rst0000666000175100017510000000024413207044712021611 0ustar zuulzuul00000000000000-------------------------------------- Tempest Test-Account Generator Utility -------------------------------------- .. automodule:: tempest.cmd.account_generator tempest-17.2.0/doc/source/library.rst0000666000175100017510000000553613207044712017564 0ustar zuulzuul00000000000000.. _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. 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 --------- Any code that lives in tempest/lib will be treated as a stable interface. 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 library/api_microversion_testing library/auth library/clients library/credential_providers library/validation_resources tempest-17.2.0/doc/source/configuration.rst0000666000175100017510000005105013207044712020757 0ustar zuulzuul00000000000000.. _tempest-configuration: Tempest Configuration Guide =========================== This guide is a starting point for configuring Tempest. It aims to elaborate on and explain some of the mandatory and common configuration settings and how they are used in conjunction. The source of truth on each option is the sample config file which explains the purpose of each individual option. You can see the sample config file here: :ref:`tempest-sampleconf` .. _tempest_cred_provider_conf: Test Credentials ---------------- Tempest allows for configuring a set of admin credentials in the ``auth`` section, via the following parameters: #. ``admin_username`` #. ``admin_password`` #. ``admin_project_name`` #. ``admin_domain_name`` Admin credentials are not mandatory to run Tempest, but when provided they can be used to: - Run tests for admin APIs - Generate test credentials on the fly (see `Dynamic Credentials`_) When Keystone uses a policy that requires domain scoped tokens for admin actions, the flag ``admin_domain_scope`` must be set to ``True``. The admin user configured, if any, must have a role assigned to the domain to be usable. Tempest allows for configuring pre-provisioned test credentials as well. This can be done using the accounts.yaml file (see `Pre-Provisioned Credentials`_). This file is used to specify an arbitrary number of users available to run tests with. You can specify the location of the file in the ``auth`` section in the tempest.conf file. To see the specific format used in the file please refer to the ``accounts.yaml.sample`` file included in Tempest. Keystone Connection Info ^^^^^^^^^^^^^^^^^^^^^^^^ In order for Tempest to be able to talk to your OpenStack deployment you need to provide it with information about how it communicates with keystone. This involves configuring the following options in the ``identity`` section: - ``auth_version`` - ``uri`` - ``uri_v3`` The ``auth_version`` option is used to tell Tempest whether it should be using Keystone's v2 or v3 api for communicating with Keystone. The two uri options are used to tell Tempest the url of the keystone endpoint. The ``uri`` option is used for Keystone v2 request and ``uri_v3`` is used for Keystone v3. You want to ensure that which ever version you set for ``auth_version`` has its uri option defined. Credential Provider Mechanisms ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tempest currently has two different internal methods for providing authentication to tests: dynamic credentials and pre-provisioned credentials. Depending on which one is in use the configuration of Tempest is slightly different. Dynamic Credentials """"""""""""""""""" Dynamic Credentials (formerly known as Tenant isolation) was originally created to enable running Tempest in parallel. For each test class it creates a unique set of user credentials to use for the tests in the class. It can create up to three sets of username, password, and project names for a primary user, an admin user, and an alternate user. To enable and use dynamic credentials you only need to configure two things: #. A set of admin credentials with permissions to create users and projects. This is specified in the ``auth`` section with the ``admin_username``, ``admin_project_name``, ``admin_domain_name`` and ``admin_password`` options #. To enable dynamic credentials in the ``auth`` section with the ``use_dynamic_credentials`` option. This is also currently the default credential provider enabled by Tempest, due to its common use and ease of configuration. It is worth pointing out that depending on your cloud configuration you might need to assign a role to each of the users created by Tempest's dynamic credentials. This can be set using the ``tempest_roles`` option. It takes in a list of role names each of which will be assigned to each of the users created by dynamic credentials. This option will not have any effect when Tempest is not configured to use dynamic credentials. When the ``admin_domain_scope`` option is set to ``True``, provisioned admin accounts will be assigned a role on domain configured in ``default_credentials_domain_name``. This will make the accounts provisioned usable in a cloud where domain scoped tokens are required by Keystone for admin operations. Note that the initial pre-provision admin accounts, configured in tempest.conf, must have a role on the same domain as well, for Dynamic Credentials to work. Pre-Provisioned Credentials """"""""""""""""""""""""""" For a long time using dynamic credentials was the only method available if you wanted to enable parallel execution of Tempest tests. However, this was insufficient for certain use cases because of the admin credentials requirement to create the credential sets on demand. To get around that the accounts.yaml file was introduced and with that a new internal credential provider to enable using the list of credentials instead of creating them on demand. With locking test accounts each test class will reserve a set of credentials from the accounts.yaml before executing any of its tests so that each class is isolated like with dynamic credentials. To enable and use locking test accounts you need do a few things: #. Create an accounts.yaml file which contains the set of pre-existing credentials to use for testing. To make sure you don't have a credentials starvation issue when running in parallel make sure you have at least two times the number of worker processes you are using to execute Tempest available in the file. (If running serially the worker count is 1.) You can check the accounts.yaml.sample file packaged in Tempest for the yaml format. #. Provide Tempest with the location of your accounts.yaml file with the ``test_accounts_file`` option in the ``auth`` section *NOTE: Be sure to use a full path for the file; otherwise Tempest will likely not find it.* #. Set ``use_dynamic_credentials = False`` in the ``auth`` group It is worth pointing out that each set of credentials in the accounts.yaml should have a unique project. This is required to provide proper isolation to the tests using the credentials, and failure to do this will likely cause unexpected failures in some tests. Also, ensure that these projects and users used do not have any pre-existing resources created. Tempest assumes all tenants it's using are empty and may sporadically fail if there are unexpected resources present. When the Keystone in the target cloud requires domain scoped tokens to perform admin actions, all pre-provisioned admin users must have a role assigned on the domain where test accounts a provisioned. The option ``admin_domain_scope`` is used to tell Tempest that domain scoped tokens shall be used. ``default_credentials_domain_name`` is the domain where test accounts are expected to be provisioned if no domain is specified. Note that if credentials are pre-provisioned via ``tempest account-generator`` the role on the domain will be assigned automatically for you, as long as ``admin_domain_scope`` as ``default_credentials_domain_name`` are configured properly in tempest.conf. Pre-Provisioned Credentials are also known as accounts.yaml or accounts file. Compute ------- Flavors ^^^^^^^ For Tempest to be able to create servers you need to specify flavors that it can use to boot the servers with. There are two options in the Tempest config for doing this: #. ``flavor_ref`` #. ``flavor_ref_alt`` Both of these options are in the ``compute`` section of the config file and take in the flavor id (not the name) from Nova. The ``flavor_ref`` option is what will be used for booting almost all of the guests; ``flavor_ref_alt`` is only used in tests where two different-sized servers are required (for example, a resize test). Using a smaller flavor is generally recommended. When larger flavors are used, the extra time required to bring up servers will likely affect total run time and probably require tweaking timeout values to ensure tests have ample time to finish. Images ^^^^^^ Just like with flavors, Tempest needs to know which images to use for booting servers. There are two options in the compute section just like with flavors: #. ``image_ref`` #. ``image_ref_alt`` Both options are expecting an image id (not name) from Nova. The ``image_ref`` option is what will be used for booting the majority of servers in Tempest. ``image_ref_alt`` is used for tests that require two images such as rebuild. If two images are not available you can set both options to the same image id and those tests will be skipped. There are also options in the ``scenario`` section for images: #. ``img_file`` #. ``img_dir`` #. ``aki_img_file`` #. ``ari_img_file`` #. ``ami_img_file`` #. ``img_container_format`` #. ``img_disk_format`` However, unlike the other image options, these are used for a very small subset of scenario tests which are uploading an image. These options are used to tell Tempest where an image file is located and describe its metadata for when it is uploaded. The behavior of these options is a bit convoluted (which will likely be fixed in future versions). You first need to specify ``img_dir``, which is the directory in which Tempest will look for the image files. First it will check if the filename set for ``img_file`` could be found in ``img_dir``. If it is found then the ``img_container_format`` and ``img_disk_format`` options are used to upload that image to glance. However, if it is not found, Tempest will look for the three uec image file name options as a fallback. If neither is found, the tests requiring an image to upload will fail. It is worth pointing out that using `cirros`_ is a very good choice for running Tempest. It's what is used for upstream testing, they boot quickly and have a small footprint. .. _cirros: https://launchpad.net/cirros Networking ---------- OpenStack has a myriad of different networking configurations possible and depending on which of the two network backends, nova-network or Neutron, you are using things can vary drastically. Due to this complexity Tempest has to provide a certain level of flexibility in its configuration to ensure it will work against any cloud. This ends up causing a large number of permutations in Tempest's config around network configuration. Enabling Remote Access to Created Servers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. _tempest_conf_network_allocation: Network Creation/Usage for Servers """""""""""""""""""""""""""""""""" When Tempest creates servers for testing, some tests require being able to connect those servers. Depending on the configuration of the cloud, the methods for doing this can be different. In certain configurations it is required to specify a single network with server create calls. Accordingly, Tempest provides a few different methods for providing this information in configuration to try and ensure that regardless of the cloud's configuration it'll still be able to run. This section covers the different methods of configuring Tempest to provide a network when creating servers. Fixed Network Name '''''''''''''''''' This is the simplest method of specifying how networks should be used. You can just specify a single network name/label to use for all server creations. The limitation with this is that all projects and users must be able to see that network name/label if they are to perform a network list and be able to use it. If no network name is assigned in the config file and none of the below alternatives are used, then Tempest will not specify a network on server creations, which depending on the cloud configuration might prevent them from booting. To set a fixed network name simply: #. Set the ``fixed_network_name`` option in the ``compute`` group In the case that the configured fixed network name can not be found by a user network list call, it will be treated like one was not provided except that a warning will be logged stating that it couldn't be found. Accounts File ''''''''''''' If you are using an accounts file to provide credentials for running Tempest then you can leverage it to also specify which network should be used with server creations on a per project and user pair basis. This provides the necessary flexibility to work with more intricate networking configurations by enabling the user to specify exactly which network to use for which projects. You can refer to the accounts.yaml.sample file included in the Tempest repo for the syntax around specifying networks in the file. However, specifying a network is not required when using an accounts file. If one is not specified you can use a fixed network name to specify the network to use when creating servers just as without an accounts file. However, any network specified in the accounts file will take precedence over the fixed network name provided. If no network is provided in the accounts file and a fixed network name is not set then no network will be included in create server requests. If a fixed network is provided and the accounts.yaml file also contains networks this has the benefit of enabling a couple more tests which require a static network to perform operations like server lists with a network filter. If a fixed network name is not provided these tests are skipped. Additionally, if a fixed network name is provided it will serve as a fallback in case of a misconfiguration or a missing network in the accounts file. With Dynamic Credentials '''''''''''''''''''''''' With dynamic credentials enabled and using nova-network, your only option for configuration is to either set a fixed network name or not. However, in most cases it shouldn't matter because nova-network should have no problem booting a server with multiple networks. If this is not the case for your cloud then using an accounts file is recommended because it provides the necessary flexibility to describe your configuration. Dynamic credentials is not able to dynamically allocate things as necessary if Neutron is not enabled. With Neutron and dynamic credentials enabled there should not be any additional configuration necessary to enable Tempest to create servers with working networking, assuming you have properly configured the ``network`` section to work for your cloud. Tempest will dynamically create the Neutron resources necessary to enable using servers with that network. Also, just as with the accounts file, if you specify a fixed network name while using Neutron and dynamic credentials it will enable running tests which require a static network and it will additionally be used as a fallback for server creation. However, unlike accounts.yaml this should never be triggered. However, there is an option ``create_isolated_networks`` to disable dynamic credentials's automatic provisioning of network resources. If this option is set to ``False`` you will have to either rely on there only being a single/default network available for the server creation, or use ``fixed_network_name`` to inform Tempest which network to use. SSH Connection Configuration """""""""""""""""""""""""""" There are also several different ways to actually establish a connection and authenticate/login on the server. After a server is booted with a provided network there are still details needed to know how to actually connect to the server. The ``validation`` group gathers all the options regarding connecting to and remotely accessing the created servers. To enable remote access to servers, there are 3 options at a minimum that are used: #. ``run_validation`` #. ``connect_method`` #. ``auth_method`` The ``run_validation`` is used to enable or disable ssh connectivity for all tests (with the exception of scenario tests which do not have a flag for enabling or disabling ssh) To enable ssh connectivity this needs be set to ``True``. The ``connect_method`` option is used to tell Tempest what kind of IP to use for establishing a connection to the server. Two methods are available: ``fixed`` and ``floating``, the later being set by default. If this is set to floating Tempest will create a floating ip for the server before attempted to connect to it. The IP for the floating ip is what is used for the connection. For the ``auth_method`` option there is currently, only one valid option, ``keypair``. With this set to ``keypair`` Tempest will create an ssh keypair and use that for authenticating against the created server. Configuring Available Services ------------------------------ OpenStack is really a constellation of several different projects which are running together to create a cloud. However which projects you're running is not set in stone, and which services are running is up to the deployer. Tempest however needs to know which services are available so it can figure out which tests it is able to run and certain setup steps which differ based on the available services. The ``service_available`` section of the config file is used to set which services are available. It contains a boolean option for each service (except for Keystone which is a hard requirement) set it to ``True`` if the service is available or ``False`` if it is not. Service Catalog ^^^^^^^^^^^^^^^ Each project which has its own REST API contains an entry in the service catalog. Like most things in OpenStack this is also completely configurable. However, for Tempest to be able to figure out which endpoints should get REST API calls for each service, it needs to know how that project is defined in the service catalog. There are three options for each service section to accomplish this: #. ``catalog_type`` #. ``endpoint_type`` #. ``region`` Setting ``catalog_type`` and ``endpoint_type`` should normally give Tempest enough information to determine which endpoint it should pull from the service catalog to use for talking to that particular service. However, if your cloud has multiple regions available and you need to specify a particular one to use a service you can set the ``region`` option in that service's section. It should also be noted that the default values for these options are set to what DevStack uses (which is a de facto standard for service catalog entries). So often nothing actually needs to be set on these options to enable communication to a particular service. It is only if you are either not using the same ``catalog_type`` as DevStack or you want Tempest to talk to a different endpoint type instead of ``publicURL`` for a service that these need to be changed. .. note:: Tempest does not serve all kinds of fancy URLs in the service catalog. The service catalog should be in a standard format (which is going to be standardized at the Keystone level). Tempest expects URLs in the Service catalog in the following format: * ``http://example.com:1234/`` Examples: * Good - ``http://example.com:1234/v2.0`` * Wouldn’t work - ``http://example.com:1234/xyz/v2.0/`` (adding prefix/suffix around version etc) Service Feature Configuration ----------------------------- OpenStack provides its deployers a myriad of different configuration options to enable anyone deploying it to create a cloud tailor-made for any individual use case. It provides options for several different backend types, databases, message queues, etc. However, the downside to this configurability is that certain operations and features aren't supported depending on the configuration. These features may or may not be discoverable from the API so the burden is often on the user to figure out what is supported by the cloud they're talking to. Besides the obvious interoperability issues with this it also leaves Tempest in an interesting situation trying to figure out which tests are expected to work. However, Tempest tests do not rely on dynamic API discovery for a feature (assuming one exists). Instead Tempest has to be explicitly configured as to which optional features are enabled. This is in order to prevent bugs in the discovery mechanisms from masking failures. The service ``feature-enabled`` config sections are how Tempest addresses the optional feature question. Each service that has tests for optional features contains one of these sections. The only options in it are boolean options with the name of a feature which is used. If it is set to false any test which depends on that functionality will be skipped. For a complete list of all these options refer to the sample config file. API Extensions ^^^^^^^^^^^^^^ The service feature-enabled sections often contain an ``api-extensions`` option (or in the case of Swift a ``discoverable_apis`` option). This is used to tell Tempest which api extensions (or configurable middleware) is used in your deployment. It has two valid config states: either it contains a single value ``all`` (which is the default) which means that every api extension is assumed to be enabled, or it is set to a list of each individual extension that is enabled for that service. tempest-17.2.0/doc/source/cleanup.rst0000666000175100017510000000020713207044712017535 0ustar zuulzuul00000000000000-------------------------------- Post Tempest Run Cleanup Utility -------------------------------- .. automodule:: tempest.cmd.cleanuptempest-17.2.0/doc/source/subunit_describe_calls.rst0000666000175100017510000000022113207044712022611 0ustar zuulzuul00000000000000------------------------------ Subunit Describe Calls Utility ------------------------------ .. automodule:: tempest.cmd.subunit_describe_calls tempest-17.2.0/test-requirements.txt0000666000175100017510000000073613207044712017557 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 # needed for doc build sphinx>=1.6.2 # BSD openstackdocstheme>=1.17.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 mock>=2.0.0 # BSD coverage!=4.4,>=4.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 flake8-import-order==0.11 # LGPLv3 tempest-17.2.0/setup.py0000666000175100017510000000200613207044712015020 0ustar zuulzuul00000000000000# Copyright (c) 2013 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) tempest-17.2.0/releasenotes/0000775000175100017510000000000013207045130015772 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/0000775000175100017510000000000013207045130017122 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/identity-token-client-8aaef74b1d61090a.yaml0000666000175100017510000000022713207044712026526 0ustar zuulzuul00000000000000--- features: - | Add additional API endpoints to the identity v2 client token API: - list_endpoints_for_token - check_token_existence tempest-17.2.0/releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml0000666000175100017510000000032113207044712033136 0ustar zuulzuul00000000000000--- deprecations: - | Deprecate default value for configuration parameter v3_endpoint_type of identity section in OpenStack Pike and modify the default value to publicURL in OpenStack Q release. tempest-17.2.0/releasenotes/notes/15/0000775000175100017510000000000013207045130017347 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml0000666000175100017510000000035713207044712027600 0ustar zuulzuul00000000000000--- features: - | As in the [doc]: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html, there are some apis are not included, add them. * namespace_objects_client(v2) * namespace_tags_client(v2) ././@LongLink0000000000000000000000000000016000000000000011212 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd0000666000175100017510000000046213207044712032675 0ustar zuulzuul00000000000000--- upgrade: - The default value for the ``allow_port_security_disabled`` option in the ``compute-feature-enabled`` section has been changed from ``False`` to ``True``. deprecations: - The ``allow_port_security_disabled`` option in the ``compute-feature-enabled`` section is now deprecated. ././@LongLink0000000000000000000000000000016600000000000011220 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-compute-validation-config-options-e3d10000666000175100017510000000230213207044712032700 0ustar zuulzuul00000000000000--- prelude: > This release is marking the start of Ocata release support in Tempest upgrade: - | Below deprecated config options from compute group have been removed. Corresponding config options already been available in validation group. - ``compute.use_floatingip_for_ssh`` (available as ``validation.connect_method``) - ``compute.ssh_auth_method`` (available as ``validation.auth_method``) - ``compute.image_ssh_password`` (available as ``validation.image_ssh_password``) - ``compute.ssh_shell_prologue`` (available as ``validation.ssh_shell_prologue``) - ``compute.ping_size `` (available as ``validation.ping_size``) - ``compute.ping_count `` (available as ``validation.ping_count``) - ``compute.floating_ip_range `` (available as ``validation.floating_ip_range``) other: - | OpenStack releases supported at this time are **Mitaka**, **Newton**, and **Ocata**. The release under current development as of this tag is Pike, meaning that every Tempest commit is also tested against master during the Pike cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Pike (or future releases) cloud. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.y0000666000175100017510000000036413207044712032141 0ustar zuulzuul00000000000000--- features: - | Define v2 snapshot_manage_client client for the volume service as library interfaces, allowing other projects to use this module as stable libraries without maintenance changes. * snapshot_manage_client(v2) tempest-17.2.0/releasenotes/notes/15/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml0000666000175100017510000000025313207044712027217 0ustar zuulzuul00000000000000--- features: - Added customized JSON schema format checker for 'date-time' format. Compute response schema will be validated against customized format checker. ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe0000666000175100017510000000041513207044712032114 0ustar zuulzuul00000000000000--- upgrade: - The default value for the ``reseller`` option in the ``identity-feature-enabled`` section has been changed from ``False`` to ``True``. deprecations: - The ``reseller`` option in the ``identity-feature-enabled`` section is now deprecated. ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-input-scenario-config-options-414e0c540000666000175100017510000000035313207044712032355 0ustar zuulzuul00000000000000--- upgrade: - The deprecated input-scenario config options and group have been removed. The input scenarios functionality already being removed from tempest and from this release, their corresponding config options too. ././@LongLink0000000000000000000000000000016300000000000011215 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024e0000666000175100017510000000042713207044712033005 0ustar zuulzuul00000000000000--- upgrade: - The default value for the ``volume_services`` option in the ``volume_feature_enabled`` section has been changed from ``False`` to ``True``. deprecations: - The ``volume_services`` option in the ``volume_feature_enabled`` section is now deprecated. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba80000666000175100017510000000032513207044712032133 0ustar zuulzuul00000000000000--- features: - | Add the implied roles feature API to the roles_client library. This feature enables the possibility to create inferences rules between roles (a role being implied by another role). ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.ya0000666000175100017510000000036413207044712031570 0ustar zuulzuul00000000000000--- features: - Define the identity v3 service client domains_client as a library. Add domains_client to the library interface so the other projects can use this module as a stable library without any maintenance changes. ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe0000666000175100017510000000103413207044712032124 0ustar zuulzuul00000000000000--- upgrade: - | Below deprecated network config options have been removed. Those config options already been renamed to below meaningful names. - ``tenant_network_cidr`` (removed) -> ``project_network_cidr`` - ``tenant_network_mask_bits`` (removed) -> ``project_network_mask_bits`` - ``tenant_network_v6_cidr`` (removed) -> ``project_network_v6_cidr`` - ``tenant_network_v6_mask_bits`` (removed) -> ``project_network_v6_mask_bits`` - ``tenant_networks_reachable`` (removed) -> ``project_networks_reachable`` ././@LongLink0000000000000000000000000000017000000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yamltempest-17.2.0/releasenotes/notes/15/15.0.0-remove-deprecated-compute-microversion-config-options-ea0000666000175100017510000000051113207044712033116 0ustar zuulzuul00000000000000--- upgrade: - The deprecated compute microversion config options from 'compute-feature-enabled' group have been removed. Those config options are available under 'compute' group to configure the min and max microversion for compute service. * CONF.compute.min_microversion * CONF.compute.max_microversion tempest-17.2.0/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml0000666000175100017510000000023613207044712026612 0ustar zuulzuul00000000000000--- deprecations: - | Remove the support of python3.4, because in Ubuntu Xenial only python3.5 is available (python3.4 is restricted to <= Mitaka). tempest-17.2.0/releasenotes/notes/tempest-identity-catalog-client-f5c8589a9d7c1eb5.yaml0000666000175100017510000000025413207044712030530 0ustar zuulzuul00000000000000--- features: - Add a new identity catalog client. At this point, the new client contains a single functionality, "show_catalog", which returns a catalog object. ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yamltempest-17.2.0/releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd9990000666000175100017510000000026513207044712032765 0ustar zuulzuul00000000000000--- features: - | Add show volume metadata item API to v2 volumes_client library. This feature enables the possibility to show a volume's metadata for a specific key. tempest-17.2.0/releasenotes/notes/add-validation-resources-to-lib-dc2600c4324ca4d7.yaml0000666000175100017510000000047613207044712030277 0ustar zuulzuul00000000000000--- features: - | Add the `validation_resources` module to tempest.lib. The module provides a set of helpers that can be used to provision and cleanup all the resources required to perform ping / ssh tests against a virtual machine: a keypair, a security group with targeted rules and a floating IP. tempest-17.2.0/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml0000666000175100017510000000034113207044712025614 0ustar zuulzuul00000000000000--- features: - | Define v2.0 ``tags_client`` for the network service as a library interface, allowing other projects to use this module as a stable library without maintenance changes. * tags_client(v2.0) tempest-17.2.0/releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml0000666000175100017510000000022713207044712032441 0ustar zuulzuul00000000000000--- features: - | Added tempest workspace remove --name --rmdir feature to delete the workspace directory as well as entry. tempest-17.2.0/releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml0000666000175100017510000000047713207044712032057 0ustar zuulzuul00000000000000--- features: - | Define v3 backups_client for the volume service as a library interface, allowing other projects to use this module as a stable library without maintenance changes. Add update backup API to v3 backups_client library, min_microversion of this API is 3.9. * backups_client(v3) tempest-17.2.0/releasenotes/notes/13/0000775000175100017510000000000013207045130017345 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/13/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yamltempest-17.2.0/releasenotes/notes/13/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yam0000666000175100017510000000070113207044712032301 0ustar zuulzuul00000000000000--- features: - | Define identity service clients as libraries. Add new service clients to the library interface so the other projects can use these modules as stable libraries without any maintenance changes. * identity_client(v2) * groups_client(v3) * trusts_client(v3) * users_client(v3) * identity_client(v3) * roles_client(v3) * inherited_roles_client(v3) * credentials_client(v3) tempest-17.2.0/releasenotes/notes/13/13.0.0-volume-clients-as-library-660811011be29d1a.yaml0000666000175100017510000000032613207044712030024 0ustar zuulzuul00000000000000--- features: - | Define the v1 and v2 types_client clients for the volume service as library interfaces, allowing other projects to use these modules as stable libraries without maintenance changes. tempest-17.2.0/releasenotes/notes/13/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml0000666000175100017510000000067313207044712031320 0ustar zuulzuul00000000000000--- features: - | Define volume service clients as libraries. The following volume service clients are defined as library interface, so the other projects can use these modules as stable libraries without any maintenance changes. * backups_client * encryption_types_client (v1) * encryption_types_client (v2) * qos_clients (v1) * qos_clients (v2) * snapshots_client (v1) * snapshots_client (v2) tempest-17.2.0/releasenotes/notes/13/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml0000666000175100017510000000076213207044712030031 0ustar zuulzuul00000000000000--- prelude: > This release is marking the start of Newton release support in Tempest other: - | OpenStack releases supported at this time are **Liberty**, **Mitaka**, and **Newton**. The release under current development as of this tag is Ocata, meaning that every Tempest commit is also tested against master during the Ocata cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Ocata (or future releases) cloud. tempest-17.2.0/releasenotes/notes/13/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml0000666000175100017510000000032213207044712031036 0ustar zuulzuul00000000000000--- upgrade: - the already deprecated tempest-cleanup standalone command has been removed. The corresponding functionalities can be accessed through the unified `tempest` command (`tempest cleanup`). tempest-17.2.0/releasenotes/notes/13/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml0000666000175100017510000000020413207044712031012 0ustar zuulzuul00000000000000--- deprecations: - Oslo.utils provides same method get_ipv6_addr_by_EUI64, so deprecate it in Newton and remove it in Ocata. ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/13/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yamltempest-17.2.0/releasenotes/notes/13/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yam0000666000175100017510000000031713207044712032076 0ustar zuulzuul00000000000000--- deprecations: - The ``call_until_true`` function is moved from the ``tempest.test`` module to the ``tempest.lib.common.utils.test_utils`` module. Backward compatibility is preserved until Ocata. tempest-17.2.0/releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml0000666000175100017510000000034313207044712032020 0ustar zuulzuul00000000000000--- features: - | The ``list_endpoints`` method of the v3 ``EndPointsClient`` class now has an additional ``**params`` argument that enables passing additional information in the query string of the HTTP request. tempest-17.2.0/releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml0000666000175100017510000000014313207044712026437 0ustar zuulzuul00000000000000--- features: - | Add --save-state option to allow saving state of cloud before tempest run. tempest-17.2.0/releasenotes/notes/http_proxy_config-cb39b55520e84db5.yaml0000666000175100017510000000072613207044712026004 0ustar zuulzuul00000000000000--- features: - Adds a new config options, ``proxy_url``. This options is used to configure running tempest through a proxy server. - The RestClient class in tempest.lib.rest_client has a new kwarg parameters, ``proxy_url``, that is used to set a proxy server. - A new class was added to tempest.lib.http, ClosingProxyHttp. This behaves identically to ClosingHttp except that it requires a proxy url and will establish a connection through a proxy ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yamltempest-17.2.0/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b10000666000175100017510000000036413207044712032625 0ustar zuulzuul00000000000000--- features: - | Add force detach volume feature API to v2 volumes_client library. This feature enables the possibility to force a volume to detach, and roll back an unsuccessful detach operation after you disconnect the volume. tempest-17.2.0/releasenotes/notes/12/0000775000175100017510000000000013207045130017344 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/12/12.2.0-clients_module-16f3025f515bf9ec.yaml0000666000175100017510000000172113207044712026202 0ustar zuulzuul00000000000000--- features: - The Tempest plugin interface contains a new optional method, which allows plugins to declare and automatically register any service client defined in the plugin. - tempest.lib exposes a new stable interface, the clients module and ServiceClients class, which provides a convenient way for plugin tests to access service clients defined in Tempest as well as service clients defined in all loaded plugins. The new ServiceClients class only exposes for now the service clients which are in tempest.lib, i.e. compute, network and image. The remaining service clients (identity, volume and object-storage) will be added in future updates. deprecations: - The new clients module provides a stable alternative to tempest classes manager.Manager and clients.Manager. manager.Manager only exists now to smoothen the transition of plugins to the new interface, but it will be removed shortly without further notice. tempest-17.2.0/releasenotes/notes/12/12.1.0-new-test-utils-module-adf34468c4d52719.yaml0000666000175100017510000000113513207044712027307 0ustar zuulzuul00000000000000--- features: - A new `test_utils` module has been added to tempest.lib.common.utils. It should hold any common utility functions that help writing Tempest tests. - A new utility function called `call_and_ignore_notfound_exc` has been added to the `test_utils` module. That function call another function passed as parameter and ignore the NotFound exception if it raised. deprecations: - tempest.lib.common.utils.misc.find_test_caller has been moved into the tempest.lib.common.utils.test_utils module. Calling the find_test_caller function with its old location is deprecated. tempest-17.2.0/releasenotes/notes/12/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml0000666000175100017510000000025513207044712027423 0ustar zuulzuul00000000000000--- features: - Adds tempest workspaces command and WorkspaceManager. This is used to have a centralized repository for managing different tempest configurations. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/12/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yamltempest-17.2.0/releasenotes/notes/12/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.ya0000666000175100017510000000120313207044712031714 0ustar zuulzuul00000000000000--- upgrade: - The input scenarios functionality no longer exists in tempest. This caused a large number of issues for limited benefit and was only used by a single test, test_server_basic_ops. If you were using this functionality you'll now have to do it manually with a script and/or tempest workspaces deprecations: - All the options in the input-scenario group are now deprecated. These were only used in tree by the now removed input scenarios functionality in test_server_basic_ops. They were only deprecated because there could be external consumers via plugins. They will be removed during the Ocata cycle. tempest-17.2.0/releasenotes/notes/12/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml0000666000175100017510000000016013207044712030452 0ustar zuulzuul00000000000000--- features: - Adds a network version client for querying Neutron's API version discovery URL ("GET /"). ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/12/12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yamltempest-17.2.0/releasenotes/notes/12/12.1.0-tempest-init-global-config-dir-location-changes-122602550000666000175100017510000000137513207044712032040 0ustar zuulzuul00000000000000--- upgrade: - The location on disk that the *tempest init* command looks for has changed. Previously it would attempt to use python packaging's data files to guess where setuptools/distutils were installing data files, which was incredibly unreliable and depended on how you installed tempest and which versions of setuptools, distutils, and python you had installed. Instead, now it will use either /etc/tempest, $XDG_CONFIG_PATH/.config/tempest, or ~/.tempest/etc (attempted in that order). If none of these exist it will create an empty ~/.tempest/etc directory. If you were relying on the previous behavior and none of these directories were being used you will need to move the files to live in one of these directories. tempest-17.2.0/releasenotes/notes/12/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml0000666000175100017510000000037313207044712030361 0ustar zuulzuul00000000000000--- upgrade: - The integrated dashboard scenario test has been removed and is now in a separate tempest plugin tempest-horizon. The removed test coverage can be used by installing tempest-horizon on the server where you run tempest. tempest-17.2.0/releasenotes/notes/12/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml0000666000175100017510000000047313207044712030530 0ustar zuulzuul00000000000000--- features: - | Adds subunit-describe-calls. A parser for subunit streams to determine what REST API calls are made inside of a test and in what order they are called. * Input can be piped in or a file can be specified * Output is shortened for stdout, the output file has more information tempest-17.2.0/releasenotes/notes/12/12.1.0-remove-trove-tests-666522e9113549f9.yaml0000666000175100017510000000021513207044712026500 0ustar zuulzuul00000000000000--- upgrade: - All tests for the Trove project have been removed from tempest. They now live as a tempest plugin in the trove project. tempest-17.2.0/releasenotes/notes/12/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml0000666000175100017510000000103613207044712032071 0ustar zuulzuul00000000000000--- features: - A new optional interface `TempestPlugin.get_service_clients` is available to plugins. It allows them to declare any service client they implement. For now this is used by tempest only, for auto-registration of service clients in the new class `ServiceClients`. - A new singleton class `clients.ClientsRegistry` is available. It holds the service clients registration data from all plugins. It is used by `ServiceClients` for auto-registration of the service clients implemented in plugins. tempest-17.2.0/releasenotes/notes/12/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml0000666000175100017510000000060513207044712030142 0ustar zuulzuul00000000000000--- features: - | Define identity service clients as libraries. The following identity service clients are defined as library interface, so the other projects can use these modules as stable libraries without any maintenance changes. * endpoints_client(v3) * policies_client (v3) * regions_client(v3) * services_client(v3) * projects_client(v3) tempest-17.2.0/releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml0000666000175100017510000000040513207044712027525 0ustar zuulzuul00000000000000--- features: - A new helper method `service_client_config` has been added to the stable module config.py that returns extracts from configuration into a dictionary the configuration settings relevant for the initisialisation of a service client. tempest-17.2.0/releasenotes/notes/12/12.1.0-routers-client-as-library-25a363379da351f6.yaml0000666000175100017510000000035013207044712030052 0ustar zuulzuul00000000000000--- features: - Define routers_client as stable library interface. The routers_client module is defined as library interface, so the other projects can use the module as stable library without any maintenance changes. tempest-17.2.0/releasenotes/notes/12/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml0000666000175100017510000000020313207044712026237 0ustar zuulzuul00000000000000--- features: - Adds the tempest run command to the unified tempest CLI. This new command is used for running tempest tests. tempest-17.2.0/releasenotes/notes/12/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml0000666000175100017510000000052713207044712026424 0ustar zuulzuul00000000000000--- features: - Tempest library auth interface now supports scope. Scope allows to control the scope of tokens requested via the identity API. Identity V2 supports unscoped and project scoped tokens, but only the latter are implemented. Identity V3 supports unscoped, project and domain scoped token, all three are available.tempest-17.2.0/releasenotes/notes/12/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml0000666000175100017510000000050413207044712027011 0ustar zuulzuul00000000000000--- upgrade: - The ``nova_cert`` option default is changed to ``False``. The nova certification management APIs were a hold over from ec2, and are not used by any other parts of nova. They are deprecated for removal in nova after the newton release. This makes false a more sensible default going forward.tempest-17.2.0/releasenotes/notes/12/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml0000666000175100017510000000043713207044712031034 0ustar zuulzuul00000000000000--- features: - RestClient now supports setting timeout in urllib3.poolmanager. Clients will use CONF.service_clients.http_timeout for timeout value to wait for http request to response. - KeystoneAuthProvider will accept http_timeout and will use it in get_credentials. tempest-17.2.0/releasenotes/notes/12/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml0000666000175100017510000000076113207044712031053 0ustar zuulzuul00000000000000--- prelude: > This release is marking the end of Kilo release support in Tempest other: - OpenStack Releases Supported after this release are **Liberty** and **Mitaka** The release under current development as of this tag is Newton, meaning that every Tempest commit is also tested against master during the Newton cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Newton (or future releases) cloud. tempest-17.2.0/releasenotes/notes/12/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml0000666000175100017510000000062013207044712030117 0ustar zuulzuul00000000000000--- features: - The RestClient (in tempest.lib.common.rest_client) now supports POSTing and PUTing data with chunked transfer encoding. Just pass an `iterable` object as the `body` argument and set the `chunked` argument to `True`. - A new generator called `chunkify` is added in tempest.lib.common.utils.data_utils that yields fixed-size chunks (slices) from a Python sequence. tempest-17.2.0/releasenotes/notes/12/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml0000666000175100017510000000031513207044712026115 0ustar zuulzuul00000000000000--- upgrade: - The previously deprecated Javelin utility has been removed from Tempest. As an alternative Ansible can be used to construct similar yaml workflows to what Javelin used to provide. tempest-17.2.0/releasenotes/notes/12/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml0000666000175100017510000000103413207044712030113 0ustar zuulzuul00000000000000--- features: - | Define volume service clients as libraries The following volume service clients are defined as library interface, so the other projects can use these modules as stable libraries without any maintenance changes. * availability_zone_client(v1) * availability_zone_client(v2) * extensions_client(v1) * extensions_client(v2) * hosts_client(v1) * hosts_client(v2) * quotas_client(v1) * quotas_client(v2) * services_client(v1) * services_client(v2) tempest-17.2.0/releasenotes/notes/12/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml0000666000175100017510000000030713207044712032245 0ustar zuulzuul00000000000000--- upgrade: - The deprecated legacy credential provider has been removed. The only way to configure credentials in tempest now is to use the dynamic or preprovisioned credential providers tempest-17.2.0/releasenotes/notes/12/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml0000666000175100017510000000060213207044712030575 0ustar zuulzuul00000000000000--- features: - | Define identity service clients as libraries The following identity service clients are defined as library interface, so the other projects can use these modules as stable libraries without any maintenance changes. * endpoints_client(v2) * roles_client(v2) * services_client(v2) * tenants_client(v2) * users_client(v2) tempest-17.2.0/releasenotes/notes/12/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml0000666000175100017510000000070713207044712027763 0ustar zuulzuul00000000000000--- features: - | Define image service clients as libraries The following image service clients are defined as library interface, so the other projects can use these modules as stable libraries without any maintenance changes. * image_members_client(v1) * images_client(v1) * image_members_client(v2) * images_client(v2) * namespaces_client(v2) * resource_types_client(v2) * schemas_client(v2) tempest-17.2.0/releasenotes/notes/12/12.1.0-bug-1486834-7ebca15836ae27a9.yaml0000666000175100017510000000030213207044712024662 0ustar zuulzuul00000000000000--- features: - | Tempest library auth interface now supports filtering with catalog name. Note that filtering by name is only successful if a known service type is provided. tempest-17.2.0/releasenotes/notes/11/0000775000175100017510000000000013207045130017343 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/11/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml0000666000175100017510000000013213207044712031554 0ustar zuulzuul00000000000000--- features: - Tempest library interface addition(API Microversion testing interfaces).tempest-17.2.0/releasenotes/notes/11/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml0000666000175100017510000000076613207044712030675 0ustar zuulzuul00000000000000--- prelude: > This release is marking the start of Mitaka release support in tempest other: - OpenStack Releases Supported at this time are **Kilo**, **Liberty**, **Mitaka** The release under current development as of this tag is Newton, meaning that every Tempest commit is also tested against master during the Newton cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Newton (or future releases) cloud. tempest-17.2.0/releasenotes/notes/11/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml0000666000175100017510000000011313207044712031075 0ustar zuulzuul00000000000000--- features: - Compute Microversion testing support in Service Clients. tempest-17.2.0/releasenotes/notes/add-volume-group-snapshots-tempest-tests-840df3da26590f5e.yaml0000666000175100017510000000033513207044712032265 0ustar zuulzuul00000000000000--- features: - | Add group_snapshots client for the volume service as library. Add tempest tests for create group snapshot, delete group snapshot, show group snapshot, and list group snapshots volume APIs. tempest-17.2.0/releasenotes/notes/add-params-to-v2-list-backups-api-c088d2b4bfe90247.yaml0000666000175100017510000000033713207044712030363 0ustar zuulzuul00000000000000--- features: - | The ``list_backups`` method of the v2 ``BackupsClient`` class now has an additional ``**params`` argument that enables passing additional information in the query string of the HTTP request. tempest-17.2.0/releasenotes/notes/add-domain-param-in-cliclient-a270fcf35c8f09e6.yaml0000666000175100017510000000075613207044712027773 0ustar zuulzuul00000000000000--- fixes: - | Allow to specify new domain parameters: * `user_domain_name` * `user_domain_id` * `project_domain_name` * `project_domain_id` for CLIClient class, whose values will be substituted to ``--os-user-domain-name``, ``--os-user-domain-id``, ``--os-project-domain-name`` and ``--os-project-domain-id`` respectively during command execution. This allows to prevent possible test failures with authentication in Keystone v3. Bug: #1719687 tempest-17.2.0/releasenotes/notes/remove-heat-tests-9efb42cac3e0b306.yaml0000666000175100017510000000024713207044712025737 0ustar zuulzuul00000000000000--- upgrade: - The Heat API tests have been removed from tempest, they were unmaintained. The future direction of api test for heat is their in-tree Gabbi tests tempest-17.2.0/releasenotes/notes/add-kwargs-to-delete-vol-of-vol-client-1ecde75beb62933c.yaml0000666000175100017510000000033613207044712031553 0ustar zuulzuul00000000000000--- features: - | The ``delete_volume`` method of the ``VolumesClient`` class now has an additional ``**params`` argument that enables passing additional information in the query string of the HTTP request. tempest-17.2.0/releasenotes/notes/add-is-resource-deleted-sg-client-f4a7a7a54ff024d7.yaml0000666000175100017510000000017313207044712030571 0ustar zuulzuul00000000000000--- features: - | Implement the `rest_client` method `is_resource_deleted` in the network security group client. tempest-17.2.0/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml0000666000175100017510000000026213207044712032475 0ustar zuulzuul00000000000000--- features: - Add support of args and kwargs when calling func in call_until_true, also to log the cost time when call_until_true returns True or False for debuggin. tempest-17.2.0/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml0000666000175100017510000000124113207044712027353 0ustar zuulzuul00000000000000--- features: - | Add a new function called ``compare_version_header_to_response`` to ``tempest.lib.common.api_version_utils``, which compares the API micoversion in the response header to another microversion using the comparators defined in ``tempest.lib.common.api_version_request.APIVersionRequest``. It is now possible to determine how to retrieve an attribute from a response body of an API call, depending on the returned microversion. Add a new exception type called ``InvalidParam`` to ``tempest.lib.exceptions``, allowing the possibility of raising an exception if an invalid parameter is passed to a library function. tempest-17.2.0/releasenotes/notes/remove-get-ipv6-addr-by-EUI64-c79972d799c7a430.yaml0000666000175100017510000000021213207044712027157 0ustar zuulzuul00000000000000--- upgrade: - | Remove deprecated get_ipv6_addr_by_EUI64 method from data_utils. Use the same method from oslo_utils.netutils. tempest-17.2.0/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml0000666000175100017510000000031713207044712027477 0ustar zuulzuul00000000000000--- fixes: - | Fix list_group_snapshots API in v3 group_snapshots_client: Bug#1715786. The url path for list group snapshots with details API is changed from ``?detail=True`` to ``/detail``. tempest-17.2.0/releasenotes/notes/16/0000775000175100017510000000000013207045130017350 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000017000000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d00000666000175100017510000000021413207044712033157 0ustar zuulzuul00000000000000--- upgrade: - | The deprecated config option 'allow_port_security_disabled' from compute_feature_enabled group has been removed. tempest-17.2.0/releasenotes/notes/16/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml0000666000175100017510000000034413207044712031124 0ustar zuulzuul00000000000000--- deprecations: - | Deprecate the client_parameters argument in `tempest.lib.services.clients.ServiceClients`. The parameter is actually not honoured already - see https://bugs.launchpad.net/tempest/+bug/1680915 tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml0000666000175100017510000000063213207044712031720 0ustar zuulzuul00000000000000--- upgrade: - The default value of rand_name()'s prefix argument is changed to 'tempest' from None to identify resources are created by Tempest. deprecations: - The resources_prefix is marked as deprecated because it is enough to set 'tempest' as the prefix on rand_name() to ideintify resources which are created by Tempest and no projects set this option on OpenStack dev community. ././@LongLink0000000000000000000000000000016300000000000011215 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf0000666000175100017510000000014413207044712032447 0ustar zuulzuul00000000000000--- features: - Define the compute server evacuate client method in the servers_client library. ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.0000666000175100017510000000036413207044712031746 0ustar zuulzuul00000000000000--- deprecations: - The ``skip_unless_config`` and ``skip_if_config`` decorators in the ``config`` module have been deprecated and will be removed in the Queens dev cycle. Use the ``testtools.skipUnless`` (or a variation of) instead. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml0000666000175100017510000000025313207044712030764 0ustar zuulzuul00000000000000--- features: - | Add versions_client module for image service. This new module provides list_versions() method which shows API versions from Image service. ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yam0000666000175100017510000000047513207044712031705 0ustar zuulzuul00000000000000--- features: - | Add the unmanage volume API service method in v2 volumes_client library. Define v2 volume_manage_client client for the volume service as library interfaces, allowing other projects to use this module as stable libraries without maintenance changes. * volume_manage_client(v2) tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml0000666000175100017510000000032113207044712031643 0ustar zuulzuul00000000000000--- deprecations: - | The ``deactivate_image`` configuration switch from the ``config`` module is deprecated. It was added to support the older-than-kilo releases which we don't support anymore. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml0000666000175100017510000000030713207044712031066 0ustar zuulzuul00000000000000--- features: - | Adds a new cli option to tempest run, --combine, which is used to indicate you want the subunit stream output combined with the previous run's in the testr repository tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml0000666000175100017510000000060613207044712031277 0ustar zuulzuul00000000000000--- fixes: - | The 'sahara' config option in the 'service-available' group has been moved to the sahara plugin (openstack/sahara-tests) along with tests and service client during the Ocata timeframe. A 'sahara' config option was left over on Tempest side, and it's removed now. As long as the sahara plugin is installed, this change as no impact on users of sahara tests. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.ya0000666000175100017510000000031413207044712032304 0ustar zuulzuul00000000000000--- deprecations: - | The ``dvr_extra_resources`` configuration switch from the ``config`` module is deprecated. It was added to support the Liberty Release which we don't support anymore. tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml0000666000175100017510000000031413207044712030125 0ustar zuulzuul00000000000000upgrade: - Remove Cinder v1 API tests. Cinder v1 API has been deprecated since Juno release, and Juno is not supported by current Tempest. Then Cinder v1 API tests are removed from Tempest. tempest-17.2.0/releasenotes/notes/16/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml0000666000175100017510000000024013207044712026734 0ustar zuulzuul00000000000000--- upgrade: - Tempest now defaults to using Keystone v3 API for the authentication, because Keystone v3 API is CURRENT and the v2 API is deprecated. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml0000666000175100017510000000075413207044712031264 0ustar zuulzuul00000000000000--- upgrade: - The ``JSON_ENC`` and ``TXT_ENC`` option in the ``_error_checker`` section have been added with additional content-type which are defined in RFC7231 but missing in the currnt rest_client.py file. The lack of these additional content-type will cause defcore test to fail for OpenStack public cloud which uses tomcat module in the api gateway. The additions are ``application/json;charset=utf-8``, ``text/html;charset=utf-8``,``text/plain;charset=utf-8``././@LongLink0000000000000000000000000000020000000000000011205 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-security-groups-by-servers-to-servers-client-li0000666000175100017510000000027613207044712032776 0ustar zuulzuul00000000000000--- features: - | Add the list security groups by server API to the servers_client library. This feature enables the possibility to list security groups for a server instance. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml0000666000175100017510000000034513207044712030254 0ustar zuulzuul00000000000000--- features: - | Interface show_quota_set of compute quotas_client has been extended to include the argument "detail", which allows for detailed quota set information for a project to be retrieved, if set to True. ././@LongLink0000000000000000000000000000016100000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000000666000175100017510000000031613207044712032135 0ustar zuulzuul00000000000000--- features: - | Add missing API call, list all role inference rules, to the roles_client library. This feature enables the possibility of listing all role inference rules in the system. ././@LongLink0000000000000000000000000000017500000000000011220 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part0000666000175100017510000000104513207044712033117 0ustar zuulzuul00000000000000--- upgrade: - | Below deprecated config options from compute group have been removed. Corresponding config options already been available in validation group. - ``compute.image_ssh_user`` (available as ``validation.image_ssh_user``) - ``compute.ssh_user`` (available as ``validation.image_ssh_user``) - ``scenario.ssh_user`` (available as ``validation.image_ssh_user``) - ``compute.network_for_ssh`` (available as ``validation.network_for_ssh``) - ``compute.ping_timeout `` (available as ``validation.ping_timeout``) ././@LongLink0000000000000000000000000000017000000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-update-encryption-type-to-encryption-types-client-f30000666000175100017510000000031113207044712032730 0ustar zuulzuul00000000000000--- features: - | Add update encryption type API to the v2 encryption_types_client library. This feature enables the possibility to update an encryption type for an existing volume type. ././@LongLink0000000000000000000000000000016000000000000011212 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc10000666000175100017510000000020513207044712032645 0ustar zuulzuul00000000000000--- upgrade: - | The deprecated ``volume_services`` option in the ``volume_feature_enabled`` section has now been removed. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml0000666000175100017510000000041613207044712031044 0ustar zuulzuul00000000000000--- features: - | Add remote_client under tempest.lib. This remote_client under tempest.lib is defined as stable interface, and now this module provides the following essential methods. - exec_command - validate_authentication - ping_host tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml0000666000175100017510000000037413207044712031263 0ustar zuulzuul00000000000000--- upgrade: - The *call_until_true* of *test* module is removed because it was marked as deprecated and Tempest provides it from *test_utils* as a stable interface instead. Please switch to use *test_utils.call_until_true* if necessary. tempest-17.2.0/releasenotes/notes/16/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml0000666000175100017510000000127613207044712030132 0ustar zuulzuul00000000000000--- features: - | Define volume transfers service clients as libraries. The following volume transfers service clients are defined as library interface. * transfers_client(v2) deprecations: - | Deprecate volume v2 transfers resource methods from volumes_client(v2) libraries. Same methods are available in new transfers service client: transfers_client(v2) The following methods of volume v2 volumes_clients have been deprecated: * create_volume_transfer (v2.volumes_client) * show_volume_transfer (v2.volumes_client) * list_volume_transfers (v2.volumes_client) * delete_volume_transfer (v2.volumes_client) * accept_volume_transfer (v2.volumes_client) ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yam0000666000175100017510000000026113207044712031533 0ustar zuulzuul00000000000000--- features: - | Add versions_client module for identity service. This new module provides list_versions() method which shows API versions from Identity service. ././@LongLink0000000000000000000000000000015500000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f10000666000175100017510000000016513207044712032126 0ustar zuulzuul00000000000000--- upgrade: - | The deprecated config option 'reseller' from identity_feature_enabled group has been removed. tempest-17.2.0/releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml0000666000175100017510000000036713207044712030115 0ustar zuulzuul00000000000000--- features: - | Add server tags APIs to the servers_client library. This feature enables the possibility of upating, deleting and checking existence of a tag on a server, as well as updating and deleting all tags on a server. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.ya0000666000175100017510000000030113207044712031726 0ustar zuulzuul00000000000000--- deprecations: - The ``skip_unless_attr`` decorator in lib/decorators.py has been deprecated, please use the standard ``testtools.skipUnless`` and ``testtools.skipIf`` decorators. tempest-17.2.0/releasenotes/notes/16/16.0.0-mitaka-eol-88ff8355fff81b55.yaml0000666000175100017510000000074013207044712025254 0ustar zuulzuul00000000000000--- prelude: > This release indicates end of support for Mitaka in Tempest. other: - | OpenStack Releases Supported after this release are **Newton** and **Ocata** The release under current development as of this tag is Pike, meaning that every Tempest commit is also tested against master branch during the Pike cycle. However, this does not necessarily mean that using Tempest as of this tag will work against Pike (or future releases) cloud. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml0000666000175100017510000000025513207044712031735 0ustar zuulzuul00000000000000--- features: - | Add versions_client module for volume service. This new module provides list_versions() method which shows API versions from Volume service. ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a40000666000175100017510000000041613207044712031636 0ustar zuulzuul00000000000000--- features: - | Add cascade parameter to volumes_client. This option provides the ability to delete a volume and have Cinder handle deletion of snapshots associated with that volume by passing an additional argument to volume delete, "cascade=True". tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml0000666000175100017510000000056413207044712031400 0ustar zuulzuul00000000000000--- deprecations: - | Volume v1 API is deprecated and the v3 are CURRENT. Tempest doesn't need to test the v1 API as the default. The volume config option 'api_v1' has been marked as deprecated. upgrade: - | The volume config option 'api_v1' default is changed to ``False`` because the volume v1 API has been deprecated since Juno release. tempest-17.2.0/releasenotes/notes/16/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml0000666000175100017510000000035313207044712030267 0ustar zuulzuul00000000000000--- features: - | Add the list auth projects API to the identity client library. This feature enables the possibility to list projects that are available to be scoped to based on the X-Auth-Token provided in the request. ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e0000666000175100017510000000047013207044712032212 0ustar zuulzuul00000000000000--- deprecations: - | Glance v1 APIs are deprecated and v2 are current. Tempest should tests only v2 APIs. Below API version selection config options for glance have been deprecated and will be removed in future. * CONF.image_feature_enabled.api_v2 * CONF.image_feature_enabled.api_v1 ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e0000666000175100017510000000014013207044712032044 0ustar zuulzuul00000000000000--- features: - Add a new client to handle the OAUTH consumers feature from the identity API. ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38ea0000666000175100017510000000065213207044712032546 0ustar zuulzuul00000000000000--- upgrade: - | The deprecated config option 'dvr_extra_resources' from network group has been removed. This option was for extra resources which were provisioned to bind a router to Neutron L3 agent. The extra resources need to be provisioned in Liberty release or older, and are not required since Mitaka release. Current Tempest doesn't support Liberty, so this option has been removed from Tempest. ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/16/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yamltempest-17.2.0/releasenotes/notes/16/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fed0000666000175100017510000000021713207044712031473 0ustar zuulzuul00000000000000--- fixes: - | Fix below volume v2 service clients to make v2 API call: Bug#1667354 - SchedulerStatsClient - CapabilitiesClient tempest-17.2.0/releasenotes/notes/extra-compute-services-tests-92b6c0618972e02f.yaml0000666000175100017510000000031713207044712027655 0ustar zuulzuul00000000000000--- features: - | Add the ``disable_log_reason`` and the ``update_forced_down`` API endpoints to the compute ``services_client``. Add '2.11' compute validation schema for compute services API. ././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-248d41827daf2a0c.yamltempest-17.2.0/releasenotes/notes/add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-20000666000175100017510000000032513207044712033536 0ustar zuulzuul00000000000000--- features: - | Add reset group snapshot status API to v3 group_snapshots_client library, min_microversion of this API is 3.19. This feature enables the possibility to reset group snapshot status. tempest-17.2.0/releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml0000666000175100017510000000030613207044712030747 0ustar zuulzuul00000000000000--- features: - | A new ``related_bug`` decorator has been added to ``tempest.lib.decorators``. Use it to decorate and tag a test that was added in relation to a launchpad bug report. tempest-17.2.0/releasenotes/notes/start-of-pike-support-f2a1b7ea8e8b0311.yaml0000666000175100017510000000076713207044712026510 0ustar zuulzuul00000000000000--- prelude: > This release marks the start of support for the Pike release in Tempest. other: - OpenStack Releases supported after this release are **Pike**, **Ocata**, and **Newton**. The release under current development of this tag is Queens, meaning that every Tempest commit is also tested against master during the Queens cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Queens (or future release) cloud. tempest-17.2.0/releasenotes/notes/add-volume-group-types-tempest-tests-1298ab8cb4fe8b7b.yaml0000666000175100017510000000025113207044712031546 0ustar zuulzuul00000000000000--- features: - | Add list_group_type and show_group_type in the group_types client for the volume service. Add tests for create/delete/show/list group types. ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yamltempest-17.2.0/releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.0000666000175100017510000000050613207044712032675 0ustar zuulzuul00000000000000--- features: - | Define v3 volumes_client for the volume service as a library interface, allowing other projects to use this module as a stable library without maintenance changes. Add show volume summary API to v3 volumes_client library, min_microversion of this API is 3.12. * volumes_client(v3) ././@LongLink0000000000000000000000000000015500000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/remove-deprecated-volume-apis-from-v2-volumes-client-cf35e5b4cca89860.yamltempest-17.2.0/releasenotes/notes/remove-deprecated-volume-apis-from-v2-volumes-client-cf35e5b4cca890000666000175100017510000000045613207044712033213 0ustar zuulzuul00000000000000--- upgrade: - | Remove deprecated APIs (``show_pools`` and ``show_backend_capabilities``) from volume v2 volumes_client, and the deprecated APIs are re-realized in volume v2 scheduler_stats_client (``list_pools``) and capabilities_client (``show_backend_capabilities``) accordingly. tempest-17.2.0/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml0000666000175100017510000000030013207044712030552 0ustar zuulzuul00000000000000--- features: - | Add groups and group_types clients for the volume service as library. Add tempest tests for create group, delete group, show group, and list group volume APIs. ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/raise-exception-when-error-deleting-on-volume-18d0d0c5886212dd.yamltempest-17.2.0/releasenotes/notes/raise-exception-when-error-deleting-on-volume-18d0d0c5886212dd.yam0000666000175100017510000000054213207044712032670 0ustar zuulzuul00000000000000--- upgrade: - | Tempest checks a volume delete by waiting for NotFound(404) on show_volume(). Sometime a volume delete fails and the volume status becomes error_deleting which means the delete is failed. So Tempest doesn't need to wait anymore. A new release of Tempest raises an exception DeleteErrorException instead of waiting. tempest-17.2.0/releasenotes/notes/plugin-client-registration-enhancements-e09131742391225b.yaml0000666000175100017510000000104213207044712031647 0ustar zuulzuul00000000000000--- features: - | When registering service clients from installed plugins, all registrations are now processed, even if one or more fails. All exceptions encountered during the registration process are recorded. If at least one exception was encountered, the registration process fails and all interim errors are reported. - | The __repr__ method is now implemented for the base `tempest.Exception` class, its implementation is identical to __str__: it reports the error message merged with input parameters. tempest-17.2.0/releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml0000666000175100017510000000115313207044712026525 0ustar zuulzuul00000000000000--- features: - The tempest module tempest.common.preprov_creds which is used to provide credentials from a list of preprovisioned resources has been migrated into tempest lib at tempest.lib.common.preprov_creds. - The InvalidTestResource exception class from tempest.exceptions has been migrated into tempest.lib.exceptions - The tempest module tempest.common.fixed_network which provided utilities for finding fixed networks by and helpers for picking the network to use when multiple tenant networks are available has been migrated into tempest lib at tempest.lib.common.fixed_network. tempest-17.2.0/releasenotes/notes/disable-identity-v2-testing-4ef1565d1a5aedcf.yaml0000666000175100017510000000046013207044712027702 0ustar zuulzuul00000000000000--- upgrade: - | As of the Queens release, tempest no longer tests the identity v2.0 API because the majority of the v2.0 API have been removed from the identity project. Once the Queens release reaches end-of-life, we can remove the v2.0 tempest tests and clean up v2.0 testing cruft. tempest-17.2.0/releasenotes/notes/10/0000775000175100017510000000000013207045130017342 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/10/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml0000666000175100017510000000007413207044712026403 0ustar zuulzuul00000000000000--- other: - Start using reno for managing release notes. tempest-17.2.0/releasenotes/notes/10/10.0-supported-openstack-releases-b88db468695348f6.yaml0000666000175100017510000000072013207044712030531 0ustar zuulzuul00000000000000--- 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. tempest-17.2.0/releasenotes/notes/10/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml0000666000175100017510000000063013207044712030027 0ustar zuulzuul00000000000000--- 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: https://docs.openstack.org/tempest/latest/library.html#current-library-apis features: - Tempest library interface tempest-17.2.0/releasenotes/notes/identity-tests-domain-drivers-76235f6672221e45.yaml0000666000175100017510000000040613207044712027651 0ustar zuulzuul00000000000000--- features: - | A new boolean config option ``domain_specific_drivers`` is added to the section ``identity-feature-enabled``. This option must be enabled when testing an environment that is configured to use domain-specific identity drivers. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yamltempest-17.2.0/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.ya0000666000175100017510000000030313207044712032501 0ustar zuulzuul00000000000000--- fixes: - | When receiving nullable list as a response body, tempest.lib rest_client module raised an exception without valid json deserialization. A new release fixes this bug. tempest-17.2.0/releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml0000666000175100017510000000011513207044712032224 0ustar zuulzuul00000000000000--- features: - | Add validation schema for Nova server diagnostics APItempest-17.2.0/releasenotes/notes/fix-remoteclient-default-ssh-shell-prologue-33e99343d086f601.yaml0000666000175100017510000000031113207044712032445 0ustar zuulzuul00000000000000--- fixes: - | Fix RemoteClient default ssh_shell_prologue: Bug#1707478 The default ssh_shell_proloque has been modified from specifying erroneous PATH=$$PATH:/sbin to PATH=$PATH:/sbin. tempest-17.2.0/releasenotes/notes/test-clients-stable-for-plugin-90b1e7dc83f28ccd.yaml0000666000175100017510000000053113207044712030343 0ustar zuulzuul00000000000000--- features: - | Two extra modules are now marked as stable for plugins, test.py and clients.py. The former includes the test base class with its automatic credentials provisioning and test resource managing fixtures. The latter is built on top of ServiceClients and it adds aliases and a few custom configurations to it. tempest-17.2.0/releasenotes/notes/14/0000775000175100017510000000000013207045130017346 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/notes/14/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml0000666000175100017510000000025013207044712031265 0ustar zuulzuul00000000000000--- features: - A new optional parameter `port` for ssh client (`tempest.lib.common.ssh.Client`) to specify destination port for a host. The default value is 22. ././@LongLink0000000000000000000000000000016100000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/14/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yamltempest-17.2.0/releasenotes/notes/14/14.0.0-add-error-code-translation-to-versions-clients-acbc782920000666000175100017510000000047713207044712032400 0ustar zuulzuul00000000000000--- upgrade: - Add an error translation to list_versions() of versions_client of both compute and network. This can affect users who are expecting that these clients return error status code instead of the exception. It is needed to change the code for handling the exception like the other clients code. tempest-17.2.0/releasenotes/notes/14/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml0000666000175100017510000000015313207044712031241 0ustar zuulzuul00000000000000--- upgrade: - The Negative Tests Generator has been removed (it was not used by any Tempest tests). tempest-17.2.0/releasenotes/notes/14/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml0000666000175100017510000000024613207044712027066 0ustar zuulzuul00000000000000--- upgrade: - All tests for the Sahara project have been removed from Tempest. They now live as a Tempest plugin in the ``openstack/sahara-tests`` repository. ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/14/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yamltempest-17.2.0/releasenotes/notes/14/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984a0000666000175100017510000000037413207044712031743 0ustar zuulzuul00000000000000--- features: - Define the identity service role_assignments_client as a library. Add role_assignments_client to the library interface so the other projects can use this module as a stable library without any maintenance changes. tempest-17.2.0/releasenotes/notes/14/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml0000666000175100017510000000035713207044712031070 0ustar zuulzuul00000000000000--- features: - The cred_client module was added to tempest.lib. This module provides a wrapper to the keystone services client which provides a uniform interface that abstracts out the differences between keystone api versions. tempest-17.2.0/releasenotes/notes/14/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml0000666000175100017510000000021513207044712027576 0ustar zuulzuul00000000000000--- upgrade: - All tests for the Ironic project have been removed from Tempest. Those exist as a Tempest plugin in the Ironic project. tempest-17.2.0/releasenotes/notes/14/14.0.0-add-image-clients-af94564fb34ddca6.yaml0000666000175100017510000000032013207044712026602 0ustar zuulzuul00000000000000--- features: - | As in the [doc]: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html, there are some apis are not included, add them. * namespace_properties_client(v2) tempest-17.2.0/releasenotes/notes/14/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml0000666000175100017510000000105513207044712026524 0ustar zuulzuul00000000000000--- prelude: > This release is marking the end of Liberty release support in Tempest upgrade: - The Stress tests framework and all the stress tests have been removed. other: - | OpenStack releases supported at this time are **Mitaka** and **Newton**. The release under current development as of this tag is Ocata, meaning that every Tempest commit is also tested against master during the Ocata cycle. However, this does not necessarily mean that using Tempest as of this tag will work against a Ocata (or future releases) cloud. tempest-17.2.0/releasenotes/notes/14/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml0000666000175100017510000000032113207044712030535 0ustar zuulzuul00000000000000features: - | Define the Volume v3 service clients as library interfaces, allowing other projects to use these modules as stable libraries without maintenance changes. * messages_client(v3) tempest-17.2.0/releasenotes/notes/14/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml0000666000175100017510000000043313207044712027420 0ustar zuulzuul00000000000000--- deprecations: - The *bootable* config option in the *volume_feature_enabled* group is removed because the corresponding feature os-set_bootable has been implemented 2.5 years ago and all OpenStack versions which are supported by Tempest should support the feature. tempest-17.2.0/releasenotes/notes/14/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml0000666000175100017510000000017413207044712030613 0ustar zuulzuul00000000000000--- features: - A Neutron Service Providers client is added to deal with resources of the '/service-providers' route. ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/14/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yamltempest-17.2.0/releasenotes/notes/14/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a8710000666000175100017510000000044013207044712031454 0ustar zuulzuul00000000000000--- features: - The cred_provider abstract class which serves as the basis for both of tempest's cred providers, pre-provisioned credentials and dynamic credentials, is now a library interface. This provides the common signature required for building a credential provider. tempest-17.2.0/releasenotes/notes/14/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml0000666000175100017510000000055213207044712030110 0ustar zuulzuul00000000000000--- features: - | Define volume service clients as libraries. The following volume service clients are defined as library interface, so the other projects can use these modules as stable libraries without any maintenance changes. * volumes_client(v1) * volumes_client(v2) * capabilities_client(v2) * scheduler_stats_client(v2) tempest-17.2.0/releasenotes/notes/14/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml0000666000175100017510000000056013207044712031023 0ustar zuulzuul00000000000000--- deprecations: - The *api_extensions* config option in the *compute-feature-enabled* group is now deprecated. This option will be removed from tempest when all the OpenStack releases supported by tempest no longer support the API extensions mechanism. This was removed from Nova during the Newton cycle, so this will be removed at the Mitaka EOL. tempest-17.2.0/releasenotes/notes/14/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml0000666000175100017510000000010513207044712027604 0ustar zuulzuul00000000000000--- features: - The volume_limits client was added to tempest.lib. tempest-17.2.0/releasenotes/notes/intermediate-queens-release-2f9f305775fca454.yaml0000666000175100017510000000023713207044712027553 0ustar zuulzuul00000000000000--- prelude: > This is an intermediate release during the Queens development cycle to make new functionality available to plugins and other consumers. tempest-17.2.0/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml0000666000175100017510000000045413207044712032305 0ustar zuulzuul00000000000000--- features: - | Define below object storage service clients as libraries. Add new service clients to the library interface so the other projects can use these modules as stable libraries without any maintenance changes. * bulk_middleware_client * capabilities_client ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/remove-deprecated-apis-from-v2-volumes-client-3ca4a5db5fea518f.yamltempest-17.2.0/releasenotes/notes/remove-deprecated-apis-from-v2-volumes-client-3ca4a5db5fea518f.yam0000666000175100017510000000044713207044712033056 0ustar zuulzuul00000000000000--- upgrade: - | Remove deprecated APIs from volume v2 volumes_client, and the deprecated APIs are re-realized in volume v2 transfers_client. * create_volume_transfer * show_volume_transfer * list_volume_transfers * delete_volume_transfer * accept_volume_transfer tempest-17.2.0/releasenotes/notes/add-show-host-to-hosts-client-library-c60c4eb49d139480.yaml0000666000175100017510000000022613207044712031330 0ustar zuulzuul00000000000000--- features: - | Add show host API to the volume v2 hosts_client library. This feature enables the possibility to show details for a host. ././@LongLink0000000000000000000000000000016000000000000011212 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f35276561.yamltempest-17.2.0/releasenotes/notes/add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f350000666000175100017510000000024313207044712032770 0ustar zuulzuul00000000000000--- features: - | Add show volume image metadata API to v2 volumes_client library. This feature enables the possibility to show volume's image metadata. tempest-17.2.0/releasenotes/notes/api_v2_admin_flag-dea5ca9bc2ce63bc.yaml0000666000175100017510000000037713207044712026133 0ustar zuulzuul00000000000000--- features: - | A new configuration flag api_v2_admin is introduced in the identity feature flag group to allow for enabling/disabling all identity v2 admin tests. The new flag only applies when the existing api_v2 flag is set to True tempest-17.2.0/releasenotes/notes/add-update-group-tempest-tests-72f8ec19b2809849.yaml0000666000175100017510000000013113207044712030071 0ustar zuulzuul00000000000000--- features: - | Add update_group to groups_client in the volume service library. ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-list-volume-transfers-with-detail-to-transfers-client-80169bf78cf4fa66.yamltempest-17.2.0/releasenotes/notes/add-list-volume-transfers-with-detail-to-transfers-client-80169bf70000666000175100017510000000026313207044712033266 0ustar zuulzuul00000000000000--- features: - | Add list volume transfers with details API to v2 transfers_client library. This feature enables the possibility to list volume transfers with details. tempest-17.2.0/releasenotes/notes/list-auth-domains-v3-endpoint-9ec60c7d3011c397.yaml0000666000175100017510000000030013207044712027651 0ustar zuulzuul00000000000000--- features: - | Add ``list_auth_domains`` API endpoint to the identity v3 client. This allows the possibility of listing all domains a user has access to via role assignments. ././@LongLink0000000000000000000000000000016000000000000011212 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yamltempest-17.2.0/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd20000666000175100017510000000025313207044712033125 0ustar zuulzuul00000000000000--- features: - | Defines the identity v3 OS-EP-FILTER extension API client. This client manages associations between endpoints, projects along with groups. tempest-17.2.0/releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml0000666000175100017510000000034513207044712025540 0ustar zuulzuul00000000000000--- features: - | Adds a new cli option to tempest run, --load-list to specify target tests to run from a list-file. The list-file supports the output format of the tempest run --list-tests command. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/make-object-storage-client-as-stable-interface-d1b07c7e8f17bef6.yamltempest-17.2.0/releasenotes/notes/make-object-storage-client-as-stable-interface-d1b07c7e8f17bef6.ya0000666000175100017510000000046713207044712032764 0ustar zuulzuul00000000000000--- features: - | Define below object storage service clients as libraries. Add new service clients to the library interface so the other projects can use these modules as stable libraries without any maintenance changes. * account_client * container_client * object_client ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.yamltempest-17.2.0/releasenotes/notes/use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.ya0000666000175100017510000000040713207044712032542 0ustar zuulzuul00000000000000--- upgrade: - verify_tempest_config command starts using extension_client of cinder v2 API only, because cinder v3 API is current and v2 and v1 are deprecated and v3 extension API is the same as v2. Then we can reuse the v2 client for v3 API also. tempest-17.2.0/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml0000666000175100017510000000020213207044712027543 0ustar zuulzuul00000000000000--- upgrade: - | The volume config option 'api_v3' default is changed to ``True`` because the volume v3 API is CURRENT. tempest-17.2.0/releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml0000666000175100017510000000013413207044712030671 0ustar zuulzuul00000000000000--- features: - Add a new client to handle the OAUTH token feature from the identity API. tempest-17.2.0/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml0000666000175100017510000000040613207044712031252 0ustar zuulzuul00000000000000--- features: - | A new config option 'manage_snapshot_ref' is added in the volume section, to specify snapshot ref parameter for different storage backend drivers when managing an existing snapshot. By default it is set to fit the LVM driver. tempest-17.2.0/releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml0000666000175100017510000000053313207044712025363 0ustar zuulzuul00000000000000--- features: - | Pause teardown When pause_teardown flag in tempest.conf is set to True a pdb breakpoint is added to tearDown and tearDownClass methods in test.py. This allows to pause cleaning resources process, so that used resources can be examined. Closer examination of used resources may lead to faster debugging. tempest-17.2.0/releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml0000666000175100017510000000024513207044712026324 0ustar zuulzuul00000000000000--- features: - | The tempest module tempest.common.dynamic creds which is used for dynamically allocating credentials has been migrated into tempest lib. tempest-17.2.0/releasenotes/notes/move-attr-decorator-to-lib-a1e80c42ba9c5392.yaml0000666000175100017510000000035513207044712027311 0ustar zuulzuul00000000000000--- features: - | A new ``attr`` decorator has been added in the ``tempest.lib.decorators`` module. For example, use it to tag specific tests, which could be leveraged by test runners to run only a subset of Tempest tests. ././@LongLink0000000000000000000000000000016300000000000011215 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yamltempest-17.2.0/releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab0000666000175100017510000000027313207044712033421 0ustar zuulzuul00000000000000--- features: - | Add show snapshot metadata item API to v2 snapshots_client library. This feature enables the possibility to show a snapshot's metadata for a specific key. tempest-17.2.0/releasenotes/notes/identity_client-635275d43abbb807.yaml0000666000175100017510000000023113207044712025332 0ustar zuulzuul00000000000000--- features: - | Enhances the v3 identity client with the ``check_token_existence`` endpoint, allowing users to check the existence of tokens tempest-17.2.0/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml0000666000175100017510000000041713207044712030652 0ustar zuulzuul00000000000000--- features: - | A new boolean config option ``serial_console`` is added to the section ``compute-feature-enabled``. If enabled, tests, which validate the behavior of Nova's *serial console* feature (an alternative to VNC, RDP, SPICE) can be executed. tempest-17.2.0/releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml0000666000175100017510000000030513207044712030617 0ustar zuulzuul00000000000000--- fixes: - | Add a missing return statement to the retype_volume API in the v2 volumes_client library: Bug#1703997 This changes the response body from None to an empty dictionary. tempest-17.2.0/releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml0000666000175100017510000000117113207044712031320 0ustar zuulzuul00000000000000features: - | Move base_client from tempest.lib.services.volume.v3 to tempest.lib.services.volume, so if we want to add new interfaces based on a v2 client, we can make that v2 client inherit from volume.base_client.BaseClient to get microversion support, and then to make the new v3 client inherit from the v2 client, thus to avoid the multiple inheritance. deprecations: - | Deprecate class BaseClient from volume.v3.base_client and move it to volume.base_client. ``tempest.lib.services.volume.v3.base_client.BaseClient`` (new ``tempest.lib.services.volume.base_client.BaseClient``) tempest-17.2.0/releasenotes/notes/credentials-factory-stable-c8037bd9ae642482.yaml0000666000175100017510000000072313207044712027372 0ustar zuulzuul00000000000000--- features: - | The credentials_factory.py module is now marked as stable for Tempest plugins. It provides helpers that can be used by Tempest plugins to obtain test credentials for their test cases in a format that honors the Tempest configuration in use. Credentials may be provisioned on the fly during the test run, or they can be setup in advance and fed to test via a YAML file; they can be setup for identity v2 or identity v3. ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yamltempest-17.2.0/releasenotes/notes/add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yam0000666000175100017510000000016213207044712033304 0ustar zuulzuul00000000000000--- features: - | Add a new client to handle the domain configuration feature from the identity v3 API. tempest-17.2.0/releasenotes/notes/add-ip-version-check-in-addresses-x491ac6d9abaxa12.yaml0000666000175100017510000000016713207044712030766 0ustar zuulzuul00000000000000--- fixes: Add more accurate ip version check in addresses schema which will limit the ip version value in [4, 6]. tempest-17.2.0/releasenotes/notes/deprecate-config-forbid_global_implied_dsr-e64cfa66e6e3ded5.yaml0000666000175100017510000000036313207044712033023 0ustar zuulzuul00000000000000--- deprecations: - The config option ``forbid_global_implied_dsr`` from the ``IdentityFeature`` group is now deprecated. This feature flag was introduced to support testing of old OpenStack versions which are not supported anymore. tempest-17.2.0/releasenotes/notes/drop-DEFAULT_PARAMS-bfcc2e7b74ef880b.yaml0000666000175100017510000000103413207044712025547 0ustar zuulzuul00000000000000--- upgrade: - | Replace any call in your code to credentials_factory.DEFAULT_PARAMS with a call to config.service_client_config(). fixes: - | The credentials_factory module used to load configuration at import time which caused configuration being loaded at test discovery time. This was fixed by removing the DEFAULT_PARAMS variable. This variable was redundant (and outdated), the same dictionary (but up to date) can be obtained via invoking config.service_client_config() with no service parameter. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.yamltempest-17.2.0/releasenotes/notes/add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.ya0000666000175100017510000000027213207044712032402 0ustar zuulzuul00000000000000--- features: - | Add reset group status API to v3 groups_client library, min_microversion of this API is 3.20. This feature enables the possibility to reset group status. tempest-17.2.0/releasenotes/notes/add_proxy_url_get_credentials-aef66b085450513f.yaml0000666000175100017510000000027513207044712030244 0ustar zuulzuul00000000000000--- features: - | Add the proxy_url optional parameter to the get_credentials method in tempest/lib/auth.py so that that helper can be used when going through and HTTP proxy. tempest-17.2.0/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml0000666000175100017510000000020613207044712030646 0ustar zuulzuul00000000000000--- upgrade: - | Remove two deprecated skip decorators in ``config`` module: ``skip_unless_config`` and ``skip_if_config``. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.yamltempest-17.2.0/releasenotes/notes/deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.y0000666000175100017510000000055613207044712033076 0ustar zuulzuul00000000000000--- deprecations: - | Image APIs in compute are deprecated, Image native APIs are recommended. And Glance v1 APIs are deprecated and v2 APIs are current. Image client compute_images_client and Glance v1 APIs are removed in volume tests. upgrade: - | Switch to use Glance v2 APIs in volume tests, by adding the Glance v2 client images_client. tempest-17.2.0/releasenotes/notes/add-create-group-from-src-tempest-tests-9eb8b0b4b5c52055.yaml0000666000175100017510000000014513207044712031715 0ustar zuulzuul00000000000000--- features: - | Add create_group_from_source to groups_client in the volume service library. ././@LongLink0000000000000000000000000000016500000000000011217 Lustar 00000000000000tempest-17.2.0/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a90bbb731d0f.yamltempest-17.2.0/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a0000666000175100017510000000026613207044712033155 0ustar zuulzuul00000000000000--- features: - | Defines the identity v3 OS-EP-FILTER EndPoint Groups API client. This client manages Create, Get, Update, Check, List, and Delete of EndPoint Group. tempest-17.2.0/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml0000666000175100017510000000035113207044712032115 0ustar zuulzuul00000000000000--- features: - | Define v2 quota_classes_client for the volume service as library interfaces, allowing other projects to use this module as stable libraries without maintenance changes. * quota_classes_client(v2) tempest-17.2.0/releasenotes/notes/add-floating-ip-config-option-e5774bf77702ce9f.yaml0000666000175100017510000000031313207044712027761 0ustar zuulzuul00000000000000--- features: - A new config option in the network-feature-enabled section, floating_ips, to specify whether floating ips are available in the cloud under test. By default this is set to True. tempest-17.2.0/releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml0000666000175100017510000000023513207044712027167 0ustar zuulzuul00000000000000--- prelude: > This is an intermediate release during the Pike development cycle to make new functionality available to plugins and other consumers. tempest-17.2.0/releasenotes/source/0000775000175100017510000000000013207045130017272 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/source/v17.0.0.rst0000666000175100017510000000017713207044712020751 0ustar zuulzuul00000000000000===================== v17.0.0 Release Notes ===================== .. release-notes:: 17.0.0 Release Notes :version: 17.0.0 tempest-17.2.0/releasenotes/source/conf.py0000666000175100017510000002161213207044712020602 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/tempest' bug_project = 'tempest' bug_tag = '' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # 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' # Release do not need a version number in the title, they # cover multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # 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 = 'openstackdocs' # 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. # The name for this set of Sphinx documents. If None, it defaults to # " v 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' # 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 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 # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] tempest-17.2.0/releasenotes/source/v16.1.0.rst0000666000175100017510000000017713207044712020751 0ustar zuulzuul00000000000000===================== v16.1.0 Release Notes ===================== .. release-notes:: 16.1.0 Release Notes :version: 16.1.0 tempest-17.2.0/releasenotes/source/_static/0000775000175100017510000000000013207045130020720 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013207044712023200 0ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/source/unreleased.rst0000666000175100017510000000015313207044712022161 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: tempest-17.2.0/releasenotes/source/index.rst0000666000175100017510000000046513207044712021147 0ustar zuulzuul00000000000000=========================== Tempest Release Notes =========================== .. toctree:: :maxdepth: 1 unreleased v17.0.0 v16.1.0 v16.0.0 v15.0.0 v14.0.0 v13.0.0 v12.0.0 v11.0.0 v10.0.0 Indices and tables ================== * :ref:`genindex` * :ref:`search` tempest-17.2.0/releasenotes/source/v10.0.0.rst0000666000175100017510000000017713207044712020742 0ustar zuulzuul00000000000000===================== v10.0.0 Release Notes ===================== .. release-notes:: 10.0.0 Release Notes :version: 10.0.0 tempest-17.2.0/releasenotes/source/_templates/0000775000175100017510000000000013207045130021427 5ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013207044712023707 0ustar zuulzuul00000000000000tempest-17.2.0/releasenotes/source/v16.0.0.rst0000666000175100017510000000017613207044712020747 0ustar zuulzuul00000000000000===================== v16.0.0 Release Notes ===================== .. release-notes:: 16.0.0 Release Notes :version: 16.0.0tempest-17.2.0/releasenotes/source/v15.0.0.rst0000666000175100017510000000017713207044712020747 0ustar zuulzuul00000000000000===================== v15.0.0 Release Notes ===================== .. release-notes:: 15.0.0 Release Notes :version: 15.0.0 tempest-17.2.0/releasenotes/source/v14.0.0.rst0000666000175100017510000000017713207044712020746 0ustar zuulzuul00000000000000===================== v14.0.0 Release Notes ===================== .. release-notes:: 14.0.0 Release Notes :version: 14.0.0 tempest-17.2.0/releasenotes/source/v12.0.0.rst0000666000175100017510000000017713207044712020744 0ustar zuulzuul00000000000000===================== v12.0.0 Release Notes ===================== .. release-notes:: 12.0.0 Release Notes :version: 12.0.0 tempest-17.2.0/releasenotes/source/v11.0.0.rst0000666000175100017510000000017713207044712020743 0ustar zuulzuul00000000000000===================== v11.0.0 Release Notes ===================== .. release-notes:: 11.0.0 Release Notes :version: 11.0.0 tempest-17.2.0/releasenotes/source/v13.0.0.rst0000666000175100017510000000017713207044712020745 0ustar zuulzuul00000000000000===================== v13.0.0 Release Notes ===================== .. release-notes:: 13.0.0 Release Notes :version: 13.0.0 tempest-17.2.0/AUTHORS0000664000175100017510000007034213207045127014365 0ustar zuulzuul00000000000000Aaron Lee Aaron Rosen Aaron Thomas Aarti Kriplani Abhijeet Malawade Abhijeet.Jain Abhishek Abhishek Chanda Adalberto Medeiros Adam Gandelman Adam Young Aditi Raveesh Adolfo Duarte Adrien Vergé Ajay Yadav Ala Rezmerita Alan Alessandro Pilotti Alex Gaynor Alex Stafeyev Alexander Gubanov Alexander Gubanov Alexander Ignatov Allen Gao Alok Maurya Alvaro Lopez Garcia Andrea Frittoli (andreaf) Andrea Frittoli Andrea Rosa Andrea Rosa Andreas Jaeger Andreas Scheuring Andrew Boik Andrew Kerr Andrew Laski Andrey Kurilin Andrey Pavlov Andy Botting Angus Salkeld Anh Tran Anita Kuno Anju Tiwari Anju Tiwari Anju5 Ann Kamyshnikova Anna Babich Anna Pankiewicz Anthony Washington Antoine Eiche Anusha Ramineni Arata Notsu Arie Armando Migliaccio Armando Migliaccio Artom Lifshitz Artur Svechnikov Arx Cruz Arx Cruz Ashish Chandra Ashish Gupta Assaf Muller Atsushi SAKAI Attila Fazekas Augustina Ragwitz Avi Avraham Babu Shanmugam Bartosz Górski Ben Nemec Bence Romsics Benny Kopilov Benny Kopilov Bhuvan Arumugam Bin Zhou BinBin Cong Bob Ball Boris Pavlovic Brad Behle Brad Pokorny Brandon Palm Brant Knudson Brent Eagles Brian Haley Brian Haley Brian Lamar Brian Ober Brian Rosmaita Brian Waldon Bruce R. Montague Bruce Tan Burt Holzman Béla Vancsics Cady_Chen Cao Xuan Hoang Carl Baldwin Carlos Goncalves Castulo J. Martinez Chandan Kumar Chang Bo Guo Chang Ye Wang ChangBo Guo(gcb) ChenZheng ChenZheng Chi Lo Chmouel Boudjnah Chris Buccella Chris Buccella Chris Dent Chris Hoge Chris Hoge Chris Yeoh Christian Berendt Christian Schwede Christian Schwede Christophe Sauthier Christopher Yeoh Cindy Lu Cindy Pallares Clark Boylan Claudiu Belu Clint Adams Clint Byrum Cory Stone Cyril Roelandt Daisuke Morita Dan Prince Dan Smith Dane LeBlanc Daniel Korn Daniel Mellado Daniel P. Berrange Danny Al-Gaaf Dao Cong Tien Dariusz Smigiel Darragh O'Reilly Daryl Walleck Davanum Srinivas (dims) Davanum Srinivas Davanum Srinivas David Kranz David Lyle David Lyle David Moreau-Simard David Paterson David Ripton David Shrewsbury Dean Troyer DennyZhang Derek Higgins Devananda van der Veen Diana Clarke Dina Belova Dirk Mueller Divyansh Acharya Dmitry Tantsur Dolph Mathews Dong Ma Doug Hellmann Doug Wiegley Dougal Matthews Duc Truong Dustin Schoenbrun Earle F. Philhower, III Edgar Magana Eiichi Aikawa Elena Ezhova Eli Qiao Eli Qiao Emilien Macchi Emilien Macchi Emily Hugenbruch Emily Hugenbruch Eoghan Glynn Eran Kuris Eric Berglund Eric Brown Eric Fried Eric Harney Eric Wehrmeister Eric Windisch Eric Windisch Erickson Santos Erlon R. Cruz Eugene Bagdasaryan Eugene Nikanorov Evgeny Antyshev Evgeny Antyshev Evgeny Fedoruk Eyal Posener Fabian Zimmermann Fabien Boucher Fei Long Wang FeihuJiang Felipe Monteiro Felix Li Feodor Tersin Ferenc Horváth Filip Hubík Flavio Percoco Florent Flament Franklin Naval Frederic Lepied Frédéric Guillot Gage Hugo Gal Amado Gaozexu Gary Kotton Gavin Brebner Genadi Chereshnya Georgy Dyuldin Ghanshyam Ghanshyam Mann Giampaolo Lauria Giulio Fidente Gleb Stepanov Gong Zhang Gordon Chung Gorka Eguileor Graham Hayes Gregory Haynes Grishkin Guillaume Chenuet Gyorgy Szombathelyi Hai Shi Haiwei Xu Haiyang Ding Hanchen Lin Hanxi Hanxi Liu Hardik Italia Harshada Mangesh Kakad Harshada Mangesh Kakad He Jie Xu Hemanth Nakkina Hengqing Hu Henry Gessau Hirofumi Ichihara Hironori Shiina Hoisaleshwara Madan V S Hoisaleshwara Madan V S Hong Hui Xiao Hugh Saunders Hui HX Xiang Hynek Mlnarik Ian Wienand Ihar Hrachyshka Ilya Shakhat IonuÈ› ArțăriÈ™i Isaku Yamahata Itzik Brown Ivan Kolodyazhny Jaesang Lee Jake Yip Jakub Libosvar James E. Blair James E. Blair James Page Jamie Lennox Jamie Lennox Jane Zadorozhna Janonymous Jaroslav Henner Javier Pena Jay Pipes Jeff Peeler Jeffrey Zhang Jeremy Freudberg Jeremy Liu Jeremy Stanley Jerry Cai Jesse Keating Ji-Wei Jianghua Wang Jim Rollenhagen Jinhe Fang Jiri Tomasek Joe Gordon Joe H. Rahme Joe H. Rahme Johan Pas John Fischer John Garbutt John Griffith John Griffith John L. Villalovos John Warren Jon Bernard Jon Grimm Jonte Watford Jordan Pittier Jordan Pittier JordanP JordanP JordanP JordanP Jorge Chai Joris Roovers Joseph Lanoux Joshua Harlow Joshua White Juerg Haefliger Julie Pichon Julien Danjou Julien Leloup Justin Shepherd Jérôme Gallard K Jonathan Harker Kaitlin Farr Kan Katherine Elliott Ken'ichi Ohmichi Kenji Yasui Kevin Benton Kevin Benton Kevin_Zheng Kiall Mac Innes Kieran Spear Kirill Shileev Kobi Samoray Kris Stercxk Kristi Nikolla Kui Shi Kurt Taylor Kyle Mestery Kyrylo Romanenko Lajos Katona Lance Bragstad Larisa Ustalov Leandro I. Costantino Lee Yarwood Lenny Verkhovsky Leo Toyoda Leonardo Maycotte Leticia Wanderley Li Ma Li Wei Lingxian Kong LingxianKong Lucas Alvares Gomes Lucian Petrut Lucian Petrut Ludovic Beliveau Luigi Toscano Luong Anh Tuan Luz Cazares Lv Fumei Mahesh Panchaksharaiah Maho Koshiya Malini Kamalambal Marc Koderer Marc Solanas Marc Solanas Tarre Marian Horban Mark Maglana Mark McClain Mark McClain Mark McLoughlin Mark T. Voelker Mark Vanderwiel Mark Washenberger Markus Zoeller Martin Kopec Martin Pavlasek Martina Kollarova Maru Newby Maru Newby Masayuki Igawa Masayuki Igawa Mate Lakat Mathew Odden Mathieu GagneÌ Matt Riedemann Matt Riedemann Matthew Edmonds Matthew Treinish Mauro S. M. Rodrigues Max Lobur Megan Guiney Mehdi Abaakouk Mh Raies Michael Chapman Michael Hudson-Doyle Michael Hudson-Doyle Michael J Fork Michael Smith Michael Still MichaÅ‚ Dulko Michelle Mandel Miguel Lavalle Mike Fedosin Mikhail Feoktistov Mikhail S Medvedev Mithil Arun Mitsuhiko Yamazaki Mitsuhiro Tanino Momoka Toyota Monty Taylor Morgan Fainberg Morgan Fainberg Nachi Ueno Naomichi Wakui Nayna Patel Neeraj Swarnkar Neeti Dahiya Ngo Quoc Cuong Nguyen Hung Phuong Nicolas Bock Nicolas Helgeson Nikhil Manchanda Nikita Gerasimov Nikola Dipanov Nikolay Pliashechnikov Nolwenn Cauchois Nuno Santos OTSUKA, Yuanying OctopusZhang Oleg Bondarev Oleksii Butenko Pablo Andres Fuente Pablo Sanchez Paul Glass Pavel Sedlák Peter Sabaini Peter Stachowski Phil Day Pradeep Kumar Pradeep Kumar KS Pramod Kumar Singh Pranali Deore PranaliD PranaliDeore Pranav Salunke Prateek Arora Prem Karat PrinikaSN Puneet Arora Puneet arora Pádraig Brady Qin Zhao QingXin Meng Qiu Hua Qiao Rabi Mishra Radoslaw Zarzynski Rafael Folco Rafael Rivero Rajkumar Thiyagarajan Ralf Haferkamp Ramakrishnan G Rami Vaknin Reedip Banerjee Richard Winters Rick Harris Rob Crittenden Robert Collins Robert Mizielski Rodrigo Duarte Rodrigo Duarte Sousa Rohan Kanade Rohan Kanade Rohan Kanade Rohan Rhishikesh Kanade Rohit Karajgi Roman Podoliaka Roman Prykhodchenko Rossella Sblendido Rushi Agrawal Russell Bryant Russell Sim Ryan Bak Ryan Hsu Ryan McNair Ryan Rossiter Ryan Tidwell Ryota Hashimoto Ryota MIBU SHIGEMATSU Mitsuhiro Sahid Orentino Ferdjaoui Salvatore Salvatore Orlando Samantha Blanco Samuel Merritt Santiago Baldassin Santosh Kumar Santosh Kumar Sarafraj Singh Saranya Pandian Sascha Peilicke Saurabh Chordiya Sean Dague Sean M. Collins Sean M. Collins Sean McGinnis Sergey Kraynev Sergey Lukjanov Sergey Murashov Sergey Nikitin Sergey Reshetnyak Sergey Sh Sergey Shnaidman Sergey Vilgelm Shane Wang Shang Yong Shu Yingya Shuquan Huang Simeon Monov Sirushti Murugesan Slade Baumann Soren Hansen Soren Hansen Sreeram Yerrapragada Stephen Finucane Stephen Gran Stephen Lowrie Steve Baker Steve Heyman Steve Lewis Steve Martinelli Steve Noyes Steven Dake Steven Hardy Sumanth Nagadavalli Sunil G Sunil Thaha Surya Prakash Singh Swami Reddy Swaminathan Vasudevan Swapnil Kulkarni Swapnil Kulkarni Syed Armani Sylvain Afchain Sylvain Baubeau Takashi NATSUME Takeaki Matsumoto Tal Kammer Tatyana Leontovich Telles Nobrega Thalabathy Venkatesan Thiago Paiva Thiago da Silva Thomas Bechtold Thomas Spatzier Tiago Mello Tianbiao Qi Timofey Durakov TimurNurlygayanov Tin Lam Tingting Bao Tom Cocozzello Tom Patzig Tong Liu Tony Yang Toru Tanami Trevor McCasland Trevor McCasland Truong Le Tushar Kalra Unmesh Gurjar Vadim Rovachev Valeriy Ponomaryov Vasyl Khomenko Vasyl Saienko Victoria Martínez de la Cruz Viktor Tikkanen Vincent Hou Vincent Untz Vitaly Gridnev Vladislav Kuzmin Vladyslav Drok Walter A. Boring IV Wangpan Wayne Vestal Weeks Wei Liu Will Xav Paice Xavier León Xavier Queralt Xiangfei Zhu Xiao Chen Xiao Hanyu Xicheng Chang Xing Yang Xinli Guan YAMAMOTO Takashi Yaguang Tang Yair Fried Yaroslav Lobankov Yassine Lamgarchal Yatin Kumbhare Yong Sheng Gong Yoshihiro Kaneko Yuiko Takada Yuriy Nesenenko Yushiro FURUKAWA ZHU ZHU Zack Feldstein ZhangHongtao Zhao Lei Zhenguo Niu Zhenzan Zhou Zhi Kun Liu ZhiQiang Fan Zhiteng Huang Zhongyue Luo Zhu Zhu Zuul abhishek60014726 afazekas ahmad ajayaa andreaf anju Tiwari anju tiwari apetrov april armando-migliaccio bhavani.cr bkopilov caojinlan caoyue chenxing chris fattarsi cmyster daniel-a-nguyen davyyy dharmendra dineshbhor donald-ngo edannon ekhugen fujioka yuuichi fumihiko kakuma gavin-brebner-orange gengchc2 gengjh ghanshyam git-harry gong yong sheng gongxiao gord chung gordon chung groghkov guo xian guo yunxian harika-vakadi hayderimran7 hgangwx hi2suresh howardlee huangtianhua huangtianhua imran malik ivan-zhu izikpenso janonymous jeremy.zhang jianghua jianghua wang jinxingfang john-griffith jufeng jun xie junboli kavan-patil lanoux lanoux lei zhang lianghao lijunjbj lisali liu-sheng liuchenhong liudong lkuchlan llg8212 lvdongbing m.benchchaoui@cloudbau.de md nadeem meera-belur melanie witt melissaml mmkmmk57 mouad benchchaoui naichuans nfedotov nithya-ganesan obutenko piyush110786 piyush110786 prabhu murthy prdsilva qianlin raiesmh08 raiesmh08 raiesmh08 raiesmh08 raigax9 rajalakshmi-ganesan rajat29 ravikumar-venkatesan reedip ricolin ricolin ronak root root rossella rsritesh sapan-kona saradpatel sarvanimounika saurabh scottda shangxiaobj shihanzhang shipeiqi sonu.kumar sridhargaddam sslypushenko step6829 sukhdev sunny-verma tanlin tmatsu ubuntu umamohan user varun yadav venakata anil venkata anil vikas vishal mahajan vponomaryov vrovachev vsaienko wanghao wanglianmin wangxiyuan wantwatering wingwj xianming mao xiejunan xing-yang xxj yan.haifeng yfzhao yuhui_inspur yuyafei zhang.lei zhangfeng zhangguoqing zhanghongtao zhangxuanyuan zhangyanxian zhangyanzi zheng yin zhhuabj zhipengh zhongjun zhoubin50 <414330705@qq.com> zhufl zhuzeyu zoukeke zwhe tempest-17.2.0/playbooks/0000775000175100017510000000000013207045130015304 5ustar zuulzuul00000000000000tempest-17.2.0/playbooks/devstack-tempest.yaml0000666000175100017510000000066713207044712021473 0ustar zuulzuul00000000000000# Changes that run through devstack-tempest are likely to have an impact on # the devstack part of the job, so we keep devstack in the main play to # avoid zuul retrying on legitimate failures. - hosts: all roles: - run-devstack # We run tests only on one node, regardless how many nodes are in the system - hosts: tempest roles: - setup-tempest-run-dir - setup-tempest-data-dir - acl-devstack-files - run-tempest tempest-17.2.0/playbooks/post-tempest.yaml0000666000175100017510000000153513207044712020647 0ustar zuulzuul00000000000000- hosts: all become: true vars: logs_root: "{{ devstack_base_dir|default('/opt/stack') }}" stage_dir: "{{ devstack_base_dir|default('/opt/stack') }}" test_results_stage_name: 'test_results' roles: - role: process-test-results test_results_dir: '{{ logs_root }}/tempest' tox_envdir: tempest - role: process-stackviz - role: stage-output zuul_copy_output: { '{{ logs_root }}/tempest/etc/tempest.conf': 'logs', '{{ logs_root }}/tempest/etc/accounts.yaml': 'logs', '{{ logs_root }}/tempest/tempest.log': 'logs', '{{ stage_dir }}/{{ test_results_stage_name }}.subunit': 'logs', '{{ stage_dir }}/{{ test_results_stage_name }}.html': 'logs', '{{ stage_dir }}/stackviz': 'logs' } extensions_to_txt: - conf - log - yaml - yml tempest-17.2.0/ChangeLog0000664000175100017510000105257613207045127015101 0ustar zuulzuul00000000000000CHANGES ======= 17.2.0 ------ * Use zuul v3 for running tempest plugin sanity check * Fix indentation in docs * Add role to build the stackviz report * test\_network\_v6: log console when test fails * Remove setting of version/release from releasenotes * run-tempest role: new tempest\_test\_regex variable * Update URL from "http" to "https" * Updated from global requirements * Edit documentation for account-generator * Fix volume v1 api ref links * Add support of args and kwargs in call\_until\_true * Move and update test\_resize\_volume\_backed\_server\_confirm * Updated from global requirements * Remove unnecessary argument in "skip\_because" decorator * Stop using resource\_cleanup in identity\_v2 tests * Process test results from a tempest run * Add post step to Tempest base job * Initial skeleton for devstack-tempest base job * Don't use server\_id class variable in \_test\_resize\_server\_confirm * Add a .htaccess for redirecting old paths * Modify the help message for getting identity extensions * Use addClassResourceCleanup to clear domains * Use addClassResourceCleanup in BaseImageTest * Fix doc issue in plugin.py * Fix AssertionError in test\_rand\_password\_with\_len\_2 * Change description of annotation * Ensure project get and list have match * Remove deprecated volume apis from v2 volumes client * Fix fixed\_ips tests to skip if ip not found * Fix 'tempest cleanup' for volume service client * Use addClassResourceCleanup in MeteringTestJSON * Add unit tests to check for CONF getattr during import * Remove v2 identity from tempest cleanup command * Add support for "per\_volume\_gigabytes" and "backup\_gigabytes" quota * Use addClassResourceCleanup in TagsExtTest * List auth domains v3 identity endpoint 17.1.0 ------ * Convert cleanup with addClassResourceCleanup in endpoint test * Add release notes for an queens intermediate release * Remove routers\_client from manager.\_create\_subnet * Fix test case for updating volume type extra specs * Don't read config in cred\_factory module * Make test.py and clients.py as stable for plugins * Add proxy\_url to get\_credentials in auth * Cap compute floating IPs tests * Don't read config in Manager class definition * Disable testing of the v2.0 identity API * Remove \_create\_router in scenario.manager * Rename scenario.manager.\_create\_port to create\_port * Fix "import xx as xx" grammer * Move the object client to tempest.lib * Add config options to set proxy\_url * Remove scenario.manager.rebuild\_server * Updated from global requirements * Do not set alias user\_client for specific client * Remove \_check\_remote\_connectivity in scenario.manager * Remove specific project checking in test * Allow to specify user and project domains in CLIclient * Only attempt to detach an in-use volume during cleanup * Rename base.rebuild\_server to base.recreate\_server * Refactor of \_check\_tenant\_network\_connectivity * Setup networking for live migration tests * Remove method get\_ipv6\_addr\_by\_EUI64 * Update README to use stestr instead of testr for unit tests * Fix object\_client methods to accept headers and query param * Remove wrapper methods from object\_client * Fix senario test: test\_swift\_acl\_anonymous\_download * Move object storage container\_client to lib interface * Add release note for --load-list * Fix create container method * Add compare header version to test images oneserver * Remove \_project\_network\_cidr in security group tests * Remove unnecessary global variables * Fill microversion doc for implemented volume tests * Use get\_tenant\_network in get\_server\_ip * Remove unnecessary client alias in AvailabilityZoneTestJson * Add test case for reset group snapshot status * Fix volume group test * Remove unnecessary assertIn * Remove redundant volume check in nova\_volume\_detach * Add compare header version to create\_image\_from\_server helper * Remove internal helper \_default\_security\_group * Add missing addCleanup of \_delete\_group\_snapshot * Delete a volume by Non-admin privileges * Fix create, update or delete container metadata method * Make list methods consistent for container client * Move internal helpers to the class that uses them * Remove deprecated APIs from volume v2 volumes\_client * Remove deprecated skip decorators * Fix the 'service' decorator path in doc * Remove unneeded resource\_cleanups in compute tests * Compute volumes via addClassResourceCleanup * Compute SG via addClassResourceCleanup * Compute servers via addClassResourceCleanup * Remove one-line helper \_delete\_volume * Add test case for reset group status * Fix list\_group\_snapshots API in v3 group\_snapshots\_client * Updated from global requirements * Fix a typo of a missing letter * Switch to use stestr for unit tests directly * Move identity\_utils to common.identity * Add unit tests for not overriding setUpClass * Add unit tests for test class fixtures * Prepare setup\_clients and resource\_setup * Prepare setup\_credentials as stable * Move object storage account\_client to lib interface * Use glance client to delete an image * Updated from global requirements * Compute images via addClassResourceCleanup * Remove unused \_add\_router\_interface\_with\_subnet\_id helper * Prepare skip\_checks as stable interface * Make skip\_checks and setup\_creds safe * Prepare network\_resources as a stable interface * Make validation\_resources a stable interface * Stop implicit validation\_resources provisioning * Remove unnecessary executable permissions * Make resource\_cleanup stable * Add a validation resources fixture * Capture logs when running unit tests * Fix the redundant use of netaddr.IPNetwork * Remove unnecessary class variable * Remove unused RFCViolation * Remove "test\_create\_with\_nonexistent\_volume\_type" * Remove unused helper in RoutersTest * Remove useless check in AttachInterfacesTestJSON * Remove unnecessary back slash * Make validation resources leak safe * Get cidr/mask\_bits according to ip\_version in resource\_setup * [DOC] Fix "Title level inconsistent" warning * Only choose available compute node as migration dest * Move data directory under doc/source * doc migration: update the doc link address * Fix addCleanup in AutoAllocateNetworkTest * Add --load-list argument to tempest to accept a non-regex whitelist * Fix addCleanup order in test\_volume\_list\_param\_tenant * Use create\_volume wrapper for volume creation * Add cost time printing in wait\_for\_volume\_resource\_status * Add compare header version function to tempest.lib * Fix for resetting snapshot status * Fix the test case for showing host * Avoid using v3 volume clients when v3 is disabled * Add release notes page for v17 17.0.0 ------ * Add release not to mark the start of Pike support * Fix volume microversion link in doc * Use skip\_checks instead of skipUnless * Add support for IPV6 tests in tempest * Add params to upload\_volume command * Updated from global requirements * check is\_admin\_available for force\_tenant\_isolation in test.py * [TrivialFix] Add bug reference to releasenote * Added unit tests for blacklist and whitelist * Use networks floatingips client to associate fips * Use skip\_checks if all testcases have same skip conditions * Do not use self.name for volume name field * Fix for resetting volume status * Test shared and non-shared external networks * Move test decorators to common * Use a non admin privileges for retyping a volume * TrivialFix for flake8 and docs build in tox.ini * Fix RemoteClient having bad default ssh\_shell\_prologue * Remove unnecessary schema check of ip address * Test nonexistent volume type extra\_spec name instead of id * Add detail specific fields check in list\_backups * Fix identity tests when domain specific drivers are enabled * [TrivialFix]Remove unnecessary value taking * [TrivialFix] Remove unused statements in compute tests * Add comments for test module of tempest.test.idempotent\_id * Updated from global requirements * Fix unit test that break isolation * Remove unused variable assignment * Remove unused assignment * Add compute test for ServerGroupAntiAffinityFilter * Resolve TODO in test: 'test\_available\_volume\_retype\_with\_migration' * Stop passing around dicts in validation resources * Add docstring for validation resources * Fix client usage in validation resources * Drop validation resources dependency from CONF * Update and replace http with https for doc links in tempest * [Trivialfix]Fix typos in tempest * Remove unnecessary class Inheritance * Remove unnecessary resource\_setup in DvrRoutersNegativeTest * Do not run test\_create\_server\_with\_scheduler\_hint\_group twice * add detaches to attachment test * Volume and group on same backend in update\_group * Do not create server in test\_list\_servers\_filter\_by\_exist\_host * Remove unused global variable * Do not print empty list in assertNotEmpty * Py3: Finish the python3 port * Refactor test\_user\_update * Mark credentials\_factory stable for plugins * Remove dependency to identity v2 * Make sure test use the latest volume clients * Add Tests for Groups Volume APIs - Part 4 * Move schedule filter check to compute module * Remove usage of credentials\_factory.AdminManager * Increase unit test coverage for v2 images client * Fix microversion doc for implemented microversion tests * Add Tests for Groups Volume APIs - Part 3 * Fix verify config API version checks * Add unit test for volume availability zone client * Remove redundant comments in credentials\_factory.py * Add release notes for client registration changes * TrivialFix: Remove the unused import code * Add test for showing volume image metadata * Moved releasenotes from tempest package to proper location * Do not call another testcase in one testcase * Remove unnecessary assertIsNotNone check * Remove unnecessary checks already coverd in schema * Remove \_list\_assertions from test\_roles.py * Remove unnecessary asserting for 'id' in body * Fix broken api links in lib/services * Judge skip conditions in skip\_checks instead of in resource\_setup * Fix disable\_ssl\_certificate\_validation values if ca\_certificates file is defined * Remove unnecessary usage of instance variable * TrivialFix: Remove the unused import code * Unsupported 'message' Exception attribute in PY3 * Updated firewalls deafult port reference to Newton * Make verify\_tempest\_config workspace aware * Fixed the broken links for api microversion * TrivialFix: Correct reST field lists in docstrings * Updated from global requirements * Try to register all service clients * Remove support for py34 * Fix some nits in object storage clients release notes * Import data\_utils from tempest.lib.common.utils * Add Tests for Groups Volume APIs - Part 2 * Revert "Remove tempest.common data\_utils" * Move object storage capabilities\_client to lib interface * Remove deprecated test.related\_bug decorator * Move object storage bulk\_middleware\_client to lib interface * Complete credentials\_factory docstrings * Add unit test for volume limits client * Add unit test for volume extensions client * Fix object storage capabilities client return value * Updated from global requirements * Add Tests for Create/Delete/Show/List Group Types * Add better docs on credential providers * Migrate the preprov creds module to tempest lib * Migrate the dynamic creds module to tempest lib * Ensure test for Neutron GET / uses right URL * [DOC BLD FIX] Correct reST field lists in docstrings * Fix object storage bulk middleware client return value * Remove creds providers dependency from clients * Added return statement to retype\_volume in v2 volumes\_client * Add volume backed instance coverage to API tests * Fix account generator unit tests * Make credentials\_factory a bit nicer * Update the documention for doc migration * Unix style line ending * test\_boot\_server\_from\_encrypted\_volume\_luks skip condition * Add unit tests for volume and snapshot manage clients * Updated from global requirements * Cleanup the orchestration client init and attr * Remove unused clients from testcases * Move multi-nic tests to dedicated module * create test to verify catalog standardization * Generic method for creating a volume snapshot * Handle volume API version enablement * Test size extend for an attached volume * Fix instance\_action\_events response schema for finish\_time and result * Using fixtures instead of deprecated mockpatch module * Refactor volume clone tests * Remove unused tenant\_cidr from test\_routers.py * Use cls instead of self in classmethod * Remove unused LOG from clients.py * Add flag to generate tempest plugin list * Doc: fix markups, capitalization and add 2 REVIEWING advices * Add a page for release 16.1 to release notes * Added script for doing tempest plugin sanity * Add 'params' argument to v2 list\_backups API 16.1.0 ------ * Add release notes for an intermediate release * Supplement unit tests for volume transfers client * Extra compute services\_client API endpoints * Updated from global requirements * Dynamic creds does not support create subnet with ipv6 * Add test case for update volume backup * Add test for showing snapshot metadata item * Fix check\_service\_client\_function doc typo * Add test for showing volume metadata item * Move base\_client from volume.v3 to volume * Switch from oslosphinx to openstackdocstheme * Boot server from encrypted volume * Fix import error "No module named six.moves" for plugin sanity job * Update volume-status waiter for new cinder attach * Add response assertions and remove unused variables in volume tests * Added --rmdir flag to workspace remove * Pause resource cleanup * Fix image deletion checks after unshelve server * Tidy up releasenotes * [Doc] Remove html\_use\_smartypants config in doc * Add token related API to Keystone v3-ext/OS-OAUTH1 client * Fix no attribute 'urlopen' error in python3 * Add negative test for live migration * Adds \*\*params to v3 list\_endpoints * Enhance v3 list\_services to test filtering by service type * Updated from global requirements * Add Scheduler Stats client unit test * Handle the case that RFP negotiation message arrived early * Add api-ref url for compute list\_baremetal\_nodes * Adding and removing bytes\_body param from unit tests * Add network tags-ext API tests * Tests for Nova instance diagnostics (microversion v2.48) * Fix skip checks in volume and snapshot manage tests * Add api-ref urls for some volume v2 APIs * Xenapi: Fix tempest for xenserver device tagging * Fix check\_service\_client\_function mock\_args bug * Add Quotas client unit tests * Fix auto allocate network cleanup * Wait for server deletion if validation setup fails * Enhance tempest client for keystone v3 token APIs * Added tests for Nova microversion v2.47 * Implement tempest client for keystone v2 token APIs * Save state of cloud before running tempest * create a non-admin token validation test * Add expect\_response\_code to base network client * Add Capabilities Client unit tests * Updated from global requirements * Fix test\_volume\_migrate\_attached to retype as admin * Add Floating IPs client unit tests * Fixed project lists for retrieving tempest plugins * Remove unused variables from api tests * Add Metering Label Rules client unit tests * Add Metering Labels client unit tests * Add Extensions client unit tests * Add Ports client unit tests * Verify config support cinder on subpath * Fix for unmanage and manage snapshot * Add network tags client * Use urllib2 instead of requests in tempest generate plugin list * ported tools/\*.py scripts to python3 * Update "test\_unmanage\_manage\_snapshot" test to support some params * Do not use any() to check whether a list is empty * Replace assertGreaterEqual with assertNotEmpty * Update "create\_server" to wait until status is 'ACTIVE' * Fix class name for test extensions * Update server nic handling for LXD style nic names * Fix html\_last\_updated\_fmt for Python3 * Replace assertions with more specific ones * Add notes for network and identity in get\_service\_list * Remove unnecessary wait\_for\_volume\_resource\_status * Fix 4 bytes utf8 char test comment for create images * Use @test.services instead of skipUnless * Test coverage for network v2 security group rules client * Test coverage for network v2 security groups client * Use Neutron (if available) to create floating IPs for validation * Remove unused variables from scenario tests * Remove TODO comment * Fix minor header wordings * Use assert(Not)Empty,IsNotNone instead of assert(Not)Equal,GreaterEqual(0, len(. * Unit test for asserting correct url in list\_services * Fix DeprecationWarning in test\_volume\_pools.py * Add server group API schema for microversion 2.13 * Updated from global requirements * Use create\_volume classmethod * Fix broken api urls in TypesClient * Add server param in manager.get\_remote\_client * Fix url in list\_services * Reuse v2 extension client for cinder v3 * Prevent error in \_parse\_resp when nullable list * Fix ssh proxy regression * Replace the usage of 'manager' with 'os\_primary' * Remove the exception description of auth\_version in doc * Fix for volume quota class test * Use data\_utils in tempest.lib.common.utils * Replace the usage of 'admin\_manager' with 'os\_admin' * Stop warning on client\_parameters * Replace the use of 'os\_adm' with 'os\_admin' * cls.os is deprecated use cls.os\_primary * Check image after unshelve * Remove undefined variable in exception message * Test coverage for network v2 subnetpools\_client * Add T115 for admin test path * Add option for whether the cloud supports floating ips * Replace the usage of 'os' with 'os\_primary' * Separate admin tests from test\_routers * Add max\_microversion for compute volumes\_extensions\_client tests * Test coverage for network v2 subnets\_client * Create deleted server in resource\_setup * Updated from global requirements * Fix volume attach tests failing when using FIP as ssh method * Replace assertEqual([], items) with assertEmpty(items) * Deprecate default value for v3\_endpoint\_type * Add check after unset\_flavor\_extra\_spec * Add additional assertions for volume transfer test * Using fixtures instead of deprecated mockpatch module * Reduce server rescue/unrescue times * Update document theme and section headers * Correct invalid client in ServersNegativeTestMultiTenantJSON * Raise exception when error\_deleting * Remove duplicated test\_list\_servers\_by\_limits * Add test case for show volume summary * Using fixtures instead of deprecated mockpatch module * Skip test\_volume\_extend\_when\_volume\_has\_snapshot until bug 1687044 is fixed * Share a server in ServersOnMultiNodesTest * Remove base\_routers module * Switch BaseAdminNetworkTest on test\_routers\_dvr * Move methods related to metering\_label * Use a helper to create aggregate * Separate admin test from RoutersNegativeTest * Remove \_remove\_router\_interface\_with\_port\_id() * Fix deprecation warnings * Fix for implied roles test * Move logging extensions list before assert checks * Remove unused \_delete\_router() * Correct skip condition for migrate\_server * Remove the heat tests * Change Cinder api\_v3 config True on default * Add Tests for Groups Volume APIs - Part 1 * Revert "cinder backup force-delete when backup is error" * Move network admin test classes under admin path * Fix recent releasenotes typo * Use min\_count to create servers in ListServersNegativeTestJSON * Create a server in resource\_setup in ImagesOneServerTestJSON * Updated from global requirements * Remove redundant api client in volume tests * Add test case for showing host details * Volume snapshot backup * Create a snapshot from a in-use volume with force=False * test.py: stop using aliases for creds manager * Move compute admin test classes under admin path * Log output of lsblk cmd if test\_device\_tagging failed * Add test for cinder volume extend when volume has snapshot * Update 'test\_volume\_upload' test docstring * Fix bugs about 'default' domain * Nova: test live migration with serial console * Keystone v3 extension os-ep-filter api testcases * Add python3-dev(el) to bindep.txt * Skip when force\_tenant\_isolation=true and no admin credentials * [Negative] Create a volume from deactivated image * Add test case for force detach volume * Update help text for block\_migrate\_cinder\_iscsi option * Use image native api in volume tests * Replace oslo\_utils.timeutils.isotime * Replace deprecated function * Add api\_v2\_admin flag * Add docstring example for get\_opt\_lists * Remove deprecated TYPE in rest\_client * Use base.create\_domain to create domain in testcases * Update .mailmap * Add test about associate floating\_ip to VM * Identity V3 - Endpoint Groups Client * test\_l3\_agent\_scheduler: remove workaround code for Liberty * Identity V3: create\_domain() must return a description field * Deprecate the forbid\_global\_implied\_dsr cfg option * [Negative] Create volume from image with decreasing size * Always provision accounts with auth\_version * Fix the position of the dots in write\_tests.rst * Move 'test\_admin\_deactivate\_reactivate\_image' test under non-admin directory * Remove unused compute networks client in volume tests * Remove duplication of skip\_tracker * Fix create, update or delete account metadata method * Make delete\_volume in volumes\_client.py use \*\*params * Use base.setup\_test\_project to create project * Use base.setup\_test\_tenant to create test tenant * Rename 16.0.0 release notes * Add support to list volume transfers with detail * Identity v3 Domain Configuration Client * Remove some debug print statements * Remove usage of deprecated get\_ipv6\_addr\_by\_EUI64 function * Move InvalidServiceTag * Add a page for release 16 to release notes * Improvement in API Microversion testing doc * Tag test\_create\_server\_invalid\_bdm\_in\_2nd\_dict as needing cinder * Enhancement of tempest cleanup documentation * Add write tests docs for using client managers and credentials * Use sequence directly instead of using len() * Move the \`attr\` decorator from test.py to tempest/lib * Deprecate Heat/Orchestration configuration options * Improve docstring examples 16.0.0 ------ * Move the \`related\_bug\` decorator from test.py to tempest/lib * Remove leading underscore in \_create\_test\_user * Cleanup services decorator * Stop using self.parameters for compute clients * Fix and improve ServiceClients docstring examples * Prepare release notes for release 16.0.0 * Add test cases for volume quota class * Fix invalid values when setting config options' default value * Fix test\_volume\_list\_with\_detail\_param\_marker * Deprecate client\_parameters from ServiceClients * Remove skip condition when resize is enable * Updated from global requirements * Use cliff.lister for tempest workspace command * cinder backup force-delete when backup is error * Make bash shebangs through /usr/bin/env * Refactor resource cleanup methods in compute.base * Remove test\_baremetal\_nodes from tempest * Add Apache License content in .py files * Add judgie condition after live-migration with volume * Add name param in setup\_test\_role * Support testing nova-novncproxy on SSL * Fix api ref link in TransfersClient * Format multi-line release notes properly * Beautify assertEmpty and assertNotEmpty * Fix broken api link and put api link in one line * Fix heading levels in write\_tests doc * Remove non-existent config options of oslo modules * Remove oslo.i18n entry from config-generator * Add test of remove all security groups in test\_server\_actions.py * Skip arbitrary container tests for Ceph * Remove duplicated testcase test\_get\_private\_image * [DOC] Fix details of tox * Remove tempest.common data\_utils * Remove 'allow\_port\_security\_disabled' option * Add size assert for creating volume snapshot * Delete volume with associated snapshots * Separate volume v2 transfers service clients * Add Nova version log * Remove 'dvr\_extra\_resources' option * Remove 'reseller' config option * TestSecurityGroupsBasicOps: log console from access point instance * Set default value of 'dvr\_extra\_resources' option to False * Use min\_count to create multi servers * Fix no module unit test for Python3.6 * Fix flavor info in server response schema * Add additional roles method to v3 roles client * Move cinder tests into unversioned path - part2 * Rename test-removal.rst * Move Cinder non-admin tests into unversioned path * Add volume backup container parameter * Fix tox coverage section * Move test\_volume\_manage to unversioned path * Add tempest test writing guide * Rename server arg in wait\_for\_interface\_status method * Remove a redundant client variable * Correct the module path for decorators * Reduce the time waiting for server ACTIVE * Updated from global requirements * Add random name in volume.base.create\_backup * Fix API reference links in volume/snapshot/type client * Move Cinder admin tests into unversioned path * Rename Cinder V2 API tests * Remove deprecated compute config options for validation-2 * Glance supports vhdx disk\_format * Removing py34 from tox * Fix API reference links in volume client * Correct invalid param in create\_volume * Remove unused CONF and LOG * Remove unnecessary usuage of instance variable * Volume force-delete when volume in maintenance state * Remove set\_mac\_address from old remote\_client * Remove remaining usage of cinder v1 API call from Tempest * Volume reset to maintenance mode * Remove unnecessary Cinder v1 code * Nova: Move \_Websocket class to a common place * Remove cinder clients api version 1 * Remove name checking for api version 1 * Should be more clear for device name opt * Raise exception when get\_disks() cannot get 'TYPE' column * Skip flaky test\_create\_router\_set\_gateway\_with\_fixed\_ip * Adding server evacuate client * Add skip method for volume test with the glance service * Add note on get\_disks about unsupported guests * Fix race in test\_attach\_detach\_volume * Fix multiple create for multiple networks * Unskip test\_snapshot\_list\_param\_limit\_equals\_zero * Separate object-storage bulk operation service clients * Identity V3-ext Oauth1 Consumers Client * Use Sphinx 1.5 warning-is-error * Remove special\_fields definition * Ensure compute features enabled in test\_images * Add "list Cinder API versions" * Remove explicit install of setuptools * doc: Remove config for unused builders * Add test case for volume unmanage and manage * Change API-WG guideline link to specs.o.o * Add test case for update volume encryption type * Fix for test\_volume\_swap * Add reno for removing Cinder v1 API tests * Deprecate the \`\`deactivate\_image\`\` configuration switch * Add user key data debug script to instance user data * Remove assign\_static\_ip from old remote\_client * Remove set\_nic\_state from old remote\_client * Deprecate Cinder v1 API option * Remove Cinder v1 tests * Improve server tag schema * Deprecate the dvr\_extra\_resources config switch * Improve multiple create server tests * Fix typo in README.rst * Updated from global requirements * Make novnc test compatible with RFB3.3 * Make remote\_client reno readable * Add "list Glance API versions" test * Remove unnecessary show\_floatingip * Revert "Docs: Add the remote tag to the badge image" * Use base.create\_image\_from\_server to create server snapshot * Add test case for Keystone API "GET /v3/auth/projects" * Add test\_router\_set\_gateway\_used\_ip\_returns\_409 * Add test\_create\_router\_set\_gateway\_with\_fixed\_ip * Cleanup ignored\_list for T110 and T111 * Add new detail kwarg to show\_quota\_set to QuotasClient * Network scenarios: remove some instance variables * Add plugin group names to CONF * Use tempest.lib data\_utils - volume * Use tempest.lib data\_utils - orchestration * Use tempest.lib data\_utils - object\_storage * Use tempest.lib data\_utils - network * Removing sahara from the service available group * Add Cinder microversion test info in doc * Use tempest.lib data\_utils - image * Use tempest.lib data\_utils - identity * Use tempest.lib data\_utils - compute * Use tempest.lib data\_utils - scenario * Add RemoteClient under tempest.lib * Add "list versions" test for Keystone * Move plugin client registration to proxy * Add a bit more debugging to the client registry * Remove send\_signal, get\_pids, mount and umount * Updated from global requirements * Add api tests for create-image * Add a negative test for security\_group api * Correct API reference link in compute client(3) * Correct API reference link in compute client(2) * Correct API reference link in compute client(1) * Correct API reference link in identity client * Deprecate glance APIs version config options * Correct API reference link in volume client * Correct API reference link in image client * Correct API reference link in servers\_client * Remove get\_ip\_list() * Add network\_mask\_bits argument to assign\_static\_ip() * Add dhcp\_client argument to renew\_lease() * Move CONF values into \_\_init\_\_ in RemoteClient * Fix keypair GET response schema * Remove get\_ram\_size\_in\_mb, get\_number\_of\_vcpus, etc * Drop allowing old test.idempotent\_id * Add call\_and\_ignore\_notfound\_exc to Keystone base * Add a new scenario tox env * Fix typo of setUpClass * Change default of auth\_version to v3 * Move test\_extension and use base for non-admin * make -e full run tempest scenarios serially * Add --combine option to tempest run * Extend compute API admin test for swap volume * Update README section on Python3 support * Improve error info in assertEmpty and assertNotEmpty * Deprecate resources\_prefix and change rand\_name() * Add smoke tag to compute version tests * test\_neutron\_resources.py exception handler * [Fix gate]Update test requirement * Correct test\_list\_servers\_filtered\_by\_ip for bug 1668828" * Improve test\_implied\_domain\_roles * Change rebuild tests to use image\_ref * Close connection after each failed connect attempt * Remove unused variable * Remove unused variable and unnecessary instance variable * Remove unused client in setup\_clients * Updated from global requirements * Fix remote connectivity checks * Removed unused PING\_IPV4\_COMMAND etc * Do not use skip\_checks in TestServerAdvancedOps * Use base.attach\_volume in test\_attach\_volume * Revert "Add reno for Tempest v15.0.0" * Remove testing of bdm v1 in boot from volume tests * Fix tempest 14.0.0 release notes * Fix tempest 15.0.0 release notes * Add unit test to verify output method * Fix subunit describe calls for py3 * Remove volume\_feature\_enabled.volume\_services * Fix the removal of config.skip\_(unless|if)\_config decorators * keystone roles are case insensitive * Remove unnecessary wait\_for\_server\_status * Add content-type without spaces * Use base.create\_flavor in test\_flavors\_access * Adds missing server tags APIs to servers client * Remove unnessessary metadata parameter * Use 'delete\_snapshot' method * Update the test removal procedure * Fix volume v2 capabilities/scheduler-stats tests to test v2 APIs * Remove extra "?expanded=" from api link * Skip test\_stamp\_pattern until bug 1664793 is fixed * Remove deprecated test runner wrappers (.sh files) * Use upper-constraints for tox envs * Remove call\_until\_true from test module * Use a valid project\_id flavor\_access neg tests * Add volume snapshot metadata parameter * Remove unnecessary show\_server in TestServerAdvancedOps * switching to decorators.idempotent\_id * Remove unnecessary assert and value assignment * Add missing @test.attr(type='negative') for negative tests * Add reno for Tempest v15.0.0 * Revert "Remove the skip\_unless\_config and skip\_if\_config decorators" * Rename reno files of v15.0.0 * Revert "Remove wait\_for\_server from create\_image\_from\_server" * There are some typos in releasenotes * Remove unnecessary int casting * Add test creating a protected image * Deprecate the skip\_unless\_attr decorator * Scenario manager: remove some useless \`\_list\_\*\` methods * Fix tests for bug 1656183 * Add negative tests for snapshot pagination * Remove unnecessary int declaration * Add test for compute API List Security Groups By Server * Add decorator for negative tests * Add a generic "wait\_for\_volume\_resource\_status" function * Add v15.0.0 releasenote 15.0.0 ------ * Do not use unnecessary instance variable in compute and volume * Change instance variable to local variable in scenario/image/volume * Fix test\_novnc for python3 * Remove unused variable and unnecessary instance variable in network * Remove deprecated compute config options for validation * Remove unused variable in compute and volume * Remove the skip\_unless\_config and skip\_if\_config decorators * Add DeleteErrorException * Remove unused neutron\_available * Make test\_volume\_pools A/A compatible * Add admin\_servers\_client in BaseV2ComputeAdminTest * Remove unused client in setup\_clients * Add skip if public\_network\_id is not specified * Remove input-scenario config options * Fix AttributeError in wait\_for\_volume\_retype * Try boot a vm with lower than min ram * admin/test\_flavors: some code cleanup and factorization * Remove deprecated network config options * Remove redundant parameters for server creation * Use base.create\_flavor in servers test * Verify metadata preservation after backup restore * Remove deprecated compute microversion config options * Add PreconditionFailed exception for HTTP 412 errors * Check whether server is located on the requested host * Use test\_utils.call\_until\_true in tempest tests * Update \_create\_test\_user in identity base * Finish switching to decorators.idempotent\_id * Remove redundant setup\_client method * Fix router port IP address references * Introduce flake8-import-order * Remove skip of test\_stamp\_pattern * Domain specific roles API tests * Add resources\_prefix to rand\_name when created * Enable sphinx on servers\_client * Test live migration back and forth * Make wait\_until default as 'ACTIVE' in manager.create\_server * Add create\_flavor in compute.base * Unskip test\_reassign\_port\_between\_servers * Add v14.0.0 releasenote * Use correct routers\_client in \_delete\_router * Swift list containers should test for reverse listing param * Revert "Fix \_check\_network\_external\_connectivity in test\_network\_basic\_ops" * Fixed wrong link in microversion\_testing.rst * test\_create\_server\_from\_volume\_snapshot: assert dict is not empty before accessing a key * Fix AZ List Detail schema to allow hosts as None * Get server fault if snapshot fails * Add unit tests for BaseV2ComputeTest.create\_image\_from\_server * Remove wait\_for\_server from create\_image\_from\_server * Implied roles API tests * Fix \_check\_network\_external\_connectivity in test\_network\_basic\_ops * Switch to decorators.idempotent\_id on scenario * Add test.attr for negative tests * Switch to decorators.idempotent\_id on volume * Switch to decorators.idempotent\_id on orchestration * Switch to decorators.idempotent\_id on object\_storage * Switch to decorators.idempotent\_id on network * Switch to decorators.idempotent\_id on image * Add test\_create\_is\_domain\_project * Implied roles methods * Remove unused client from "\_create\_network" method * Log server state changes when waiting for delete * Add related\_bug for bug/1660878 * let addCleanup use the current client * Fix map usage on py3 in v2 test\_volumes\_snapshots\_list * Fix tests which use 'display\_name' for both V1 and V2 * ssh: Add proxy support * Add related bug#1659811 for tenant\_id filter compute tests * Fix date-time format checking in response schema * Add test for compute API microversion 2.42 * Create volume from private volume type * Snapshot v2 pagination tests * Switch to decorators.idempotent\_id on identity * Switch to decorators.idempotent\_id on compute.\* * Switch to decorators.idempotent\_id on compute.servers * Switch to decorators.idempotent\_id on compute.admin * Remove default\_params\_with\_timeout\_values variable * Refactor test\_roles\_client * Use single underscore variable in loop iteration(in compute) * Use single underscore variable in loop iteration(in identity) * Remove a redundant 'body' variable * Add extra elements check for 'show\_volume\_type' command * Remove ListImagesTest from test\_images * Don't skip Cinder backup tests based on Swift's availability * Fix update\_host API response schema * Using oslo\_log instead of logging * Use single underscore variable in loop iteration * Remove a redundant cleanup\_snapshot method * [py35] Fixes to get more tempest tests working * Add tempest tests for volume retype with migration * Migrate volume while attached to an instance * Fix test test\_rescue\_unrescue\_instance * Modify the indentation problem for the if loop * Boot server from snapshot * Add namespace tags client and tests * Correct print pattern in rest\_client * Fix compute baremetal service client tests * Replaces yaml.load() with yaml.safe\_load() * Add random name in base.\_create\_keypair * Modify comments in xxx\_name\_length\_exceeds\_256 * Add generate\_random\_security\_group\_id in BaseSecurityGroupsTest * Use oslo.log library instead of system logging module * Improve volume\_backed logic in create\_test\_server * Add a test case for metadata POST * Fix logging messages not being formatted correctly * Shared images test create with default visibility * Use Token Clients from the client factory * Use invalid id with rand string instead of special char * Add short options to tempest * Remove unnecessary name definitions * Define 'delete\_snapshot' method as a static method * Feature flag: allow disabling 'manage snapshot' tests * Use 'attach\_volume' method * Add tempest test to test NoVNC support * Formally deprecate the allow\_port\_security\_disabled feature flag * Define 'delete\_volume' method as a static method * Allow not the same sequence in container\_formats and disk\_formats * Fix a typo in 'attach\_volume' docstring * Unmanage and manage snapshots * Fix variable name * Set PYTHON env variable for python3 * Deprecate the volume\_feature\_enabled.volume\_services feature flag * Use base.delete\_server in addCleanup * Deprecate the identity-feature-enabled.reseller config option * Remove an unused variable in the BaseTestCase class * Remove the tempest/tests/negative/ directory * Check error message returned by the system * Removes unnecessary utf-8 encoding * Replace six.iteritems with dict.items * Remove 'id' from expected in SecurityGroupRulesTestJSON * Remove \_migrate\_server\_to's return value * Add missing IPs validation to test\_rebuild\_server * Add related\_bug() to 1629110's test * Remove skip for Heat-Neutron tests * Add test namespace object functions in images * Remove redundant assertIn('id', xxx) * Do not use message=msg in InvalidConfiguration * Add a test for reproducing bug/1651064 * Removes unnecessary utf-8 coding * Correct boundary value of image id length * Add check user access in test\_remove\_member * Port object\_storage tests to Py3 * Add accounts.yaml to .gitignore * Making all identity service clients as tempest available 14.0.0 ------ * Use call\_and\_ignore\_notfound\_exc method * Fix container cleanup in test\_account\_bulk * Rename reno files for releasing 14.0.0 * Fix service clients for kwargs as None or 0 * Use lower value to create flavor for creating server * Define v3 domains\_client as library * Add releasenotes for baremtal tests removal * Forcing dynamic credentials for identity admin * Clients.py: add back the default\_params\_with\_timeout\_values class variable * Hacking: enable H904 * Add connectivity check test for migration with revert * Avoid volume limit usage for existing volumes * Do not use self.volume\_origin in volume.base.attach\_volume * Remove unused clients in setup\_clients * Use ephemeral=0 to create flavor without ephemeral disk * Fix flavor\_client create\_flavor interface * test\_service\_providers: require service-type ext * Add volume backup description parameter * Do not create volume in resource\_setup for test\_volume\_reset\_status * Remove unused CONF and LOG * Remove unused client in SecurityGroupRulesNegativeTestJSON * Use base.delete\_server in base.rebuild\_server * Few updates for baremtal dependent tests * Removing baremetal tests from tempest tree * Use base.delete\_server in ServersTestJSON * Improve volume pools tests code * Fix a few Python 3.x issues * PCI-DSS tests * Separate capabilities service method from account\_client * Add test.related\_bug() to know launchpad bug reports * Make identity v3 domains\_client use \*\*kwargs * Set auth in clients before they are used * Add namespace property unittest * Use base.create\_volume in VolumesTestJSON * Remove unused client in setup\_clients * Backup and restore bootable volume * Docs: Add the remote tag to the badge image * Network test: rework the service types/service providers tests * Correct improper comment * tempest/test.py: make set\_validation\_resources() more idiomatic * Use wait\_until in create\_test\_server to wait for server ACTIVE * Wait for server to be active before deleting * Improve help message for scheduler\_available\_filters * Correct tempest reraising of exception * Add status\_code to RESTful exceptions * Honour discoverability feature flag in swift tests * Add extra unit tests for base\_url * Remove the Auto Tests Generators * Tox.ini: removed an outdated code comment * Tox: Hacking: enable extensions H106 and H203 * Remove unused admin\_hosts\_client * Add server\_group in default\_quota\_set * Correct variable usage error in test\_get\_hypervisor\_uptime * Correct fake V3 token responses * Delete volume first before deleting snapshot * Replace str(uuid.uuid4) with uuidutils.generate\_uuid() * Modify incorrect import path * Add get metadata schema test methods * Remove unused client * Change personality inject path to / * Add sleep(2) when try to delete a container * Use different file content to test max\_number\_personality\_files * Correct boundary value test in ImagesOneServerNegativeTestJSON * Remove prepare\_instance\_network in ImagesOneServerNegativeTestJSON * Rename test\_delete\_image\_id\_is\_over\_35\_character\_limit * Write a couple of test cases together * Add a test for attaching 2 volumes to a server * Make common->compute->shelve\_server param more clear * Add resource clean for tests * Add server group limits in test\_absLimits\_get * Remove unnessary assertIsNotNone * Fix unit-test test\_user\_messages file name * Use TempestException from tempest.lib * Added bindep.txt to the project * Remove \_is\_true() from test\_volumes\_actions.py * Specify snapshot size when creating volume * Add possibility to pass prefix before cli command * Remove setup\_client method * Add 'Negative' to negative test class names * Rename "VolumesCloneTest" class name to "VolumesV2CloneTest" * Add test create volume from bootable volume * Add resize\_server in compute.base * Fix compute test\_attach\_volume * Remove old v3 volume services library and use lib/service for v3 * Create two new clients under tempest.lib libary * Fix typo in documentation * Show team and repo badges on README * Minor fix in role\_assignments\_client docstring * Remove an obselete msg from then optimize a router test case * Updated from global requirements * Move role\_assignments\_client to tempest lib * Use names containing "non-existing" as non-existing resource names * Do not use instance variable in FloatingIPsNegativeTestJSON * Replace directly import of logging with import oslo\_log * Remove CONF.volume\_feature\_enabled.bootable * Rename testcase names with 'with\_out' to 'without' * Add namespace properties client and tests * Add deprecated\_reason for nova\_cert * Integration tests for device role tagging * Check project connectivity on port admin state * Add wait\_for\_server\_termination in test\_server\_basic\_ops * Updated API ref link as single line which is more readable * Add hypervisor\_type option * Make the parameter 'device' optional * Remove skipException for volume creation failure * Remove negative\_rest\_client * Make get\_partitions() work for partitioned disks * Use names containing "invalid" or "nonexistent" as invalid name * Make Identity v2 service clients as available module * Check volume and minimum disk sizes to create volume * Cinder absolute-limits tests * Improved Cinder snapshot tests * Remove unused client in ServerMetadataTestJSON * Remove meaningless assignment * Use call\_and\_ignore\_notfound\_exc to cleanup floating-ip-bulk * Move list\_hosts to resource\_setup in test\_hosts\_negative * Add missing volume snapshot skip * Do not remove server\_groups from default\_quota\_set * Use assertIs(Not)None to check for None * Add api ref link in TenantUsagesClient * Fix ref link in volume v1 service clients * Fix volume\_create to use shared function with a cleanup * Delete duplicated dvr tests * Bump hacking version in test-requirements.txt * Use the correct path of InvalidConfiguration * Typo fixing * Move wait\_for\_interface\_status to waiters * Wait for FIP status to get to DOWN in test\_router\_rescheduling * Improve error message on volume tests failure * fix bulk service name * \_log\_console\_output missing space * Updated from global requirements * Fix glance create image * Use more specific asserts in tests * Get rid of useless tenant\_id attribiute * \_log\_console\_output do not raise NotFound * Move cred\_client to tempest.lib * Update volume client class description * Updated from global requirements * Add connectivity check test for migration * Validate power\_state enums in compute API response schema * compute/images/test\_images\_oneserver: don't share resources * Updated from global requirements * Fix cinder message-client naming to volume\_v3\_messages\_client * Delete TimeoutException in tempest/exceptions.py * Require l3-ha extension for test\_centralized\_router\_update\_to\_dvr * Add negative tests about update-volume API * Fixing 'test\_verify\_created\_server\_ephemeral\_disk' test * Do not have heat to connect to external service * Use InvalidCredentials exception from correct path * Use random name in network common function * Remove services/volume/{v1,v2} directories * Remove a redundant dictionary * Fix a docstring typo in manager.py * Remove unnecessary function \_create\_multiple\_servers * Pop name from volume.base.create\_server * Boot a server from a non-bootable volume * Use assertGreater(Equal) over assertTrue * Make the non ha router usage explicit in a dvr test * Fix race in test\_networks.py: don't try to get a possibly-deleted network * Correct a scheme example error from fake netutron versions client * Use is\_scheduler\_filter\_enabled for ServerGroupAffinityFilter * Add random name in scenario.manager.create\_server * [TrivialFix] Replace 'assertTrue(a in b)' with 'assertIn(a, b)' * Use random name in common function * Use random name in volume.base.create\_snapshot * Use base.create\_test\_server\_group to create server group * Define image status enums for compute proxy API schema * Pass username on v3 token issue * Move some network tests from Neutron to Tempest * Add missing tests for the image v2 API * Use assertGreater(Equal) over assertTrue * delete list\_all\_container\_objects in the container\_client * Updated from global requirements * Add \_error\_checker() call on versions\_client * Remove unused arguments from \_error\_checker() * Use assertGreater(len(x), 0) over assertTrue(len(x) > 0) * Revert "Skip unstable v6 scenario tests" * Repalce to "tempest account-generator" * Don't rely on testtools.tests * Remove the Stress framework * Use rand name in common function in scenario tests * Use common function create\_volume to create test volume * Add test\_volumes\_list functions in test\_volumes\_list.py * Use random name in network common function * Correct image's random name * Set random name in common function create\_server * Remove unnecessary name definition * Remove unnecessary function \_create\_multiple\_servers * Fix a typo in test\_security\_groups.py * Remove testscenarios denpendency * Image cleanup is missing in test\_create\_delete\_image * Remove one-line function in test\_volumes\_actions * Fix a typo in test\_networks.py * Move volume service clients under tempest.lib * Skip test\_volume\_backed\_live\_migration unconditionally * Revert an accidental error modification of config item help message * Deprecate nova api extensions config option * Fix wording for python3.4 section in the readme * Stop using subprocess for testr init in tempest init * Improve error reporting when workspace not registered * Move InvalidConfiguration exception to tempest.lib * Use lib version of data\_utils in dynamic creds * Move cred\_provider abstract class to tempest lib * Remove CONF usage from dynamic\_creds module * Remove the NegativeAutoTest Framework * waiters.py: raise BackupException defined in tempest/lib * Use Cinder v2 by default in scenario tests * Remove NetworksIPV6TestAttrs duplicate tests * Correct 'list index out of range' in FloatingIPDetailsTestJSON * Add test list namespace function * Correct 'list index out of range' error in dhcp agent test * Isolate change\_server\_password to use its own server * Remove unnecessary name definition * Add unit tests for volume quotas\_client * Add port parameter to ssh Client * Add unit tests for volume snapshots\_client * Use common func create\_volume in test\_volumes\_actions * Remove unused func \_detach * Correct improper assert judgement * Remove redundant assert judgement * Add a generic method for backup creation * Remove unnecessary wait-for-volume-available * Add list the primary tenant * Add negative tests for deleting attached volume * Updated from global requirements * Use a common method for is a router interface * Rename reno files of 13.0.0 * Fix the 13.0.0 newton release note * Remove duplicate python-subunit from test-requirements.txt * Add v13.0.0 release note page * Enable release notes translation 13.0.0 ------ * Add release notes for start of Newton support * Add unit tests for encryption\_types\_client * Remove meaningless volume negative test * Only call register\_service\_clients if there are clients * Test case with list-instance-actions for deleted server * Remove Sahara tests from Tempest * Updated from global requirements * Clarify the guideline of negative tests * Moving scenario docstring under the relevant test method * Remove a redundant image\_id variable * Re-use common methods in ServerRescueNegativeTestJSON * Re-use common methods in test\_rebuild\_server\_with\_volume\_attached * Re-use common methods in test\_delete\_server\_while\_in\_attached\_volume * Re-use common volume create/attach methods in test\_iscsi\_volume * Add compute API admin test for swap volume * Switch unit test tox jobs to use ostestr * Fix docstrings in Tempest REST client for Ironic * Add link for Unset keys and update some links in qos\_client * Permission changes: Python file should be 0644 not 0775 * Migrate backups clients to tempest.lib * Add a test for revert cold migration * Adding a new option CONF.network.dvr\_extra\_resources * Add 'code-block' and revise tab * Reviewing guideline: ask for CRD when new test+feature flag * Remove over-comment * Add existing volumes when using pre-provisioned credentials * Trivial: Delete a definition that is not needed * Updated from global requirements * Fix typo in the file * Add inherited role assignments tests * Fix tempest init inconsistency when it fails * Update hotplug test description * Docstrings should not start with a space * Correct a misleading in docstring * Allow for wait in \_restore\_password * Fix a few grammatical errors in docs * Correct several typos * Trivial: Remove redundant variable * Updated from global requirements * Add a test for cold migration * Remove redundant links to clients * Wait for floating\_ip to detach from server after deletion * test\_images\_oneserver - use the addCleanup mechanism * test\_list\_servers\_by\_admin(specified\_tenant) for preexisting servers * Add prefix "$" for command examples * Remove misleading arguments * Fix Release Notes index page title * standardize release note page ordering * Add more swift container negative tests * Introduce a new tox target that checks requirements.txt * Fix compute test\_agents tests * Remove a redundent variable * Fix LOG.warn to LOG.warning * Change into staticmethod * Skip some tests if security-group extension is disabled * Move wait\_for\_qos\_operations method to common.waiters * Move wait\_for\_backup\_status function to common.waiters * Add test to get images by owner * Avoid local variable referenced before assignment * Move test\_snapshot\_list\_\* tests to new file * Remove invalid assertions for 304 resp * neutron: added test case to check connectivity using MTU sized frames * Updated from global requirements * Fix assert for public networks with multiple subnets * Remove residual package * Define v2 encryption\_types\_client as library * Remove duplicated judgement * Migrate V1 and V2 qos service to tempest lib * Fix missing serial option of tox full-serial target * Rename renos of 12.1.0 * Rename renos of 10.0.0 and 11.0.0 * Trivial: group import statement in client.py * Deprecate method get\_ipv6\_addr\_by\_EUI64 * Don't include openstack directory in exclude list for flake8 * Delete Savanna element in client list * Container Services underscore in metadata key translate to dash * Remove image v1 test\_delete\_image\_with\_invalid\_image\_id * tempest-cleanup can only be invoked as tempest cleanup * Fix subunit-trace output with tempest run * Fix typo in data\_utils.py * remove unused statement * Split base snapshots\_client into v1 and v2 * Revert "Fix of verify\_glance\_api\_versions" * Define v1 encryption\_types\_client as library * Rename renos of 12.2.0 * Fix with using oslo\_serialization base64 module * Use other item instead of security\_group in network-quotas test * Fill name param in create\_test\_server * Switch tox to use tempest run and deprecate bash runners * Scenario: server\_basic\_ops: use regular variable (not instance var) * Define the volume types\_client as library * Scenario: object\_storage\_basic\_ops: move and update test description * Scenario: test\_minimum\_basic: remove useless function * Scenarios: remove redundant call to \`resource\_setup()\` * Add some params docstring to create\_server * Add config option to create networks with port\_security\_enabled * Always wait on server delete * Remove \_setup\_network\_and\_servers() in TestNetworkAdvancedServerOps * Fix credential client to return raw response * Use ConfigParser instead of SafeConfigParser * Update skip message in overlimit testcases * Remove unnecessary resource\_setup() * Modify use of assertTrue(A in B) * Rename test\_list\_servers\_filter\_by\_server\_status * Ignore deb-\* packaging repos in the plugin list * Fix the docstring of \_default\_security\_group * Remove unnecessary \_\_init\_\_.py of identity v2 * Replace volume's "test" name with classname as prefix * Update Available para link of image v1 client * Remove tests/fake\_auth\_provider * Use local variable for volume instead of instance one * Add description to the help of enable\_instance\_password * Update tempest init help message * Remove auth\_request as no used * Reordering tests under the approprite directories * py3: Miscellaneous fixes * Define v3 credentials\_client as library * Define v3 trusts\_client as library * Move method to v2 volumes\_client * Update Available para link of keystone v3 client * Use api\_extensions to decide security\_group type nova used * Move v3 inherited\_roles\_client to library interface * Move v3 roles\_client to library interface * Define separate inherited\_roles\_client for inherited roles * Validate floating IP in server['addresses'] scenario * Use an instance method instead of class method * Define v3 identity\_client as library * Update Available para link of network client * Move 3 volume methods to v2 volumes\_client * Remove base\_types\_client for the reability * Separate encryption\_types\_client from types\_client * Don't create network resources for identity tests * Define the v3 Users Client as a library * Define v3 Groups Client as library * Update Available params of image * Delete duplicate if judgment * Use testcase name as prefix of resource name * Add skip logic for admin cred in test\_auto\_allocate\_network * Make identity v3 roles\_client use \*\*kwargs * Define v2 identity\_client as library * Add deprecated code review guideline 12.2.0 ------ * Remove unnecessary resource\_cleanup/setup * Narrow assertion in attach/delete volume test * test\_list\_servers\_by\_admin for preexisting servers * Move wait\_for\_qos\_operations into base volume test * Move \`call\_until\_true\` to tempest/lib * Fix doc build if git is absent * Count volumes vs partitions and remove hard-coded expected result * Remove get\_attachment\_from\_volume() * Remove the default size in volumes\_client * Remove unused methods from volumes\_client * Define v3 projects\_client as library * Add omitted blank space to assert\_msg * Update user and tenant client's Available params link * Use quota\_set instead of default\_quota\_set * Remove a word 'smoke' from some scenario tests * Add volume clone flag to clone tests * Add doc section on expectations of clean tenants * Adding testcases for metadata def resource types * Fix MismatchError in test\_host\_name\_is\_same\_as\_server\_name * Define v3 services\_client as library * Add a space between super's and setUpClass * Use expect\_empty\_body flag * Remove unused global variable from VolumesV1NegativeTest class * Define v3 regions\_client as library * Remove the update\_object() in object\_client * Move volume-type-access methods to volume v2 * Skip admin floating IP tests if router extension is disabled * Remove expected\_success check in images\_client * Add a release note link on README * Fix docstrings to match with method arguments * Adding update backups quota * Remove unused config.CONF * Revert "skip get-me-a-network tests" * Deprecate meaningless TYPE * Clean imports in code * Exclude gateway\_ip of subnet in get\_unused\_ip\_addresses * Delete unuesd function arguments in rest\_client * TrivialFix: Remove cfg import unused * skip get-me-a-network tests * TrivialFix: Remove logging import unused * Fix mutliple attach/detach issue * Add using dash options instead of underscore * Remove thirdparty from tox.ini * Lower the aes-xts-plain64 key\_size to 256 * Create resources using wrapper utility * Improve test\_update\_image\_metadata testcase * Update volume type details * Fix OverLimit's message * Add admin list\_servers test with invalid\_status * Use 'OS-EXT-SRV-ATTR:host' directly * Fix exec\_command to hang indefinitely * Call addCleanup(delete\_server) immediately after resize server * Fix parameter receive * Added logic to validate storage policy info * Remove unnecessary setUp() * remove unnecessary call in test\_delete\_container * Make args of types\_client same * Fix test\_volume\_services testcase * Revert "Move dscv and ca\_certs to config section service\_clients" * Remove an unused function in object\_client * Use classname as prefix of volume name * Use cls in class method instead of self * Scenario: remove the \`addCleanup\_with\_wait\` helper method * Add compute API tests for 'get-me-a-network' * test\_schedule\_to\_all\_nodes should choose available nodes * Class Credentials not define \_\_ne\_\_() built-in function * Update the links to api-ref of network * Add existing volumes when using pre-provisioned credentials * Delete no need definitions * Use cls in classmethod instead of self * Remove unnecessary str() from some clients * Remove skip statement for bug 1450859 is fixed * Fix test\_shelve\_volume\_backed\_instance * Remove useless parenthesis * Fixed net\_id key evaluation for net['id'] * Remove a redundent resource\_cleanup method * Remove the test\_upload\_large\_objects negative test * Improving test\_volumes\_get testcase * Add missing config file read to tempest init * Adds to verify\_cinder\_api\_versions api\_v3 check * Remove unnecessary str() from base\_types\_client * Fixed manager.py to support multinode test on vnic\_port * Remove unnessary delete\_volume in test\_volumes\_list * Define volume quotas\_client as library * Remove base\_quotas\_client for the reability * Fix of verify\_glance\_api\_versions * Fallback to creds provider for fixed IP network if no network\_for\_ssh * Fix checks for content length in object storage tests * Fix network\_for\_ssh config option help * Fix reference to nonexistent ssh\_connect\_method in config help * change nova\_cert config to default False and deprecate it * skip test\_connectivity\_between\_vms\_on\_different\_networks * Fix manager->get\_auth\_provider interface * Move dscv and ca\_certs to config section service\_clients * Configure stable service clients via the registry * Define volume services\_client as library * Define volume hosts\_client as library * Remove unnecessary str() which was for XML * Don't load non json body * Migrate service\_clients to tempest.lib * Fix tempest and available modules * boot into a network that has "port\_security\_enabled=False" * Updated from global requirements * Remove a redundant wait\_for\_backup\_deletion() * Make identity v3 users\_client use \*\*kwargs * Make identity v3 credentials\_client use \*\*kwargs * Define volume availability\_zones\_client as library * Define volume extensions\_clients as library * Fix notes which differnt from actual parameters * Add a TODO to remove Ironic related workaround * Optional setting service client on factory * Make identity v3 services\_client use \*\*kwargs * Make identity v3 groups\_client use \*\*kwargs * Remove deprecated Javelin CLI utilities from Tempest * Add : to docstring of service\_clients * Fix. Do not create port if vnic\_type defined and port is passed * Run attach/detach volume tests even without ssh * assertEqual can be used instead of assertListEqual * Set timeout value in urllib3.poolmanager.PoolManager * Updated from global requirements * Update the links to api-ref of network * Remove the wapper method show\_quota\_usage() * Remove unused TYPE from base\_quotas\_client * Remove base\_services\_client for the reability * Remove base\_hosts\_client for the reability * Skip test\_reassign\_port\_between\_servers until fixed * Delete unused parameters * Do not use $ in regex * Modified into a more appropriate function * Modify a spelling mistake * Delete no meaningful definition * Remove base\_availability\_zone\_client for the reability * Remove base\_extensions\_client for the reability * Minor change to comment * Migrate image client group to client factory * Migrate network client group to client factory * Migrate compute client group to client factory * Register Tempest clients via the new interface * Service Clients registration interface for plugins * Fix README * Replace OpenStack LLC with OpenStack Foundation * Add available params in compute clients' comment * Add volume type description support * Do not use $ in OS user password * Use skip\_checks in test\_snapshot\_pattern * Deprecate run\_tests.sh * Fix typo about message of exception * Add server\_id in exception ServerUnreachable * Skip unstable v6 scenario tests * Fix release notes around * Increase size of subnet allocation pool * Define v3 policies\_client as library * Updated from global requirements * Define v3 endpoints\_client as library * Fix subunit-describe-calls name of usage * py3: Replace map/filter with a list comprehension * Modify comment which does not conform to code * Introduce the ClientsFactory * Add available params in volume clients' comment * Reverse order of get\_unused\_ip\_addresses * Remove unused LOG to keep code clean * Add new live\_migration case to support block\_migration=auto * Remove DataGenerator from the dentity base * Remove DataGenerator from admin V3 identity tests * Remove DataGenerator from admin V2 identity tests * Add python 3.5 classifier and venv * Fix typo in tempest/run help document * cinder backup reset status * Use skip\_checks instead of skipUnless in TestShelveInstance * Add a test for attach/detach port on multiple servers * Add sync to avoid the loss of pub key data * Fix typo in Test Removal Procedure doc * Correct reraising of exception * Requirements.txt: remove pyOpenSSL * Removes explicit looping over dict .keys() method * Add available params in metering labels client's comment * Add available params in ports client's comment * Add available params in neutron security group client's comment * Updated from global requirements * Don't enable DHCP on floating IP subnet tests * Add missing validation to test\_delete\_saving\_image * Remove non-locking accounts from the config guide * Add available params in network clients' comment * Add available params in subnet pools client's comment * Add available params in neutron security group rule client's comment * Add available params in baremetal client's comment * Add available params in identity v2 client's comment * Remove unused LOG * Add available params in neutron float ips client's comment * Add documentation for glance api * Factor up (most) CONF value in clients.Manager * Check network\_data info in config drive tests * Update docstring for create\_test\_server * Remove the unused tenant and user option in cleanup * Moving backup test under non admin directory * Remove white space between print and () * Cinder get-capabilities tests * Add available params in networks client's comment * Add available params in subnets client's comment * Tempest: Fixed a typo * Fix minor details in README * Update quickstart for revised temepst init setup * Making delete\_on\_termination configurable * Extend server schema for extended volume attribute * Fix test\_project\_create\_with\_parent * Updated from global requirements * Disable file injection by default * Add purge flag in image\_meta\_to\_headers * Delete dynamic tenant correctly when \_cleanup\_default\_secgroup fails * Cleanup tempest quickstart * Remove duplicated identity v2 clients * Fix teardown of the identity api * Use find\_test\_caller in test\_utils instead of in misc * Add basic tempest run instructions to the quickstart * Add support for specifying a config file to tempest run * Add request/response to subunit-describe-calls * Adds clone/snapshot volume test for create volume * Add documentation for glance api 12.1.0 ------ * Add support for workspaces to tempest run * Add whitelist and blacklist file options to tempest run * Add plugin registry generation to sphinx build * Fix subunit describe calls utility document warnings * Tidy up document index page * Define 4 identity v2 clients as libraries * Updated from global requirements * Prepare the Manager class for tempest.lib * Fix resize tests * Fix search disk name for the config\_drive in scenario test * Remove unused service tags and client * Member role may already exist * Remove placeholder file from releasenotes/notes dir * Add parent\_id to create\_project * Remove trove tests from tempest * Add \_\_pycache\_\_ to .gitignore * Define v1 images\_client as library * Move image\_meta\_to\_headers from images\_client * Py3: don't access the \`unicode\` type directly * Remove unnecessary \_\_init\_\_ and resource\_setup/cleanup * Fix AttributeError with run\_validation=true * Add available params in base\_volumes\_client's comment * Fix assertItemsEqual usage for py3 * Add workspace subcommands help * Fix Available params docs in service clients * Add Available params in volume backup and snapshot clients * Correct "Available params" link in create\_flavor * Move properties handling to the test side * Add guidance on negative tests in HACKING.rst * Api specs for update\_volume\_image\_metadata * Remove the \_try\_wrapper function from identity tests * Cleanup projects in reverse order * Restriction on sequence of allowed address pairs * Fix the init command global conf dir path * Py3: Don't use dict.keys()[0], dict.values()[0] or dict.items()[0] * Remove testscenarios usage from test\_server\_basic\_ops * Fixed typo in in data\_utils.py * Remove unnecessary setUp and tearDown * Add subunit-describe-calls * Move oslo config generator config inside package * Add section on release notes to reviewing doc * Service client modules in various services \_\_init\_\_ * Service client modules in object-storage \_\_init\_\_ * Service client modules in image \_\_init\_\_ * Service client modules in volume \_\_init\_\_ * Fix un-assignment local variable 'returncode' error * Add test removal procedure doc * Cinder volume type access tests * Add available params in set metadata * Test-requirements: Bump hacking to >=0.11 * Image metadata for volume * Service client modules in identity \_\_init\_\_ * Volume pagination with specific tenant * Update identity v2 users\_client methods name * Correct some misspelt words in print messages * Service client modules in network \_\_init\_\_ * Service client modules in compute \_\_init\_\_ * Move helper methods for object\_storage to base.py * Generic "delete volume" method * Remove unused LOG from images\_client * Define v1 image\_members\_client as library * Make \`tempest init\` working dir an abspath * Return ResponseBody object from delete\_role * Make identity v2 roles\_client use \*\*kwargs * Make identity v2 tenants\_client use \*\*kwargs * Make identity v2 user\_client use \*\*kwargs * Make identity v2 service\_client use \*\*kwargs * Add IPv6 rule creation to validation resources * Add a response schema for "log" attribute * Add tests for Cinder user messages v3 API * Backup create using force flag * Fixes port.id bug added with 777a307b3c9f4284facf081e6b951b5755333adf * Change hostname\_equals\_servername to get\_hostname * Add operator role to heat stack owner * Cleanup exceptions in tempest * Consider pre-existing VMs when testing server list * Add InvalidIdentityVersion in lib.exception * Remove neutron OO wrappers * Rename mathods of v1 image\_members\_client * Quota usage after volume transfer * Add available params in identity client's comment * Clarify "data" arg in create/update\_image() * Move get\_image\_meta\_from\_headers from images\_client * Remove the test path of image service * Add image API version to the reno * Remove unnecessary setUp() * Define v2 endpoints\_client as libarry * Make endpoints\_client use \*\*kwargs * tempest: use host instead of hypervisor for aggregate test * Make --concurrency not required in account\_generator cmd * Add available params in attach\_volume's comment * Define v2 images\_client as library * Add missing tests for the identity v3 API * Fix dsvm typo to dscv * Use is\_valid\_ipv4 from oslo.utils * Fix rendering of account generator utility authentication table * Fix docs warnings of auth * Fix the reno format of image-clients-as-library * Disable SmartyPants for docs * Add tempest run command * Count partitions starting with sd prefix * Move reno of add-tempest-workspaces * Stop passing TestResources to Managers * Define \_set\_network\_clients * Define \_set\_image\_clients * Remove unused return value from take\_action * Define image\_members\_client of image v2 as library * Use scope in v3 identity client * Rename image members clients * Define 3 image clients as libraries * Add unit tests for glance v2 service clients * Separate resource\_types\_client from images\_client * Allows specifying a name for a particular endpoint * Add unit test coverage for account generator * Cleanup some small issues in the microversion testing doc * Fix cinder volume nameing for admin * Remove use\_default\_creds from preprov creds * Fix the v2 image client variable name * Centralized Workspaces * Make missing global config dir not fatal in tempest init * Separate Image v1 members client * Separate schemas\_client from v2 images\_client * Cleanup wording and section for remote access config doc * Let method \_get\_ssh\_connection handle timeout error * Separate namespaces\_client from v2 images\_client * Separate Image v2 members client * Fixed a Typo * Adding/fixing docstring to \_create\_creds function * Add vcs string to version option output * Account generator for identity v3 * Tempest image clients use version in URL * test\_aggregates should choose available compute node * Remove unused \_get\_file\_size() * Check which glance client version to use * Define routers\_client as library interface * Move comptue base test to common dir * Remove tempest/tests/services/compute path * Remove unnecessary \_\_init\_\_() * Updated from global requirements * Return 'DOWN' ports when Ironic enabled * Make username and project use same random ID * Increase test coverage on preprov creds * Fix volume backup import test * Change "$>" to "$" as CLI prompt * Not to create security group when security\_group ext is disabled * Fix Tempest testing way on the doc * Remove integrated dashboard tests * Add hacking rule to enfore no config in tempest.lib * Remove delete\_extra\_routes() * Remove update\_extra\_routes() * Remove GET ops from update\_router() * Updated from global requirements * Keep py3.X compatibility for urllib * Revert "Modify --endpoint-type to --os-endpoint-type for nova" * Correct test\_get\_usage\_tenant's AttributeError * Use @wraps decorator * Introduce new helper: call\_and\_ignore\_notfound\_exc() * Unit test: fix flaky test\_networks\_returned\_with\_creds * Remove the \`file\_utils\` module * Modify --endpoint-type to --os-endpoint-type for nova * Remove the Glance HTTP client. Use the common Rest Client instead * Use api\_extensions to decide security\_group type nova used * Moved Cinder QOS tests to admin directory * Finish removing legacy credentials * Add admin role on domain for v3 * Swift object client: use urllib3 builtin support for chunked transfer * Fix docs errors and warnings * Use common "waiters.wait\_for\_image\_status" function everywhere * Heat: wait condition: allow insecure HTTPS url * Introduce scope in the auth API * Rename get\_image\_meta method into check\_image for Glance V1 client * Hash credentials on user, project/tenant and pwd * Modify --endpoint-type to --os-endpoint-type for nova * Remove kilo config flags from Tempest * Document tempest APIs which plugins may use * Fix update\_password tests * Use mock instead of relying on command stderr * Add simple test for Neutron GET / * Remove deprecated legacy credentials provider * Modify projects\_client to receive more attributes * Add missing test for the object storage v1 API * Updated from global requirements * Cinder storage pools tests * Docs: Fix Hacking guide bulleting * Improve logging of credentials * Add assertion to test\_create\_security\_group\_rule\_with\_invalid\_ports * Update cfg option network\_for\_ssh's help msg * Correct misspelt word in msg in test\_images.py * Add a test case for rebuild of instances with volumes * Revert "Skip test\_resize\_volume\_backed\_server\_confirm for now" * Adding wait\_for\_resource\_deletion function for volume cleanup * Skip test\_resize\_volume\_backed\_server\_confirm for now * Addresses Expect: 100-continue client behavior * Change scenario test 'test\_resize\_server\_confirm' * Add separate release notes page for 12.0.0 * Improve RestClient rate limiting 12.0.0 ------ * Add release notes for kilo EOL release * Add test case for CONF skip decorators with message * Ensure tempest tests don't assume IP address allocation strategy * Snapshot list using 'limit' parameter * Add cleanup in identity/test\_users\_negative.py * Adding documentation to dynamic\_creds functions * Editing descripition for \_create\_loginable\_secgroup\_rule * Skip multinodes testing if no same/different scheduler filter enabled * Correct mispell words in comments * test\_port\_security\_macspoofing\_port: Don't assume DHCP port * Fix the docstring of skip\_unless\_config() * ssh before shelve to avoid ssh failures * Pass server to RemoteClient in API tests * Extend remote client to allow for better debugging * Use a single venv for all tempest jobs * trove: skip broken tests * Added random Infiniband GUID address generator * Update configuration docs wrt admin credentials * Remove few refercences left to tempest-lib * Remove negative test framework documentation * Removed q-vpn from the list of dirty logs * Migrate network resources into scenario path * object\_storage/object\_client.py: kill some dead code * Fix reference to tempest\_lib * Verify config support keystone on subpath * Fix tempest init to update config options * Fix conf file open mode when "tempest init" * Volume: remove a useless \`\_delete\_volume\_type\` alias * Py3: \`reduce\` is not a built-in anymore, use six.moves instead * Py3: fix an error when adding two \`dict\_items\` * Add option to tempest init to show global conf dir * Fix error message about credential * Search another path for etc/tempest * Use the same logic for selecting config path * Remove unnecessary space in help * Make README.rst consistent * Add generic CONF skip decorators * Fix base unit test class location * Fix account generator error message * Remove Ceilometer tempest tests * Fix usage of rest\_client expected\_success() in tests * Add ostestr tox jobs * Add tests of fixed\_ips in the attach interfaces nova API * Skip test\_volume\_list\_with\_detail\_param\_marker * Updated from global requirements * List only alive agents * SSH: Do not rely on the .closed attribute * Align multiple lines in tox * Add pep8 check to use data\_utils.rand\_uuid() * Fix rest\_client's expected\_success for non int status * Add a test for reverting a resize with a deleted flavor * Remove definitions of unused exception classes * Remove iso8601 and anyjson from requirements.txt * Remove old cinder multibackend configuration from tempest.conf * "is\_resource\_deleted" fails to verify delete * Microversion v2.20 tests: nova volume operations when shelved * Move shelve server logic in compute utility * Add compute 2.10 microversion tests info in doc * Replace Mox with Mock * Do not create network resources in verify-config * Updated from global requirements * Get rid of httplib2, use urllib3 instead * Remove openstack-common.conf * Updated Image Not Created Error Thrown * Remove JSON from class name * Fix version replacement when path * Refactor extract code to function in auth.py * Use tempest.tests.lib.base instead of tempest.tests.base * Adding explanation comment for multi-node test * Fix Review Checklist URL * Enable T108 check for network api tests * Update extra\_headers param docstring * Updated from global requirements * Add separate release notes page for v11.0.0 * Move multi-tenant server negative tests into subclass * Change alarm client name and tenant name * Change assertTrue(isinstance()) by optimal assert * Extend T110 check for lib's service clients * Added test for router's port update with fixed IP 11.0.0 ------ * Move keypair client to lib interfaces * Skip test\_compare\_db\_flavors\_with\_os * deprecate use of tenant in configs * Make ports\_client use common waiter method * Fix volume mountpoint - read from configuration file * Remove AuthorizationTestJSON tests * Ignore created\_at/updated\_at in test\_show\_port * Fix \`test\_requires\_ext\_decorator\_with\_all\_ext\_enabled\` * Add pep8 check for tempest.lib import * Prepare for dynamic generation of tempest plugin registry * Remove the migrated service client * Add T111 history to HACKING.rst * Use common "waiters.wait\_for\_snapshot\_status" function everywhere * Removing wrapper method for "wait\_for\_volume\_status" function * Fix typo in variable name * Add pypi download + version badges into README.rst * Removing some redundant words * Fix a few typos in microversion doc * Add release notes for tempest release 11.0.0 * Add release notes page for v10.0.0 * glance\_http: add IPv6 support * More info when fail to get api version * Fix comments in microversion doc * Removed deprecation warning from 'tempest --help' * Correct server basic ops test case name * Add microversion testing doc in tempest * Move wait\_for\_resource\_status() * Move list\_dhcp\_agents\_on\_hosting\_network() * Remove redundant list\_router\_interfaces() * Fix tests when having multiple floating pools * Update the docs on ssh validation * Add tests for compute v2.10 microversion * include domain\_id when creating groups * Remove unsed wrapper method * Trivial: Fix some document comments in microversion * Make routers\_client use \*\*kwargs * Add support of microversion in all compute service clients * Cinder verifies getting volume list with marker * Fix API test for external subnet visibility * Add note a section to lib doc about where to put plugins * Fix cleanup CLI for router client * Fix PendingDeprecationWarning for assert\* * Migrated microversion testing framework to tempest/lib * Move microversion config options to 'compute' section * Remove tempest duplicate copy of skip\_unless\_attr * Fix V3 credential behavior, documentation * TrivialFix: Remove pending deprecationwarning * Optimize "open" method with context manager * Adding a note to 'test\_update\_user\_password' test for Keystone V2 * Update comments for keystone precision and fernet * Unskip test\_list\_virtual\_interfaces * Use tempest.lib's base module for network clients * Split out Neutron routers client * Make 'device' optional in volume attach response schema * Test base\_url works with an unversioned endpoint * Switch hdp to cdh in the default list of Sahara plugins * common/compute.py: remove some useless code * Added support to verify extra-routes deletion * Fix typo in library doc title * Allow get\_tenant\_network() for non-primary creds * network: Fix subnet\_allocation extension check * Use urlunparse to reconstruct base\_url * Add OS-INHERIT of keystone v3 in api tempest * Actually run \`test\_skip\_attr\_\*\` unit tests * Making Keystone clients consistent * Fixes LiveBlockMigrationTestJSON.test\_iscsi\_volume bad call * Remove service\_client module * Use tempest.lib clients for bulk ops * Allow heat volume tests to use configured volume size * Updated from global requirements * Make network clients use rest\_client * Make identity clients use rest\_client * Make image clients use rest\_client * Make volume clients use rest\_client * Make database/telemetry clients use rest\_client * Unit tests: mock some time.sleep and time.time * Fix ambiguous method name * Add more detail info to EndpointNotFound * Make object/orchestration clients use rest\_client * Implement TIMEOUT\_SCALING\_FACTOR for tempest * Scenario/manager.py: always wait when delete a cinder vol 10.0.0 ------ * Security Groups multi-node scenario * Add LOG.info to boot from volume tests * Fix test\_get\_service\_by\_host\_name * Make compute keypairs\_client use rest\_client * Make data\_processing/baremetal use rest\_client * Add release notes for upcoming release * Properly handle failures during resource cleanup * Add documentation for the library interface * Split roles\_client for keystone v3 client * Tokens need user domain be created correctly * Make identity service client class name consistent * Cleanup useless aliasing in test\_container\_acl * Updated from global requirements * Add Identity v3 account configuration information * Allow user to be disabled * Instance ID was corrected * Use isinstance instead of type * Added processing /compute URL for getting list of versions * Allow image name to be empty for simple list test * remove largeops test * Skip test\_volume\_backed\_live\_migration() * Add reno to tempest * Use tempest.lib code in tempest * Deprecate Tempest stress tests * Migrate tempest-lib code into new lib dir * Skip Heat neutron tests * Use new oslo.config api to generate configuration-group help * Split trusts\_client from keystone V3 client * Split domains\_client from keystone V3 client * Minor test style update * Add microversion fixture to set microversion * Remove virtualenv management scripts from oslo-incubator * Remove zaqar tests from tempest * Python 3 deprecated the logger.warn method in favor of warning * Enable logging if log\_config\_append is None * Fix cleanup command * Fix cleanup cmd for assigning role to user * Remove redundant interfaces from v3 identity client * Remove some dead code * Remove test test\_create\_delete\_server\_group\_with\_multiple\_policies * Get NIC name by "ip -o link" * Use subnetpools\_client from tempest-lib * Use quotas/security\_groups clients from tempest-lib * Don't run testcase if interface\_attach is False * Remove MapR 4.0.1 tempest test * Skip the test\_get\_service\_by\_host\_name * skip overlimit tests when default quota set is -1 * Split users\_client from keystone V3 client * Separate projects client from identity V3 client * Fix cleanup for networking quota * Updated from global requirements * Revert "Remove setup\_credentials from orchestration tests" * Add cleanup for endpoint tests * Remove old code for Juno compatibility * Log more info when mke2fs fails on a guest * Use 2 network clients from tempest-lib * Update .mailmap * Fix KeyError in exception message of manager * Revert "Add new test "RebuildInstanceWithVolume"" * Add tests of port\_id in the attach interfaces nova API * Updated from global requirements * Updated .gitignore to work with ide * Update the home page * Make v3 identity\_client consistent * Emit warning when instances have ports not ACTIVE * Fixing typos in tempest/test.py * Fixed bug with url building * Add new test "RebuildInstanceWithVolume" * Add note about ironic config options * Make 2 functions in network\_client use \*\*kwargs * Only run dual-NIC/dual-stack IPv6 tests in gate * don't explicitly delete security group rules * Remove setup\_credentials from orchestration tests * Remove Keystone client variables that are not used anywhere * Add new exception InvalidAPIVersionRange for microversion * skip test\_list\_server\_filters\_by\_regex * Remove the port list that is not used * Separate microversion test case related unit tests * Identity V3: missing api coverage for 'Get Endpoint' method * Consolidate the ssh code * Refactoring of 'DataGenerator' classes for identity V2 and V3 * Add the group and role checks API in tempest * Fix stress tests job to use dynamic cred * Split endpoints-client out of keystone v2 identity client * Split out Neutron extensions client * Updated from global requirements * Scenarios: remove trivial wrapper methods * Remove redundant import from scenario tests * Replace legacy cred provider in heat api test * Replace exit() by sys.exit() * Revert "Cap Pip<8 due to pip bug" * Split DataGenerator class for v2 and v3 * Updated from global requirements * Separate base microversion client and compute client * Updated from global requirements * Fix logic in dump\_accounts * Cap Pip<8 due to pip bug * Add omit files for coverage * create\_server: pass arguments as part of kwargs * Split services client out of keystone v2 identity client * Fixing test\_create\_port\_in\_allowed\_allocation\_pool when 3 (or more) controllers * Use container/disk format settings in glance v1 test * Wait for servers termination when cleaning up telemetry test * Adds default domain name to dashboard login params * add client\_manager\_class param to BaseTestCase * Delete unnecessary internal method * Mark amazon image file config options deprecated * Split out Neutron security group rules client * Raise tox min version to avoid maximum recursion issue * Don't use non-existent method of Mock * Migrate tempest-account-generator to new cliff cli framework * Apply "POST/DELETE" rules to agents\_client * Add network wrapper method checks * Add different\_host test on multiple nodes * Add tests for Nova microversion v2.2 * Fix to show cli subcommand help message * Use 5 neutron clients from tempest-lib * Fix create\_test\_server for multiple create request * Make v1 list\_images use \*\*kwargs & doc string update * Fix tempest\_lib import order * Mark Javelin as deprecated * Split users\_client from keystone client v2 * Updated from global requirements * Migrate verify-tempest-config to new cliff cli framework * Skip security group tests without secgroup ext * API/compute: create all servers through the common wrappers * Make volume snapshots\_client to use \*\*kwargs * Updated from global requirements * Add tempest-list-plugins * Use floating\_ips\_client from tempest-lib * Update global-requirement for tempest-lib * Scenario: Skip TestServerMultinode when only 1 compute node * Add external gateway to dvr before scheduling with dvr\_snat agent * Redundent if Statement * Make v1 create\_image & update\_image use \*\*kwargs * Make volume volume\_client to use \*\*kwargs-part2 * Make volume volume\_client to use \*\*kwargs-part1 * Add support of schema versioning for microversion * remove log\_console\_of\_servers from get\_remote\_client * Trival: Remove unused logging import in some files * Rename compute images\_client to compute\_images\_client * Fix create/show/delete methods on subnetpools\_client * Fix the password input * Rename references to compute security group rules client * Scenario manager: don't access glance\_client if Glance is not available * Silences Javelin UT * Typos and consistency in configuration.rst * Fix grammatical mistake, Changed word from "an" to "a" in below file: * Correct wrong message in configuration doc * Wrong spelling of existing * Add a note about using a full path for test accounts option * Config: change cinder bootable flag to True * Change LOG.warn to LOG.warning * Misspelling in message * Fixing the deprecated library function * Misspelling in messages * Change validate message for test\_exceed\_max\_template\_size\_fails * Fix leak from tempest.conf in fake\_config fixture * Add support override OS\_TEST\_TIMEOUT for all environments * Wrong usage of "an" * Wrong usage of "a" * Fix server admin password usage * Safe ram/disk/vcpus values for test-made flavor * Refactoring of setting 'name' variable in tempest/common/compute.py * Add set microversion in base compute service client * Add logic to select the request microversion * Make 2 base\_snapshots\_client function use \*\*kwargs * Split out Neutron subnetpools client * Split out Neutron agents client * Make v3 update\_role use \*\*kwargs & doc string * Use six.StringIO/BytesIO instead of StringIO.StringIO * Make v2 for identity\_client functions use \*\*kwargs * Updated from global requirements * Keep py3.X compatibility for urllib.urlencode * [V3] Renaming region\_client into regions\_client * Fixing two tests when nova-net is enabled and run\_validation = True * Stop testing rebuild for ironic * [V3] Renaming policy\_client into policies\_client * Split out Neutron security groups client * Make 3 functions in network\_client use \*\*kwargs * Add stdout logging for cleanup CLI * Use servers\_client from tempest-lib * Use Tempest-lib's compute server\_groups\_client * Use Tempest-lib's compute security\_group\_rules\_client * Use floating\_ips\_client from tempest-lib * Update global-requirement for tempest-lib * Fix password not strong enough for identity test\_groups * Make v2,v3 create\_role use \*\*kwargs & doc string 9 - * Fix tempest cleanup command * Add description for stress in tempest cli * [V3 identity] Renaming service\_client into services\_client * Add stdout logging for stress * Enable test\_list\_security\_groups\_list\_all\_tenants\_filter with neutron * Remove the ec2 api tests from tempest * Make volume\_qos\_client use \*\*kwargs * Drop lock\_path section from config guide * Set a tempest lock\_path default to a tmpdir * Fix password not strong enough for cmd * Get rid of unused env variable * Update configuration options ssh-auth-strategy * Remove pre-check for test accounts file in credential factory * Split out roles client from keystone identity client * Make update\_agent in network\_client use \*\*kwargs * Fix telemetry tests to use v1 create image correctly * Fix password not strong enough failures * Make create\_image use \*\*kwargs * Fix and improve server personality tests * 'List volume type' comment has fixed * Clean up comment about dual-stack case handling * Fixed the comments of port security group test * Split out Neutron quotas client * Make server action methods[m-z] use \*\*kwargs * [sahara] adding new plugin versions to sahara tempest tests * Make add\_dhcp\_agent\_to\_network use \*\*kwargs * Make v3 update\_user\_password use \*\*kwargs * Replace assertEqual(None, \*) with assertIsNone in tests * Split out keystone tenant client * Make server action methods[f-l] use \*\*kwargs * Make server action methods[a-c] use \*\*kwargs * Set force\_tenant\_isolation on neutron quotas tests * Teach Tempest how to count vcpus * Add compute client base class for microversion support * Rename references to compute security groups client * Make volume backups\_client to use \*\*kwargs * Change docstring of compute client * Make live\_migrate\_server() use common method * Makes microversions config option clear about 'None' * Make v3 create\_trust use \*\*kwargs & doc string * Make update\_extra\_routes use \*\*kwargs & doc string * Making names of Cinder client classes consistent * Remove unused add\_sample() * Use Tempest-lib's compute volumes\_client * Fix T110 violation for database client * Remove pre-request-validation from v2 image\_client * Make v2 create\_user\_ec2\_credentials use \*\*kwargs * Make add\_router\_to\_l3\_agent use \*\*kwargs * Make volume\_types\_client use \*\*kwargs * Add unit test for test\_servers\_client-allfunctions * Add run\_stress to cliff-based cli framework * Make add\_member use \*\*kwargs and doc string update * Updated from global requirements * Fix some Type in docstrings * Split out Neutron metering label rules client * Make create\_floating\_ip use \*\*kwargs * Use the idempotent id and tooling from tempest-lib * Switch run\_tests.sh to use subunit-trace * Renaming Cinder admin client files * Remove unused agents's schema * Add script to use tempest-lib files in tempest * Use interfaces\_client from tempest-lib * Update global-requirement for tempest-lib * Rename add\_image\_member to create\_image\_member * Rename image\_client to images\_client * Add docstring for update\_image\_member * Make volume\_quotas\_client use \*\*kwargs * Add network quota exceeding negative test * Put py34 first in the env order of tox * Updated from global requirements * Migrate scenario tests ssh-auth-strategy * Changing directory structure for Cinder clients * Fix typo in tox.ini * Add tests to update group with few fields * Split out Neutron metering labels client * Remove unused json\_request() * Rename private methods of glance\_http * test\_port\_security\_macspoofing\_port: Fix an inversed check * [V3] Rename get\_policy to show\_policy * Make argument params of list methods consistent * Add keystone v3 user negative case test authentication with disabed user * [V3] Rename get\_region to show\_region * [V3] Rename get\_credential to show\_credential * Fix some Typo in docstrings * Add the base microversions test part * Make argument params of list methods consistent * Remove "s" from imange clients' method * Make image\_client use \*\*kwargs * Change checks for test\_server test * Fix race condition when changing passwords for admin tests * Fix some inconsistency in docstrings * Change multi-backend configuration to a list * Use versions\_client, versions from tempest-lib * Use tenant\_usages\_client from tempest-lib * Use tenant\_networks\_client from tempest-lib * Use snapshots\_client from tempest-lib * Use Tempest-lib's compute services\_client * Use Tempest-lib's compute security\_groups\_client * Drop fixed\_network dependency from CONF * Use V3 params when initializing Keystone V3 clients * Use Tempest-lib's compute security\_group\_default\_rule * Use Tempest-lib's compute quotas\_client * Use Tempest-lib's compute quota\_classes\_client * Fix T110 violations for image\_client * Switch run\_tempest.sh to use subunit-trace * Change tests to adapt alarm spliting * Adds test for mac\_spoofing * Split out Neutron floating IPs client * Scenarios: cleanup class scope variable usage * Change comments and description for minimum\_basic test 8 - * Removing initialization of clients that are not used * Use Tempest-lib's compute networks\_client * Use Tempest-lib's compute migrations\_client * Use Tempest-lib's compute limits\_client * Remove preprov provider dependencies from CONF * Use Tempest-lib's compute instance\_usage\_audit\_logs * Use Tempest-lib's compute images\_client * Add T111 hacking rule for consistent DELETE method * [V3] Rename get\_group to show\_group * Refactor T110 rule * Add keystone v3 user negative cases * Skip test that rely on creds by role * Updated from global requirements * Set default value for tempest log file * Fix T110 violations for servers\_client * [V3] Make groups\_client use \*\*kwargs * [V3] Separating groups\_client from identity\_client * Move identity wait until after password is updated * Fix H404/405 violations for remaining * Unbreak test\_created\_router\_interface (heat) * Fix H404/405 violations for thirdparty and stress * Fix H404/405 violations for unit tests * Fix H404/405 violations for common code * Fix H404/405 violations for api tests(3/3) * Fix H404/405 violations for api tests(2/3) * Fix H404/405 violations for api tests(1/3) * Move get\_user\_by\_username to common part * Move get\_tenant\_by\_name to common part * Add negative test to test\_domains\_negative * Fix H404/405 violations for tools * Fix H404/405 violations for service clients * Fix skip\_checks in live\_migration * Move get\_server\_or\_ip method to common class * Fix H404/405 violations for scenario tests * Remove novaclient/neutronclient description * Remove server\_groups\_client from ignore\_list * [V3] Make service\_client use \*\*kwargs * Add installation procedure of cookiecutter to doc * Updated from global requirements * Fix AttributeError in multinode jobs * Rename get\_server\_group to show\_server\_group * Add hacking rule for "GET /resources" * Modify baremetal\_basic\_ops test * make schedule\_to\_every\_node use real scheduler hint * add 'create domain with name length > 64' * [V3] Make region\_client use \*\*kwargs * Factor up config dependent credential classes * change some sentences to help understand * add test to ensure cloud is using minimum number of nodes * [V3] Make policy\_client use \*\*kwargs * [V3] Make endpoints\_client use \*\*kwargs * [V3] Make credentials\_client use \*\*kwargs * Rename references to compute floating IPs client * Remove unused config option image\_alt\_ssh\_user * Use skip\_checks for live-migration feature in test * Fix race condition when changing passwords * Trivial fix of doc string * Fix TypeError exception in test\_server\_actions setup * Rename list\_users\_for\_tenant to list\_tenant\_users * Apply a naming rule of GET to v2 keystone clients * Remove unused has\_admin\_extensions() * Modify scenario tests * Use choices kwarg when defining string options with defined choices * Remove BaseComputeTest and \_api\_version * Provide better message when no IPv4 addresses found * Split a server test into two distinct tests * Refactor volume\_boot\_pattern test * Add negative test: create domain with empty name * Add cleanup to cliff-based cli framework * Allow empty directories for tempest init * Add assertion after domain deletion * Updated from global requirements * Split out Neutron ports client * Add documentation for service catalog format * Prepare cred\_client for migration * Fix unit tests for migration * Remove resource\_setup() from test\_live\_migration * Switch to using pbr version for cli version string 7 - * test\_snapshot\_pattern: cleanup class scope variable usage * Remove checks for ipv6 utilities before use * Add plugin cookiecutter in documentation * Drop admin\_role CONF dependency from cred provider * Remove dependency from credentials domain CONF * Remove CredentialProvider deps to CONF * Stop validating pre-provisioned credentials * Removed shared instances for live-migration * plugin.rst: Fix a typo in an example code fragment * skip compute alt tenant tests if no project\_id in url * Fix DEFAULT\_CONFIG\_DIR * Remove skip decorator for volume scenario test * Make docstring of servers consistent * test\_network\_advanced\_server\_ops: cleanup class scope variable usage * test\_minimum\_basic: cleanup class scope variable usage * Pass the status 'ACTIVE' for getting a target port * test\_shelve\_instance: cleanup class scope variable usage * test\_stamp\_pattern: cleanup class scope variable usage * Use Tempest-lib's compute hypervisor\_client * Use Tempest-lib's compute hosts\_client * Use Tempest-lib's compute floating\_ips\_bulk\_client * Use Tempest-lib's compute floating\_ip\_pools\_client * Use Tempest-lib's compute flavors\_client * Fixing checking bytecode * Use Tempest-lib's compute fixed\_ips\_client * Remove migrated service clients from test\_service\_client * Add compute personality feature config * Use Tempest-lib's compute extensions\_client * Use Tempest-lib's compute certificates\_client * Use Tempest-lib's compute baremetal\_nodes\_client * Use Tempest-lib's availability\_zone\_client * Use Tempest-lib's aggregate client * Split out Neutron subnets client * Bump minimum tempest-lib version * Moves an incorrectly marked negative test * Set correct ACL for Swift staticweb tests * Add failure ports to error message * Make services\_client use \*\*kwargs * Log output from ping command * Fix value explanation in get\_creds\_by\_roles * Add unit test for create\_server,list\_addresses * Handle exceptions from plugins gracefully * Rename accounts to preprovisioned accounts * Add unit test for init.py->generate\_sample\_config() * Fix return value of reserve\_fixed\_ip client method * Use tempest-lib's agents\_client * Fix fixed-ip schema for empty response body * Updated from global requirements * Rename isolated creds to dynamic creds * Add json schema for disable service * Fix tempest-account-generator command roles issue * Add container and disk format parameters * Added Test Cases for ImagesClient * Added Test Case for InterfacesClient * Add unit tests for show\_server and list\_servers * Remove "extensions" from volumes\_extensions\_client * Add unit tests for snapshots\_client * Pass mount\_path through to ssh\_client.mount in scenario manager * Remove unused \_ssh\_to\_server from test\_stamp\_pattern * Remove "extensions" from snapshots\_extensions\_client * Make create\_server\_group use \*\*kwargs * Split out Neutron networks client * Refactor tempest scenarios tests * Check cinder api version in compute tests * Fix gate-tempest-dsvm orchestration.stacks.test\_volumes failure * Add test for config drive extension * Replacing data\_processing with data-processing * Add volume backed live migration test * Add unit test for flavors\_client * Fix spelling typo in warning message * Follow up patch on missed IdentityV2 methods * Subnet pools extension api added * Updated from global requirements * Fix init command * Bring back Python 2.6 compat (don"t use dict comprehension) * Negative test cases added in Telemetry * Rename SwiftScenarioTest to ObjectStorageScenarioTest * Full response for missed IdentityV2Client methods * create\_router cannot take enable\_snat=False * Metadata definition namespaces api added * Add resource cleanup in volume snapshots tests * Enhance unit test coverage: availability\_zone\_client * Rename \`index\` test into list * Add unit test for floating\_ips\_client * Make create\_server use \*\*kwargs * Rename references to compute networks client * Create base class for Tempest Network clients * Use tempest-lib's fake\_auth\_provider on services * Fixing a typo in "TODO" message * Move add/remove fixed ip action to servers\_client * Change tenant usage tests to wait until retrive valid response * Test case updated with alarm history api * Add tests for getting nova api version details * Remove \_stop\_instances hack in test\_volume\_boot\_pattern * Add unit tests for volumes\_extensions\_client * Add test for volume snapshot in compute api * Fix missing value types for log message * Add unit tests for security\_group\_rules\_client * Remove nova v2 API comments * Added Identity API tests for user ec2 credentials * Fix way to create login\_url in dashboard test * Return complete response from servers\_client * Add a simple test for port security vs security group * Skip test\_router\_rescheduling if l3 scheduler extension is not enabled * Fix the condition of nova cert tests * Switch using token\_client to the recommended * Fix test\_port\_list\_filter\_by\_ip with test\_accounts * Deprecate credential config options * Enhance unit test coverage: agents\_client * Add unit tests for security group default rules * Remove list\_security\_group\_rules wrapper method * Updated from global requirements * Split the operator and reseller users in the account generator * Add unit test for hypervisor\_client * Add possibility to skip test\_subnet\_details if DHCP client not available * Use proper method for nova disable service unit test * Fix typos in scenario tests, etc * Add unit tests for floating\_ips\_bulk\_client * Return complete resp from volumes\_extensions\_client * Give access to CredClient in tests * Switch using token\_client to the latest for gate * Only add roles for enabled services * Add unit test for server\_groups\_client * Use Cinder API V2 if V1 is disabled * Add docs section on config options to reviewers guide * Fix wrong dict reference for show\_network * Unskip baremetal api tests * Remove unused parameter 'tenant\_id' * Added Test Case for HostsClient * Added Test Case for BaremetalNodesClient 6 - * Add unit tests for migrations\_client * Add unit test for floating\_ip\_pools\_client * Split snapshot test in test\_volume\_quotas\_negative * Deduplicate cert, audit\_log and tenant\_net client * Add unit tests for security\_groups\_client * Remove code duplication in delete methods * Added Test Cases for FixedIPsClient * Ensure test\_list\_show\_tenant\_networks is isolated * Deduplicate client unit tests * Added test\_user\_update\_own\_password tests for Identity v2, v3 * Added test\_list\_tenants non-admin test for v2, v3 api/identity * Cleanup API version schema * De-duplicate test class names * Make update\_server use \*\*kwargs * Remove assertion checking rescoped issued\_at * Add test for Nova list-versions API * Remove Nova v3 version API schema * Add hacking check for testtools.skip * Cleanup tempest docs a bit * Fix checks for X-Trans-Id in DLO-related storage tests * Migrate volume tests to the ssh-auth-strategy * remove service-type from neutron extension list * Fix the condition of some snapshot tests * Do not pass wait\_until to create\_server() * Add docstring for tenant\_network * Add unit tests for tenant\_networks\_client * Switch remaining json import to oslo\_serialization * test\_volume\_boot\_pattern: re-use create\_floating\_ip from base class * Remove too\_slow\_to\_test flag * Add "server" to action methods - part 2 * Fix identity new endpoint\_type options for old users * Skip ebs scenario test due to bug 1489581 * Full response for VolumesClient methods * Return complete response from remaining service client * Encap netaddr-0.7.16 * Add mac info to test\_list\_virtual\_interfaces * Return correct domains in cleanup service * Remove meaningless braces * Skip router rescheduling test * Fix typo in baremetal\_group & AuthGroup of config.py * Full response for database flavor client methods * Fix response of database Limits & Versions client * Add initial unit test for tempest plugins * Update logic to detect DVR in rescheduling test * add waiting for snapshot available status * Fix the mix use of name and uuid in NetworksNegativeTestJSON * Disable cinder backup import/export test * Remove unused init file and directory * Fix tests updating server by name rather than id * Fix typo in the create\_isolated\_networks help in config.py * Added CONF.validation entry security\_method * Add unit tests for instance\_usage\_audit\_log\_client * Add unit tests for certificates\_client * Add unit test for availability\_zone\_client * Add unit tests for tenant\_usages\_client * Add flag for volume clone tests * Fix typo in tempest coding guide * Return complete response from compute images\_client * Return complete response from limits,migration client * Return complete response from compute quotas client * Properly skip nova tests if nova isn't available * remove test\_update\_host\_with\_extra\_param * Use the oslo.config sphinx module to generate sample * Fixed few typos * Add shelve/unshelve volume backed instance test * Full response for missed Identity V3 Client * Make volumes\_extensions\_client use \*\*kwargs * Add plugin docs section on configuration options * Update the Tempest README for new run workflow * Return complete response from compute networks\_client * Return complete resp from security\_groups\_client * Return complete resp from sec grp default rule client * Return complete response from tenant\_usages\_client * Return complete response from tenant\_networks\_client * Return complete response from services\_client * Return complete resp from server\_groups\_client * Return complete resp from security\_group\_rules\_client * Return complete response from interfaces\_client * Return complete response from instance usage client * Return complete response from flavors\_client part-2 * Return complete response from flavors\_client part-1 * Return complete response from compute/aggregates\_client * non-admin user only able to download Active image * Update tox.ini for sphinx build instructions * Fix misspelling in the "README.rst" * Fix typo in tempest configuration guide * Fix name "Quantum" to "Neutron" in comment * Full response for Volume SnapshotsClient methods * Router should be disabled to be upgraded to DVR * Marking the login\_url option as "deprecated\_for\_removal" * add 'boot from ebs' scenario test * Full response for Database Limits & Version Client * Remove some dead code * scenario/test\_minimum\_basic: relax a MatchesDictExceptForKeys check * Add unit tests for services\_client * Fixed typo * Full response for VolumeHostsClient methods * Full response for Volume QosClient methods * Full response for Volume BackupsClient methods * Fix typo in tempest configuration page * Full response for DataProcessingClient methods * Full response for Orchestration client methods * Return complete response from hypervisor\_client * Return complete response from floating\_ips\_client * Return complete response from hosts\_client * Return complete response from floating\_ips\_bulk\_client * Return complete response from compute certificates\_client * Return complete response from compute extensions\_client * Add unit tests for network\_client * Add unit test for quotas\_client * Make live\_migrate\_server use \*\*kwargs * Full response from v2 ImageClient methods * Migrate telemetry tests to the ssh-auth-strategy * Return complete response from compute/keypairs\_client * Return complete response from compute/agent\_client * Adding timeout to verify\_metadata method in test\_server\_basicops * Set validatable False by default * Fix typo in Tempest Coding Guide * Updated from global requirements * Full response for Volume ExtensionsClient methods * Full response for v3 ServiceClient methods * Full response for VolumeServicesClient methods * Full response for Volume AvailabilityZoneClient methods * Full response for VolumeTypesClient methods * Full response for v3 PolicyClient methods * Full response for v3 RegionClient methods * Remove redundant response schema check * Make attach\_volume use \*\*kwargs * Move wait\_for\_server\_termination from servers\_client * Add docstrings for parameter translation * Add "server" to action methods * allow non '/' root for dashboard * Full response for v3 IdentityClient methods * Full response for v3 EndpointsClient methods * Enhance IPv6 scenario - dual network testing * Removed unnecessary commented code * Full response for v3 CredentialsClient methods * Bake in oslo sample config generation to sphinx build * Add extension alias for test\_list\_show\_extensions * Fixed single quoted docstring * Fixed unformatted message string * Move test\_waiters to match directory structure * Handle 'error\_restoring' status in wait\_for\_volume\_status * Fix constants of config sections names in verify\_\*\_api\_versions * Cleaned up class variable scope issue * Fixed invalid error message * Remove unused ignore\_error * Adds test for router rescheduling * Full response for v2 IdentityClient methods * Fixing tests when run\_validation is set to true * Fix a spelling typo in tempest/thirdparty/boto/test.py * Add unit test for quota\_classes\_client * Add unit test for keypairs\_client * Add unit tests for extensions\_client * Enhance IPv6 scenario - Ping default GW * Return complete response from floating\_ip\_pools\_client * Full response for v1 ImageClient methods * Fix scenario test fails when port\_vnic\_type is set * increase failure information for ip\_regex test * Added endpoint types for intialization of different IdentityClients * Fix the way to get tempest.conf in README.rst * Fixed Typos * Add plugin interface documentation * Discover the correct default config dir * Stop gating on sample generation check * Include sample configuration in dev docs * Updated from global requirements * Revert "Fix scenario test fails when port\_vnic\_type is set" * Logging migrations for particular server if migration failed * Return complete response from compute fixed\_ips\_client * Move common functionality to base class * Return complete response from compute baremetal client * Return complete response from availability\_zone\_client * Make security\_group\_rules\_client use \*\*kwargs * Fix Typos * Add identity v2 endpoint operations * Fix sample config yet again * Add plugin interface for extending sample config generation * Ensure server unlocking after test\_lock\_unlock\_server * Add unit test for aggregates\_client * Make security\_groups\_client use \*\*kwargs * Make security\_group\_default\_rules\_client use kwargs * Make create\_image use \*\*kwargs * Make create\_flavor use \*\*kwargs * Make keypairs\_client use \*\*kwargs * Make set\_flavor\_extra\_spec use \*\*kwargs * Skip test\_check\_nova\_notification\_event\_and\_meter * Avoid default quota limit of 10 subnets * Remove openstack.common package * Use relative path in data\_files in setup.cfg * Rename service client classes which include "Ip" * Allow v3 identity to work without the admin domain name * Add unit test for method show\_limits * Separate security\_group\_rules\_client * Updated from global requirements * Add unit test for method list\_keypairs * Add unit tests for create/update/delete\_agent * Add documentation to HACKING.rst describing idempotent\_id * Fix two typos on tempest documentation * Fix scenario test fails when port\_vnic\_type is set * Update smaple config for new oslo.config release * Add tempest init command to tempest cli * Activate site-packages for tempest plugins * Make quotas\_client use \*\*kwargs * Make interfaces\_client use \*\*kwargs * Fixing test\_volume\_boot\_pattern when use\_floatingip\_for\_ssh=false * Add unit test for method list\_aggregates * Updated from global requirements * Fix Neutron cleanup * Add 'allow\_duplicate\_networks' to compute-feature-enabled options * Make fixed\_ips\_client use \*\*kwargs * Use image minDisk as volume size when necessary * Adds testcase for volume backup-export and import * Avoid weird typecasting * Remove extra space in docstring * Reuse mocked body value between tests * More cleanup/refactoring of neutron client * Fix list\_migration response schema for None values * Ensure we close the file accounts file after reading * Updated from global requirements * Remove redundant attach/detach volume methods from test\_minimum\_basic * Ensure a role is assigned to created users with v3 auth * Migrate compute tests to the ssh-auth-strategy * Stop test execution in case of an plugin error * Fix error in loader.discover() call * Make aggregates\_client use \*\*kwargs * Add unit test for agents\_client * Switch all uses of json to oslo\_serialization * Rename test.plugins to test\_plugins * Fix non-admin compute quota issue * Make argument params of list methods consistent * Use self.client in test\_server\_actions * Remove wait\_for\_server\_status from servers\_client * Separate floating\_ips\_bluk\_client * Separate floating\_ip\_pools\_client * Updated from global requirements * network\_client should support multi routes * Fix mock==1.1.0 break test\_raw\_request\_chunked * Sync venv scripts from oslo-incubator * Make the arguments of resource id consistent * Provide a full path top level to test discovery * Remove wait\_for\_volume\_status from compute client * Remove unused is\_enabled() * Remove wait\_for\_image\_status from compute client * Move wait\_for\_interface\_status from service client * Create test for private image * test\_server\_basic\_ops: Test metadata service * Separate server\_groups\_client from servers\_client * Add plugin interface for appending to tempest config * Add basic external test plugin support to tempest * Expanded assertion in test\_create\_token for keystone v2, v3 * Wait server to be terminated in VolumesV2NegativeTest * Fixing test\_port\_list\_filter\_by\_ip in case of several DHCP agents * Adding negative test cases for ports * Remove unnecesarry code in PortsAdminExtendedAttrsTest * Use the prefix-embedded rand\_name method * Adding negative test cases for subnets * Apply a naming rule of GET to show\_server method * Get rid of duplicated creation of security groups in test\_volume\_boot\_pattern * Remove "JSON" from sevice clients' names * Add cliff based common cli entrypoint * Fix useless usage of Linux utils * Add ":" to docstring of CredentialProvider * Remove str() calls from compute clients * Apply a naming rule of GET to compute clients(v\*) * Apply a naming rule of GET to compute clients(t\*) * Apply a naming rule of GET to secgroup clients * Use random password generator for IsolatedCreds * Updated from global requirements * update ceilometermiddleware sample target * Removing unused functions parameters in test.py * Removing unnecessary comments * Unbreak the world! * use services decorator to enable telemetry test * add initial check for nova event in ceilometer * Re-factor neutron client for 'list' methods * Neutron service client should not trim API response * Re-factor neutron client for 'delete' methods * Re-factor neutron client for 'show' methods 5 - * Re-factor neutron client for 'update' methods * Re-factor neutron client for 'create' methods * Add resource prefix string * Modify RemoteClient to use ssh validation config parameters * Add tests for Cinder volume list pagination * Apply a naming rule of GET to compute clients([ik]) * Updated from global requirements * Added comment how to get list of network extensions in tempest.conf * Support for ipv6 in compute response JSON schema * Fix the description in tempest.conf.sample * Delete resources when dvr test skipped * Updated from global requirements * Apply a naming rule of GET to compute clients(q\*) * Add compute\_feature\_enabled.attach\_encrypted\_volume config option * Set the correct API version for the V2 identity client * Block additionalProperties on Nova API tests * Provide a config option to customize remote shell command * Add create\_test\_server function * Fix failed to access pip server when run behind proxy * Fix no attribute 'is\_dvr\_router' error in test\_l3\_agent\_scheduler * Add a rule for blocking "-" from rand\_name call * re-enable glance notification tests * Made a several-seconds checking&waiting for object to be deleted * Remove key 'absolute' from limits client * More javelin unit tests * Apply a naming rule of GET to compute clients(n\*) * Separate quota\_classes client from quotas client * Remove error handling of list\_networks * Apply a naming rule of GET to compute clients(l\*) * Apply a naming rule of GET to compute clients(h\*) * Remove advanced services tests * Updated from global requirements * Apply other "get" rule of GET to image v2 client * Neutron: Remove get\_namespace from API extensions * Fix spell error in configuration.rst * More Javelin Unit tests * Add tenant network creation to account creation script * Fix L3AgentSchedulerTestJSON failure for dvr routers * Fix fail test\_iscsi\_volume * Apply a naming rule of GET to compute clients(f\*) * Apply other "get" rule of GET to image v1 client * Make the names of image\_member methods consistent * Provides a sample resourcefile for Javelin * Unskip test skipped because of closed bugs * Fix several bugs in verify\_tempest\_config * Added documentation to tempest-account-generator * Fixing some tests for DHCP IPv6 * Check for NotFound exception in setUp() function * Fix condition of Swift SLO API tests run * Add test\_port\_list\_filter\_by\_ip test * Add support for image deactivate and reactivate * Check domain's enabled attribute strictly * Deleting ports before deletion of routers in resource\_cleanup * Raise baremetal.unprovision\_timeout default to 300 sec * Do not consider subnets of shared public nets * Add config option to disable network isolation * Fix sample conf file based on new oslo.log release * Log instance console when ssh fails in EC2 test * Verify exact match for volume metadata update * Check only for added/deleted metadata entries * Remove extra return value from scenario call to show\_router * Verbose waiting for status in neutron FWaaS * Remove skip for bug 1439371 on test\_volume\_boot\_pattern * Pass wait\_until arg in create\_test\_server call * Fix Nova API misimplementation for security groups * Replace "hardcode" in scenario tests * Apply a list\_image\_members rule of GET to image client * Apply a show\_image rule of GET to clients * More Javelin Unit tests * Fix typo in skip\_checks function of BaseTestCase class * Apply a list\_images rule of GET to image client * Add domains negative test * Added test of default domain in Keystone * Add test caller to scenario manager ssh connection failure for tracking * Fixing broken Heat tests for Swift resources * isolated creadentials are not cleaned up * Remove heat-slow tox entry * Fix typo in cleanup\_service class name * Fixing wrong cleanup order in test "test\_rebuild\_server" * Ensure scenario utils creds are cleaned up * Tool for generation user accounts from spec * Make sure filtered\_by\_ip list servers test uses correct ip filter * Add passenv option to tox.ini * Only create a TokenClient if api\_v2 is enabled * Fix doc issue of lock test accounts * Extend credentials to support roles * Move identity\_version to class level * Don't create network resources for scenario utils * Add py34 to readme, pkg metadata, and envlist * Image properties in scenario tests * To test bootable flag in a cinder volume * Fix last unit tests on py34 * Fix raise syntax in test.py for python3 compat * Use six.moves.builtins to mock open in unit tests * Introduce creds\_provider in test.py * Extend get\_client\_manager to support roles * Add new config option to globally trigger resource validation * Add keystone v2.0 and v3 api discovery checks * Adds unit test for Javelin * Check IPv6 mupltiprefix feature * Add cleanup after creating keypair * Updated from global requirements * Move remaining schema files into v2\_1 directory * Switch use of hashing strings to unicode in accounts * Fix tenant isolation and unit tests with py3 * Switch all uses of iteritems to use six instead * Fix comprehension syntax error with python3 * Switch all uses of urllib and HTMLParser to import from six * Fix verify\_tempest\_config to not leak a tenant * Remove common/ssh.py due to migration to tempest\_lib * Instance ID was corrected * Remove CLI testing once and for all * Fix server creation n test\_large\_ops * Drop use of 'oslo' namespace package * Define validation\_resources function for ssh validation * Drop v2 and v3 tokens clients * Respect configured cred provider in scenario utils * Delete server after resize test * Remove unused wrapper methods * Trim object storage smoke tests to a small set * Remove unnecessary force\_tenant\_isolation * Remove version string from the setup.cfg * Test live migrate on a paused instance * add image smoke tags * trim compute smoke tag * Fix typo in scenario manager * Fix logging.conf sample * Drop auth and corresponding unit tests * remove qos tests from volume smoke tag * trim network smoke tests * remove smoke tag from admin tests * remove smoke tag from negative tests * not all scenario tests should be in smoke 4 - * Use addCleanup in test\_rebuild\_server * Rework get\_network\_from\_name() function * Remove unused methods * Initial class creds creation in test base class * remove gate tag (part 3) * remove gate tag (part 2) * remove gate tag (part 1) * Apply a naming rule of GET to compute clients(a-e) * Apply a naming rule of GET to telemetry client * Cleanup some details about the accounts files * Wait for complete deletion of volume snapshot before deleting volume * Extend PATH and set -o pipefail in linux ssh * Add OS\_TOP\_LEVEL to testr conf * Switch all uses of httplib to import from six * Switch all uses of ConfigParser to import from six * Switch all uses of urlparse to import from six * Switch all uses of StringIO to use it from six * Fixing spelling errors in messages when tests are skipped * remove swift cli tests * Direct ssh bash output to /dev/null * Fix non-locking test accounts doc section * Add a paragraph on the use of tempest\_roles option * Add section to config guide for setting up services * Add config guide section on service feature enabled sections * Add logging of account allocation to accounts provider * Add configuration guide sections for more required options * Start a tempest config guide networking section * Fix test accounts unit test race with networks * Revert "Test creation of server attached to created port" * Honour ssh\_connect\_method in test\_baremetal\_server\_ops * Cleanup fixed\_name logic in test\_list\_server\_filters * Update linux client to support basic MTU testing * Add version and min\_version to version API schema * Add network support to the accounts providers * Improve logging in fixed\_networks * FIx Accounts with identity v3 and v2 data in yaml * Add new multi ssh configuration options * Refactor exception handling * Rename an internal method to \_helper\_list() * Apply a naming rule of GET to messaging client * Enable tempest install to setup a config dir * Adding port\_admin\_state\_change option to config * Fix external connectivity check for dualstack * Test creation of server attached to created port * Add router to resources section of yaml output * Make this script work with keystone v2 and v3 * Rearrange hypervisors response schemas into one file * Embed network resoruces and credentials in TestResources * Make L3AgentSchedulerTestJSON DVR-aware * Prevent failures when running as non-admin * Use network from get\_tenant\_network in scenario * Use the catalog to discover available services instead of get\_service * Adjust registration of ami image in boto tests * Added cleanup for tenant quotas * Remove EC2 test case that incompatible with Amazon * Apply a naming rule of GET to orchestration client * Apply a naming rule of GET to volume client * Skip test\_volume\_boot\_pattern until bug 1439371 is fixed * Fix fixed\_network bug 1439634 * Fix fixed\_network bug 1439634 - ipsec issue * Rename test\_live\_block\_migration to test\_live\_migration * L3 Agent Scheduler testcase cleanups * Allow SSH instance with username and password in scenario cases * Remove Sahara CLI tests (migrated) * Limit tempest.config's use of cfg.CONF * test\_accounts\_file can be None * Print an error message and fail if network could not be found by name * Allows Javelin to specify Swift role * Only use accounts.yaml with locking provider * Allow Javelin to create volumes without servers * Remove hyphen from rand\_name calls in scenario tests * Allows selection of floating IP pool in Javelin * Skip test\_security\_groups\_basic\_ops for vnic\_type=direct of macvtap * Use the configured version of identity in stress * Add API tests for Neutron DVR extension * Handle fixed\_network edge cases gracefully * Clarify how to resolve a uuid collision * Update description of Enable block\_migrate\_cinder\_iscsi * Change default cirros ssh user * Split resource\_setup for network tests * Remove hyphen from rand\_name calls in identity tests * Remove hyphen from rand\_name calls in compute tests * Remove hyphen from rand\_name calls in thirdparty tests * Start instances using fixed network when possible * Remove hyphen from rand\_name calls in baremetal tests * Remove hyphen from rand\_name calls in image tests * Remove hyphen from rand\_name calls in volume tests * Non-admin token tests for Keystone API * Pass identity\_version into get\_credentials * Support identity\_version in credential provider wrappers * Refactor floatin\_ips V2.1 API response schema * Drop any dependency from config in test\_credentials * Refactor fixed\_ips API response schema * Rearrange aggregates response schema into one file * 1373513 has been fixed, remove skip * Adding sudo to command \_renew\_lease\_dhclient in remote\_client * Remove invalid use of kwargs from image clients * Remove remaining CONF references in service\_client * Use oslo concurrency api to get lock path * Updated from global requirements * Support v3 in credential providers and subclasses * Remove ceilometerclient CLI tests * Add test for the various config cases of is\_admin\_available() * Arrange quotas response schema into one file * Rearrange keypairs response schema into one file * Fix interfaces response schema * Rearrange certificates response schema into one file * Rename compute response schema dir to v2\_1 * Respect configured credential provider everywhere * Remove unused dependency to config module * Fixing spelling error in the message when test is skipped * Remove redundant calls to clear\_isolated\_creds * Make check\_uuid executable * Add 'instance\_uuid' in show baremetal schema * Add 'disabled\_reason' in hypervisor schema * Allow sress test runner to skip based on available services * Fixes a typo and adds proper captilization * javelin: don't destroy floating IP resource * Remove last references to python-keystoneclient * Add additional checks for attributes * Adding common header attributes in image header schema * Add 'fault' attributes in server schema * Add 'images\_links' attribute in image list schema * Add 'servers\_links' attribute in server list schema * javelin: add missing network\_name in destroy\_servers loop * javelin: fix destroy\_servers * Restore "Add scenario 'test\_preserve\_preexisting\_port'" * Fix NoSuchOptError when using use\_floatingip\_for\_ssh * Distinguish between luks and cryptsetup volume types * javelin: implement floating-ip support * Skip test\_connectivity\_between\_vms\_on\_different\_networks for baremetal * Deprecates 'format' in Javelin image description * Revert "Add scenario 'test\_preserve\_preexisting\_port'" * Remove python-ironicclient from requirements * remove redundant negative flavor admin tests * Remove neutron CLI tests * Cleanup the tempest readme * Skip boto tests when auth\_version is v3 * Testcase to create domain without description * Create test nodes for test\_baremetal\_nodes * Create a new project for trusts tests * Allow full v3 authentication * Support SSL and trace params in get\_credentials * Add scenario 'test\_preserve\_preexisting\_port' * fix import grouping * Add section to the config guide on lock\_path * Update all Oslo module use * Decouple Sahara templates test from vanilla plugin * Add support to the services decorator for trove * Make is\_admin\_available not call for a token * Stop using [orchestration] image\_ref * Revert "disable neutron network tests that fail too much" * Replace hardcoded volume size by created volume size * Remove last dependency from keystone client * Fix image/server schema for 'OS-DCF:diskConfig' * Add extended server attributes in list detail schema * Fix hypervisor schema for hypervisor status extension * Keep up with changes in oslo-config-generator * Make scenario tests requiring admin skip if no admin creds * Split resource\_setup for compute tests * Split resource\_setup for scenario tests * Add check on role availability before request creds by roles * Remove migrated utils code * Copy the default params dict to avoid race * Fix: don't hardcode admin role name, use config option instead * Update auth section of the configuration guide * Use ssl conf settings for raw verify\_tempest\_config requests * Remove identity CLI tests * Remove orchestration scenarios * fix target location for ceilometermiddleware data * Fix unpack error in test\_list\_servers\_detailed\_filter\_by\_image * Switch swift tests to use cred\_provider with roles * Add support for roles to credentials providers * Remove tempest-lib exc Frobidden to Unauthorized translation * Fix server schema for OS-EXT-IPS extension attributes * Add server groups quota attributes in limit schema * Add 'flavors\_links' attribute in flavor list schema * Fix FIP bulk schema for 'fixed\_ip' * Adding 'security\_groups' attributes in server schema * Update test to fail instead of skip if floating pool is already allocated * Skip CLI tests is identity v2 is not available * Skip test\_subnet\_details for baremetal * Replaces hardcoded volume size by configured value * Testcase to create domain with disable status * Remove CONF values from images clients * Remove CONF reference from servers client * Move identity v2 tests to their own folders * Check Floating ip status before load balancer check * Fix check for FloatingIP status before check connection * Fix isolated cred cleanup for cli tests * Allow time for ipv6 auto addresses to get assigned * Split resource\_setup for thirdparty tests * Fix ICMP code/type in security group rule test * Editing comment in test "test\_trust\_expire" * test\_neutron\_resources slow tag to gate * Add a test for network connectivity between two tenant networks * Log the names of the other isolated\_creds on starvation * Fix response schema for compute aggregate API * Add UUIDs to all tempest tests and gate check * Add UUIDs to tests with tools and checks * Remove python-novaclient from requirements.txt * Prepare token clients for migration to tempest-lib * Fix and re-enable H105 * Move to hacking 0.10 * Updated from global requirements * Revert "Change messaging clients to return one value and update tests" * Fix missing variable in test accounts warning log * Set the boto ca\_certificates\_file from tempest config * Add tempest config guide doc * Fix AttributeError on testtools missing MismatchError * Make tempest's python 2.6 policy clear * Fix schema of list hypervisors API * Add response parameters for quota\_server\_group ext * refactor save\_state code * add support for ceilometermiddleware data * Simplify method deserialize\_list of network\_client * Floating IP test enhancements * Fix missing exception catch in is\_admin\_available() * Remove unused NotFound raise * Split resource\_setup for compute volumes tests * Split resource\_setup for volume tests * Adds IPV6 bulk ops tests for network/subnet/ports * Fix baremetal node property keys * Change network/interfaces clients to return one value and update tests * Change messaging clients to return one value and update tests * Change database clients to return one value and update tests * Handle errors listing flavors and images in scenario utils * Allow tenant isolation by default * Split resource\_setup for limit tests * Split resource\_setup for telemetry tests * Split resource\_setup for identity tests * Remove CONF values from Token clients * Remove CONF values from identity clients * Split resource\_setup for object\_storage tests * Omitting microseconds when creating a trust * Fix compute baremetal\_nodes API schema * Change servers client to return one value and update tests * Fix left-over client calls in stress test to receive one value * Update compute base test to split up resource\_setup * clear dict post cleanup in clear\_isolated\_creds * Finish cleanup CONF from auth * Skip new instance port admin state test for ironic * Add 'type' parameter to compute image schema * Change two-value client methods to use ResponseBodyData * Cinder: Wait for volume to be 'available' before attaching * Split resource\_setup for image tests * Adds "retype" to "volume/admin/test\_volume\_types.py" * Fix some typos in log and test * Add the description of hacking rule T107 * Change tempest BadRequest exc to tempest-lib exc * Remove unused password in test\_live\_block\_migration * Use cls instead of self in classmethod * Remove CONF reference from volume client * Add os-baremetal-nodes tests * Remove CONF values from data\_processing client * Rename data\_processing client * Remove unused CONF from limits/identity\_client * Fix 'Number of new ports: 2' failure mode on test\_hotplug\_nic * Updated from global requirements * Drop the legacy and un-used \_interface * Change basic server client methods to return one value and update tests * Split resource\_setup for database tests * Unskip and Add timeout method to dns check scenario * Change project and domain parameters * Change tempest Unauthorized exc to tempest-lib exc * Change tempest NotFound exc to tempest-lib exc * Change tempest Conflict exc to tempest-lib exc * Change tempest OverLimit exc to tempest-lib exc * Move default creds to cred\_provider * Adding the ability to use pre-created ports in Network scenarios * Remove novaclient CLI tests * DHCP should be disabled on external subnet * Change fixed/floating ip clients to return one value and update tests * Fix super calls in ResponseBody and ResponseBodyList * Change some small compute clients to return one value and update tests * Change compute volume client to return one value and update tests * Remove HTTP\_SUCCESS from tempest.test module * Removes unused function from javelin * Remove auth\_version config from get\_credentials * Adding api tests for dvr routers * Add compute feature enabled flag for the ec2 api * Fix typos in boto decision\_maker() * Change flavors client to return one value and update tests * Change telemetry client to return one value and update tests * Change data\_processing client to return one value and update tests * Change limits/quota clients to return one value and update tests * Volumes: strict type and value check of the 'bootable' flag * Update test\_live\_migration for return-one-value * Split resource\_setup for orchestration tests * Change security-group clients to return one value and update tests * Split resource\_setup for messaging tests * Change keypair client to return one value and update tests * Split resource\_setup for baremetal tests * Split resource\_setup for data\_processing tests * Test for id\_username instead of "Log In" * Separate token client from identity client * Switch to using the skip\_because decorator from tempest-lib * Fix stress test which was not changed to receive one client value * Updated from global requirements * Remove CONF values from volume clients * Fix import in nova cli tests * Add debug info to timeout exception in \_hotplug\_server * Change tempest InvalidContentType exc to tempest-lib exc * Change tempest UnprocessableEntity exc to tempest-lib exc * Remove unused exceptions * Adds "list-groups" testcase to v3/test\_groups * Remove UnexpectedResponseCode exception * Remove CONF values from baremetal client * Remove inheritance from baremetal client * Move JSON de/serializers to base baremetal client * Add sec-grp-rule for ipv6-icmp to enable ping6 between instances * Order service clients setting first * Remove CONF values from telemetry client * Test case for database limits * Update live migration test for return one value * Remove data\_utils import from a service client * Remove CONF values from messaging client * Remove CONF values from object\_storage clients * Do not parse Neutron error message to tolerate message changing * Fix typos in security group scenario test * boto: Remove skip for bug 1072318 * Add os-tenant-networks test * Fixed a small spelling mistake * Fix create server request in test\_network\_v6 * Add Forbidden translation * Add \_set\_object\_storage\_clients() to client setting * Add a tests for service clients * Drop nova-manage CLI tests * Turn off sitepackages * Improves a couple of docstrings in Javelin * Fix FIP negative tests for nova V2.1 API * Always use an available plugin for CLI sahara test * Updated from global requirements * Add ironic port show by mac address * Add a test for new-style container-sync * Remove CONF values from database client * Allow to specify the list of sahara enabled plugins * Add SSL parameters to negative tests * Use server\_client clearly for removing server\_group * Remove CONF values from network client * Remove CONF values from compute clients * Update glance\_http for SSL * Fix \_create\_loginable\_secgroup\_rule in scenario manager * update test\_volume\_boot\_pattern to assign one value * Skip test\_subnet\_details * Add ipsec-site-connections to resource map of a network client * Use valid metadata keys for compute images * Switch rest\_client module to tempest-lib * Remove RestClientException inheritance * Change orchestration client to return one value and update tests * Remove CONF values from OrchestrationClient * test\_update\_instance\_port\_admin\_state * Test multiple security groups association for a vm * Change some compute admin image client methods to return one value * Add explicit cleanup to test\_ports * Add compute\_quotas lock fixture to test\_absolute\_limits\_negative * Use create\_test\_server in test\_max\_image\_meta\_exceed\_limit * Add return one value to volume delete methods * Adds scenario for DNS-nameserver configuration * test\_verify\_multiple\_nics\_order failing in gate * Remove JSON-specific code from client settings * Change compute image client methods to return one value * Make rest\_client module py34-compatible * Move tempest\_roles options to auth group * Add roles to all tempest created users * Change volume client methods to return one value * Move fake get\_credentials methods to test\_auth * Remove CONF reference in test\_rest\_client * Move ResponseBody/List classes to service\_client * Make TestSwiftBasicOps use object\_client * Test port update with new security group * Separate tests of negative\_rest\_client * Remove imports of http module * Add raw\_request() to RestClient * Remove all CONF values from RestClient * Updated from global requirements * Adds tests create/update port with multiple attributes * Create port in allowed allocation pools * Restore the persistent server option in the LB tests * Add image client build timeout config option * Change image client methods to return one value * printout testr tests that fail * Add a test for filtering list servers for a specific tenant * Change v2 identity client methods to return one value * Floating IP Negative Tests * Handle HTTP 415 in rest\_client * Update sample config * Separate NegativeRestClient from rest\_client * Move \_get\_region() to NegativeRestClient * Remove ObjectClientCustomizedHeader class * Remove AccountClientCustomizedHeader class * Remove \_get\_endpoint\_type() from RestClient * Separate build\_interval/timeout from RestClient * Updated from global requirements * Delete unused schema definition console\_output * Ensure subnet for port security group tests * Avoid port\_state error in test\_create\_list\_show\_delete\_interfaces * Drop ComputeAdmin configs, credentials and manager * Remove python client related logging * Preupgrade setuptools in tox tempestenv * Change v3 identity client methods to return one value * Ignore router\_interface is not found while cleanup * Test delete of SLAAC subnet without port delete * Floatingip as port fixed ip * Refactor custom matchers * Fixing typo in config.py * Add ComputeClient for cleanup * Skip personality max limit tests in case no limit * Fix log message on exception in setUpClass * Remove interface json and set skip\_test method * EC2: do not assume order in dictionary * Add ObjectStorageClient for cleanup * Add VolumeClient for cleanup * Improve error message on exception raised by get\_default * Improves documentation formatting of cleanup.py * Add IdentityV3Client for cleanup * Fix dhcpv6-stateful tempest test to validate only valid use-case * Avoid overlapping subnets in class NetworksTestJSON * Add test case for floating\_ip response body * Actually attach a volume to an instance before taking snapshot * DHCP6 Tests fail to remove DVR ports * Adds documentation for Javelin * Fix slowest test output after test run * Change neutron client methods to return one value and update tests * Test wrong IP version of prefix in security rule * Skip load balancer API test for ipv6 network * Routers Negative API Test * Correctly fetch the ips from server * Add a word "Test" to metering test classes * Remove Nova v3 API config * Reorder setup and cleanup functions for readability * Remove unnecessary setting from CredentialsTests * Add Test to Create Port with no security groups * Fix rebuild server tests for wrong input * Add note about build\_timeout * Remove Nova v3 API schemas * Remove Nova v3 API clients * Remove Nova v3 API tests * Use isolated creds for dashboard scenario * Fix ipv6 network scenario in multi-network environment * Skip IPv6 scenarios for baremetal * Remove use of 'cls.enabled = False' * Bump working version to 4 * Remove Nova v3 API code from RestClient * Raise a new exception NotImplemented for HTTP501 * Remove XML related code from RestClient * Updated from global requirements * Change rest client exceptions' inheritances * Move safe\_body() into specific class * Make InvalidServiceTag inherit from TempestException * Framework for staged setup * Use assertIn to check for subnet membership * Do not test the metadata\_items limit when there is no limit * Remove unused TOKEN\_CHARS\_RE * Remove network debug * Check that the number of fixed\_ips is at least one * Skip test\_update\_router\_admin\_state for baremetal * Temporarily disable service-type test * Do not test the maxImageMeta limit when there is no limit * Ensure extension aliases are used for nova based services * Revert "Log the credentials used to clear networks" * Add more verbose info for wait\_for\_...\_status methods * Remove Volume V1 specific test base classes * Log the credentials used to clear networks * Ironic: Remove few misguided negative tests on ports * Test to create bulk port * Handle pagination keys in lister response * Add ca\_certificates\_file option to test against SSL servers * Make policy client methods return one value, and fix tests * Support creating users with a default\_project\_id * Port API Test Enhancement * Basic networks Scenario Test Enhancements * Remove unnecessary secgroup attrs from scenario tests * add cinder v2 api tests for volume\_services * part-2 expect badRequest in server metadata test * Use volume\_id rather than volume\_name in volume extensions timeout * Fix print name of ResponseBody object * Adds scenario for IPv6 addresses * Expect badRequest in server metadata negative test * Adapt negative testing documentation * Use a compute service type from tempest.conf * Respect auth.allow\_tenant\_isolation * Remove unnecessary parameters on create\_volume API * Fixing casual failing of nc service in LBaaS 3 - * use built in HTMLParser instead of lxml * Firewall as a service API Test Enhancement * Remove not used attribute \_schema\_file * Remove unused xml config options * Enable cinder v2 api tests in volume quotas * Add code to enable single-value response for http clients * Scenario manager: catch Exception in get\_remote\_client * Fix doc for usage of python clients in scenario tests * Expand service validation to work for Nova cells * remove xml\_utils and all things that depend on it * purge xml clients from tempest.clients * part 2 of delete xml * delete all compute xml tests * Skip test\_volume\_boot\_pattern until bug 1373513 is fixed * Dashboard login page assert string fix * rm pyc files before test runs * Cleanup class resources for large-ops scenario * Take InstanceType for launchconfig res creation * Unified interface for ScenarioTest and NetworkScenarioTest * DHCPv6 network tests * Update documentation for clarity * Updated from global requirements * Fix error message when isolated tenant subnet creation fails * Add check for too\_slow\_to\_test flag on swift notification test * Use standard assertVolumeStatusWait for volume detachment * Added neutron cli test case for fwaas * Security Groups API Tests Enhancements * enable cinder v2 api for test\_volumes\_backup * Add internal methods for setting identity clients * Sync latest log module from oslo-incubator * Add Conflict exception in test\_floating\_ips\_actions case * Add internal methods for setting volume clients * Moved init\_conf call and preserve tempest network objects * Add config options for XML support in Keystone * Ignore NotFound during network cleanup on API * Enable cinder v2 api for volume type extra specs tests * Enable v2 api for volume and snapshot tests * Fix inheritance for BaseTestCase * Move common compute clients to the setting method * Enable cinder v2 api for volume hosts * Adds test for deleting external network with floatingIPs * Updated from global requirements * Adds doc for public\_router\_id in tempest.conf * Update README.rst for generating sample conf file * Add internal methods for setting compute clients * Attempt to test live\_migration by default * skip test\_server\_cfn\_init as the failure rate is too high * Delete console negative tests * Add a shelve/unshelve scenario * Make every swift clients use expected\_success * enable cinder v2 api for volumetype test * Fix invalid URL in AccountClientCustomizedHeader * Add scenario test for swift-proxy ceilometer middleware * Support for IPv6 tests for API * Stop resource leakage for negative tests * Use addCleanup when running swift scenario tests * Make delete\_container use expected\_success * Fix empty string as id in create flavor request * Fixes grammatical errors in HACKING.rst * Creating individual resources for vpnaas test case * Remove a redundant KeyPairsV3ClientJSON * javelin2: destroy functions above network resources * javelin: add network and secgroup resources * quota testcases use tenant\_name should use tenant\_id * Fix tox stress invocation to not quote arguments * Update docs for regenerating sample conf file * Decouple resources and preconditions in secgroup scenario * Fixes volume client in volume tests * Revert "Skip test\_list\_server\_addresses if neutron" * Adds "list external networks" test to regular user * Remove ephemeral size assertion in baremetal scenario * Add wait for async secgroup add to minimum basic scenario * Skip some tests when cinder is not enabled * Remove "Disabled Reason" from test\_cinder\_service\_list * Increase default maximum safe\_body function * Don't setup identity v3 client if v3 is disabled * Fixed a comment on dirty logs list * Fix service validation schema * Increase the 'all' timeout because slow tests are included * Change CLI tests to use auth providers * Prevent external router IPs from being sent back * Correct docstring indentation * Fix the flavor ram string value * Allow some lag for floating IP to reach a status * Fix unit tests to detect import failures * Adds method to verify multiple tenants are available * Include 207 in http success status code RFC-4918 * Add list-networks test for os-networks API * Fix missing usage of the xml neutron clients * Use "ip" instead of "ifconfig" to get MAC address * Remove checking of non empty resp body for 204 * Add config option for XML support in Networking * Switch to using subunit-trace from tempest-lib * Migrate cli test framework to tempest-lib * Fix AttributeError when test\_function\_help fails * Create subnet without gateway and explicit IP ver * Adds test for list/get volume attachments V2 APIs * Fix resource leakage in messaging.test\_queues * Remove AltManager class from client * Bump version string to coincide with kilo release * Security Group ICMP validation for icmp-code * Test to update port with CIDR value * Added neutron cli test case for vpnaas * Fix and re-enable test\_disassociate\_not\_associated\_floating\_ip 2 - * Make duration in serial test approximate total run time * Add cleanup to test\_verify\_multiple\_nics\_order * Remove PYTHONHASHSEED=0 from tox pep8 job * Log identifiers of resources being cleaned up * Move tempest to oslo-config-generator * Add list servers to large ops test * Fix execute status of cleanup script files * Don't double log API request * enable cinder v2 api for test\_multi\_backend * Fix resource\_setup order in scenarios * Add missing space to build\_timeout description * Verify network interfaces are in requested order * Fix cleanup for EC2 test\_compute\_with\_volumes * Adds TestSecurityGroupsBasicOps created VM's security\_groups check * Move status check to client for "messaging" tests * Make rest\_client.wait\_for\_resource\_deletion timeout error more specific * Add exceptions.BadRequest to the test case of reserve\_fixed\_ip * Run telemetry in javelin if resources.yaml says so * Add a server console-log test with unlimited length * Don't use a specific arg list for stress in tox.ini * Remove @author tags from copyright statements * Port API Tests Enhancements * Add Credentials Provider factory * Fix duplicate ref of volume\_types\_client in vol tests * Orchestration use stack timeout waiting for ping response * Allow javelin to run with incomplete resources.yaml * Add expected response checks of secgroup rules * Fix verify\_tempest\_config with disabled services * Fix use of kwargs instead of \*\*kwargs * Drop unused safe\_setup decorator * Hacking rule to forbid resource unsafe fixtures * Log AttributeError as 'info' instead of 'exception' * Remove OfficialClient dependency from HACKING.rst * Adds status check for FloatingIP in network scenarios * Remove setUpClass added after cleanup * remove heat test\_updates tests * Migrate cli tests to resource\_\* fixtures * Migrate thirdparty tests to resource\_\* fixtures * Migrate scenario tests to resource\_\* fixtures * Migrate volume API tests to resource\_\* fixtures * Improve IPV6 parity in Security Group testcases * Revert "Skip telemetry\_notification\_api test due to bug 1336755" * Fix Docstrings for test\_server\_basic\_ops.py * Separate security group rule test for each args * Remove Resp status code checks in stress tests * Fix create\_user parameters for v3 * Don't parse config file if it doesn't exist * Use shared values for test\_security\_group\_rules * skip cfn\_init test, it is failing too much * Revert "Add "reboot\_instance" EC2 API test" * Remove "nova-manage flavor list" CLI test * Removes deprecated net\_common module * Drop client\_type for auth module * Drop OfficialClientManager and references to it * Drop OfficialClientManager from tenant isolation * Add the start message for debugging net info * javelin: fix user destruction * disable neutron network tests that fail too much * Fix finding router for scenario tests * Fix schema definition admin\_flavor\_create * Reduce complexity during import phase * Add an internal test method for rebooting a server * Fixes image create utf-8 multibyte test * Add a test for deleting a suspended server * javelin: Detach the volume before destruction * Updated from global requirements * Fix MismatchError for LB scenario test * Clean up tox.ini and make tempest and unit test distinct * Migrate scenario utils to tempest client * Skip large\_ops\_scenario if large\_ops\_number < 1 * Migrate telemetry API tests to resource\_\* fixtures * Migrate queuing API tests to resource\_\* fixtures * Migrate orchestration API tests to resource\_\* fixtures * Javelin: enable volume resources * Fix resource cleanup in server rescue tests * Rename NeutronScenarioTest to NetworkScenarioTest * Drop OfficialClientTest and NetworkTest * Cleanup leftover dependencies to official clients * Add test caller to test\_server\_cfn\_init timeout error message * reenable full request/response logging at DEBUG * Fix use of code name in services decorator * Add "reboot\_instance" EC2 API test * Stop using intersphinx * enable volumes v2 snapshot tests by sharing codes * Easier to read console output * Add bash to tox whitelist\_externals * Remove duplicate \_ping\_ip\_address() methods * Log deletion errors in clean servers * Migrate test\_load\_balancer\_basic to tempest client * Properly preserve trace on error during wait * Lock test\_resize\_server\_using\_overlimit\_\* tests on 'compute\_quotas' * Replace wait with communicate to avoid potential deadlock * Add a test documentation section to the docs * Drop autoscaling scenario test * Updates Tempest to match Zaqar's API name change * Restoring dashboard tests * Delete baremetal resources in correct order * Add tempest post-run cleanup script * Migrate baremetal\_basic\_ops to tempest clients * Add missing baremetal API tests * Typo fixes "tryies", "snaphot", "Retruns" * Migrate object\_storage API tests to resource\_\* fixtures * Migrate image API tests to resource\_\* fixtures * Make cli test namespace include test files * Migrate data\_processing API tests to resource\_\* fixtures * Migrate database API tests to resource\_\* fixtures * Migrate baremetal API tests to resource\_\* fixtures * Migrate network API tests to resource\_\* fixtures * Migrate identity API tests to resource\_\* fixtures * Migrate computev3 API tests to resource\_\* fixtures * Migrate computev2 API tests to resource\_\* fixtures * Framework for resource safe class level fixtures * Migrate TestStampPattern to tempest client * Removes bogus negative create image compute * Select AuthProvider type from credentials type * Fix scenarios not passing down specific network * Add subnet tests for extra attributes * Add response schema for V2 sec grp default rule * Migrate test\_server\_cfn\_init to tempest clients * Fail tempest if 0 tests are run * Remove PYTHONHASHSEED=0 from tox.ini * Update Heat CLI test to pass with random hash * Unskip test\_update\_port\_with\_second\_ip() * Unskip test\_rescope\_token() * Unskip test\_stack\_update\_add\_remove() * Unskip test\_create\_and\_get\_delete\_bucket() * Add test list\_servers\_filtered\_by\_name\_regex * Properly print STDOUT and STDERR in test\_wrappers * Backward compatibility to credentials in conf * Migrate test\_large\_ops to tempest client * large ops test should be derived from OfficialClientTest * Skipping dashboard test to unblock gate * Support lack of ephemeral volumes for baremetal * Change LOG.exception to LOG.info for skipped tests * VolumeMultiBackendTest: delete error volumes * Add a test for deleting multiple objects by POST method * Replace confusing member name * Remove redundant waiter in create\_volume cleanup * Better timeout exception in wait\_for\_resource\_deletion * javelin: fix object destruction * Allow new quota types * Make SwiftScenarioTest methods public * Have the EC2 instance test to wait for termination * Migrate advanced server network to tempest clients * Migrate test\_encrypted\_cinder\_volumes to tempest client * Migrate security\_groups\_basic to tempest clients * Fwaas API Test Enhancement * Fix unstable assertion in test\_cinder\_endpoints * Add baremetal API tests about console * Move javelin2 over to use oslo logging * Move cli tests into service subdirectories * Move success response checking to the token client * Move response code checking to client for v3 identity tests * Check port's status also in show port tests * Remove force\_tenant\_isolation=True from test that doesn't need it * Verify network connectivity before state check * Only log console log if nova supports consoles * Avoid to iterate over empty list in DictMismatch * Add a NotLockingAccounts credentials provider * Fix use of nonexistent class variable in accounts * Properly detect grenade in check\_logs script * Fix TypeError in TestVolumeBootPattern * Make output from check\_logs less verbose * Fix V2 hypervisor server schema attribute * Some scenario tests do not delete network resources * Work toward Python 3.4 support and testing * Make unit tests not depend on random hash seed * Adds the clients and tests for CINDER QoS V1 & V2 APIs * Fix load balancer scenario if no tenant network * Fix network/secgroup scenario tests for ironic * Allow out of quota failure status code to be 413 or 403 * Migrate test\_network\_basic\_ops to tempest clients * Fix IPv6 default masks for SLAAC * Adds v2 keystone service test to service v3 * Fix response body format of orchestration\_client to dict * Fix "mutable" object as default value * Cleanup neutron default security groups on tenant deletion * Use safe\_setup instead try block in setUpClass * Add a check\_telemetry method to javelin * javelin: implement resource destruction * Heat SwiftResources : only count containers created by Tempest * Migrate test\_volume\_boot\_pattern to tempest client * Migrate test\_server\_advanced\_ops to tempest client * Migrate test\_server\_basic\_ops to tempest client * Refactor ways of creating anonymous test user * Improve the selectability of Swift tests * Fix tox coverage job * Adds Ironic set/get boot device API test * Extract SwiftScenarioTest class to manager * Client response checking for orchestration service * Move API response success check to Neutron client * Ensure proper ordering of XML arguments * Add a test for inline parameter of TempURL * Adds support for custom image formats in scenarios * Fixes Hyper-V basic scenario volume partition name * Add cases for Swift scenario test * Allow FWaaS API test to accept more than one end state * Revert change to get\_remote\_client() * Add \_interface attribut in BaseNetworkTest * Add sec-group rules for ping6 and ssh -6 * Catch BadRequest exception from correct module * Add REVIEWING.rst * Ensure PYTHONHASHSEED=0 for the 'all' tox env * Remove functionality to load json files * Updated database clients for bp client-checks-success * Updated the baremetal client for bp client-checks-success * missing network configuration in telemetry tests * Add glance tag to test\_server\_actions * Migrate test\_snapshot\_pattern to tempest client * Migrate test\_minimun\_basic to tempest client * Fix "NotFound" error in \_clear\_stacks() * test\_hotplug\_nic wait for the guest * Change default size for strings to 1 * Add functools.wraps on all function decorators * Update volume client check success * Add service tags to tempest/api/orchestration/stacks * Add network's status check after creation of network * Supports DEFAULT group opts in TempestPrivateConfig * Do not run fwaas API tests in smoke * Don't run lbaas scenario test as smoke test * Add MatchesDictExceptForKeys custom matcher * Deacrease the required image store size by 4 GiB * Rename Marconi to Zaqar * Remove test\_db\_archive\_deleted\_rows * Add os-networks JSON client for tempest * Migrate test\_aggregates\_basic\_ops to Tempest clients * Migrate swift scenario test to tempest client * Remove test start output in pretty tracer * Use python abc in DeletableResource class * Use python abc in StressAction class * Add new snapshot compute feature flag * Disable hacking rule H305 * Remove not used server creation from test\_server\_rescue * Add ironic instance rebuild test * Convert request schema (servers) * Convert request schema (flavors\_admin) * Convert request schema (flavors) * Allow dict's as schema definitions * Revert "Added neutron cli test case" * Adds an orchestration API test for resource types * Enable E128 ignore E129 * Use list comprehension in create\_bulk\_networks * Enable H407,H305,H307,E122 ignore E123 * Relax check for router interface in \_verify\_network\_details * Remove cleanup code according to TODO comment * Fix errors on import with service tags * Added neutron cli test case * Add console\_output compute feature flag * Add a config option to select compute xml api * Fix arguments for method expected\_success * Remove skipping test\_list\_servers\_by\_admin\_with\_all\_tenants test * Remove skipping flavor\_access\_add/remove related tests * Fix typo in comment * Router API Tests Enhancements * Move response validation schema's in sub folder * Allow new quota types * Removes test\_force\_delete\_server\_invalid\_state negative test * Implement javelin2 destroy images * Do not isolate networks for baremetal * Add interface\_attach compute feature flag * Skip ceilometer test test\_check\_glance\_v\*\_notifications * Avoid unneeded use of set, and use list instead * Stop leaking identity resources in teardown\_all() * Add image-list to glance cli help test * Set python hash seed to 0 in tox.ini * Allow out of quota failure status code to be 413 or 403 * Change 'ceilometer' tag to 'telemetry' and add tags to cli tests * Add a new credential provider to use a list of creds * More comments to doc strings * Add min\_client\_version decorator for CLI tests * Skip test\_rescope\_token * Add compute tags to tempest/api/database/flavors test * Enable some volumes v2 tests by sharing codes part2 * If no volume, don't delete volume in teardown * Allow out of quota failure status code to be 413 or 403 * Use random binary data for test images * Tag test\_nova\_keypair\_resources as gate, not slow * Fix skip\_path logic if path part is empty * Add network tags to test\_quotas\_negative * Separate create server schema for admin password * Sahara: add API tests for jobs * Sahara: preparations for job tests * Move CommandFailed exception to tempest.exceptions * Add unit test section to the field guide index * Add links to the field guide index * Fix javelin2's skip logic in checks * Run javelin2 check at the end of create * Make javelin2's ping for longer * Make javelin2 create\_server wait for server to boot * Avoid errors in log when neutron tests are skipped * Add glance notifications tests for ceilometer * Check rescue\_server action attributes of Nova APIs * Add destroy\_server to javelin2 * Add logs to javelin2 * orchestration api test\_stacks wait for deletion * orchestration: tolerate NotFound in wait\_for\_stack\_status * Skip telemetry\_notification\_api test due to bug 1336755 * test\_neutron\_resources to read template's values * Adds "user-password-update" to v3/test\_users * Fix exception when api\_extensions is set to empty * Use python abc in auth class * Revert "Add tests for wait\_for\_server\_status" * Add new shelve compute feature flag * Check rebuild server action API attributes * Delete unused class Service * Add client response checking for data processing service * Add config file flag to javelin * Log when javelin2 finishes running successfully * Make javelin resources optional * Update compute attach tests to use "volume\_device\_name" * Updated from global requirements * Move baremetal API tests to an /admin subdir * Skip migrate server invalid state if resize is off * Make sure cli CommandFailed prints out stdout and stderr * Fixes incorrect assertion check * Migrate test\_dashboard\_basic\_ops to tempest clients * Base class for scenario test using Tempest clients * remove default=None for config options * Fix a few bugs when running with -u * Add base class for all credential providers * Refactor random url generation into its own method * Adjust stress test documentation * Verify get\_instance\_action attributes of Nova API * Call clear\_isolated\_creds in scenario tests * Removed deprecated command from glance cli * Make javelin check logging a bit better * Added Heat Software Config-Deploy API tests * Skip hypervisor uptime test for baremetal * Create test images via Glance instead of Nova * Correct misspelled words * Add tests for wait\_for\_server\_status * Adds new v3 projects tests to Keystone * Adds list users tests in v3 * Correct volume type api schema validation * Add "capabilities:" to multiple\_backends extra-spec key * Re-enable 'check\_trust\_roles' * Add three new CLI tests of sahara * fix javelin to let you specify imgdir * olso log sync * Don't use swift roles when no swift * Avoid conflicts with other tests instances * Skip baremetal tests if driver not supported * Always pass str to shlex.split for py26 compat * First part of moving success response checking to identity client * Enable some volumes v2 tests by sharing codes * Read template's expected value orchest test Part-2 * Read template's expected value orchest test Part-1 * Add a skip for bug #1334368 * Change of copyright to HP for keystone regions * Make os-quota-class-sets test not break quotas * Running without keystone v3 doesn't require uri\_v3 * Adds test for Sec group default rule Nova V2 API * Remove docutils pin * Exclude volume tags while listing instance tags * Validate server detail list attribute of Nova APIs * Add Ironic show driver API test * Make test\_drivers.py use driver name from conf * Fix disk\_available\_least in JSON schemas * Add python-ceilometerclient in requirements * Convert scenario test tearDown to addCleanup * Improve cinder CLI existing tests * unskip list server actions tests * update iptables rules for more useful debugging * Remove unnecessary log message * Sahara: minor changes for API tests * Add skip decorators in test\_servers\_negative * Fix "create a flavor" tests for strong validation * VPNaaS API Tests Enhancements * Add querying for lists in ceilometer client * Fix race condition in flavor tests * Remove the skip for test\_get\_instance\_action() * Add network service tag to compute tests * Verify "get a server" API response attributes * Add parametric tests of Swift object API, part 4 * add py27 to default tox * Make unable to ping log messages appropriate level * Add caller to v1 image\_client wait\_for\_image\_status timeout exception * Correct nc command in test\_load\_balancer\_basic * Improve neutron CLI existing tests * Require stevedore for check\_uptodate.sh * Add 'id' to the server group cleanup list, not body * Fixes Scoping bugs * Replace the console output on Error with fault a message * Skip quota-out tests temporary to change error codes * Fix availability zone client in compute admin test * Missing node driver interface validation API * Sharing codes for volumes list tests * Allowed Address Pair API Tests Enhancements * Avoid snapshotting from compute authorization test * Verify create agent attributes of V2/V3 APIs * Drop unused admin client in server metadata tests * provide enough time to do expires in obj tests * Add Tests for Message & Claim APIs * Add 'Member' role to all created users * Add client response checking for image service * HEAD doesn't return a body * Added service "data\_processing" to service list * Remove assertTrue from ssh unit tests * Updated from global requirements * Verify list-Floating-IP-Bulk Nova API attributes * Add new rescue compute feature flag * Avoid empty string for length in os-getConsoleOutput * Use the http image location conf option for api tests * Make test\_snapshot\_pattern ssh error path work * Improve test isolation in test\_networks:BulkNetworkOpsTest\* * Use config generator rc instead of wrapper script * Update config generator from oslo-incubator * Sahara: add API tests for job binaries * Fix T104 for scenario test subdirs * Add Ceilometer client for scenarios tests * Skip case for fixing incorrect exception assertion * Verify delete-Floating-IP-Bulk Nova API attributes * Force connection auth as part of RemoteClient in scenario * Don't try to trace non-printable characters in debug output * Add tox job to build docs * Adds scenario tests for volume encryption * Added swift CLI test cases * Verify V2 list server\_group API attributes * Check create-Floating-IP-Bulk Nova API attributes * Fix calls to mock.assert\_not\_called() * Enable E251,E265 rules ignore H402 * Sharing codes for cinder v1 and v2 tests * Enable E113,E111,H302,F812,E713 rules * Don't include boto tests as part of smoke tests * Bump hacking to 0.9.x series * Unbreak baremetal API tests that were not updated * Add test for compute API os-quota-class-sets * Set default to false for nova v3\_api config option * javelin: create and attach volumes * Code clean-up of CLI test "cinder list-extensions" * Verify list\_instance\_action attributes of Nova API * Use known flavor ID as marker * Tempest API tests: Add ipv6 attribute tests * Disable isolated\_creds.net\_resources on network scenarios * Sahara: preparations for job binary tests * Handle backup not found in test\_create\_backup * Remove the validate\_driver\_info test * Sync versionutils from oslo-incubator * add block\_device\_mapping to create\_server params for nova clients * Add total runtime to summary output * Heat Overlapping ip issue * fix usage of allow\_tenant\_isolation * Cleanup for local variable in test\_attach\_volume * make 'server' as 'required' in server update schema * Adds test for Floating Ips bulk Nova V2 API * Correcting 'id' type in compute schema * Add unittests for remote client * cleanup on remote\_client * Fix update\_server JSON Schema for Nova V2 * make logformat more similar to oslo * Bump baremetal power/assoc/unprovision timeouts * Add a hacking check to block scenario manager in api tests * Convert get\_console\_output length=None to -1 for Nova V3 API calls * Change how tempest debug logs are displayed * Use correct base test class for test\_stress unit tests * VPNaas IPSec policies tests * Mising CLI test for Keystone * Adds keystone roles v2 tests in v3 * Consolidate \_check\_tenant\_network\_connectivity in NetworkScenarioTest * Consolidate \_check\_public\_network\_connectivity into NetworkScenarioTest * Add bug skip for test\_container\_synchronization() * Make run\_stress.py script an entry point * Add README section about API stability * Revert "don't reuse servers in test\_server\_actions" * provide ability to tweak oslo log defaults * Place baremetal common methods in base class * Add parametric tests of Swift object API, part 1 * Optimization and response status code update * Check create/get/delete V2 server\_group attributes * Revert "Fail fast during advanced networking test" * don't reuse servers in test\_server\_actions * Add branchless tempest section to README.rst * dump console if we time out on a server op * Fix warnings from build\_sphinx * Remove the list\_servers\_since\_test * adds the missing \_interface variable * Clarify API extension description of Nova API * Nodestate API test for power state set * Add cinder CLI tests "qos-list" and "encryption-type-list" * Add CLI test of "nova server-group-list" * Add missing compute service tags to volume API tests * Reworked scenario tests to use their own non-default security groups * Wait for server activations for the updated servers * Add compute notifications tests for ceilometer * Enhanced Trove (database) flavor API tests * Verify 'update-server-metadata' APIs attributes * botoclient does not honor disable\_ssl\_certificate\_validation * Fixed argument parsing in run script * Upload public image requires admin role by default * Relax security group rules cleanup * Sahara: add API tests for cluster templates * Add veritification to name property in cinder volume * Don't store duplicate policies for server\_group * test\_aggregates\_basic\_ops picks a non compute node * javelin 2 * allow for arbitrary kwargs in image client * Updated from global requirements * cleaning up index.rst file * Sahara: minor changes for API tests * allow 'main' as valid name for find\_test\_caller * log request start when tracing classes * Verify delete security group response of V2 API * Remove default from image\_ref options * Remove dependency on keyring * Fix sudo\_cmd\_call in tempest.common.commands * set all build\_intervals to 1s * Deduplicate status call checks * remove skip attributes for fixed bug * Test script for CRUD operations on regions * Use old dict generator syntax in baremetal/test\_ports * validate server action 'console\_output' attributes * Remove nova v2 client in ListSnapshotImagesTest * Update version string to follow new convention * Add quota delete test to cinder * Make test\_server\_cfn\_init a scenario test * Add Marconi Smoke Tests * tests the Keystone extensions V2 API * Verify server list\_addresses V2/V3 APIs attributes * Cleanup docstring in test\_l3\_agent\_scheduler * Verify Server Actions attributes of Nova APIs * Revert "Adds certificate tests for keystone v3" * Sahara: add API tests for job binary internals * Sahara: preparations for job binary internal tests * Make sure image exists in test * Start using oslotest for tempest unit tests * Sahara: add API tests for data sources * Add "create a server" test with server group * Switch back to nc in test\_load\_balancer\_basic * Fail fast during advanced networking test * Remove time.time mock from test\_ssh * lbaas basic - open firewall for http on vip port * Fix security\_group\_create\_get\_delete testcase * Fix alt tenant id's issue in BaseV2MemberImageTest * Enable H302 rule everywhere * Use a more sensible network topology for some router tests * Fix test\_create\_show\_delete\_security\_group\_rule * Add hacking import exception for service clients * tolerate HTTPNotFound in scenario manager teardown * add test for command bash-completion * Add API test for resizing a stopped server * Verify more information in floating ip tests(part2) * Fix hardcoded gateway in IPV6 subnet api tests * Fix subnet api test case for non-default cidr * Sahara: editing licenses * Fix test case in security groups * Verify more information in floating ip tests * Adds certificate tests for keystone v3 * Get credential IDs from Credentials class * Split large-ops into three tests so each has its own timeout * Remove tempest\_auto\_config script * Enable GET port\_detail API to set parameters * Fix issues introduced /w migration to Credentials * adds tests for CINDER encryption volume-type APIs * Updated from global requirements * Verify the response attributes of 'aggregate-add-remove-host' API * Add a common admin class for Nova v2/v3 API tests * create test and worker summary report * Add tests for server-group Nova V2 APIs * Verify "list interfaces" Nova v2/v3 API response attributes * refactor out code duplication * orchestration: add test for untyped provider resources * orchestration: Add test for resource\_registry environment * orchestration add basic support for environment API test * Use find\_test\_caller to put test name in timeout exception details * Check list servers attributes of Nova APIs * Share Certificates API's tests between V2 & V3 * Inherit V2 API's flavors tests from V3 tests * Inherit V2 keypair API's tests from V3 tests * Verify list\_addresses\_by\_network APIs attributes * Verify the response attributes of 'create-aggregate' API * Verify update host Nova V2/V3 APIs attributes * Add unit tests for tempest hacking checks * Check non json type on glance client json\_request method * Add in a concurrency aware subunit filter * Access credential fields as attributes * Enforces the use of Credentials (part2) * tests the user-password-update for Keystone V2 API * Removing unnecessary pass instructions * Refactor \_find\_caller into a public test finder utility * Add tests for availability-zone-list CINDER V1 API * modifies the test to sort based on UUID * Add compute service tags to ListSnapshotImagesTest * Verify "update a server" API response attributes * Skip creation of network resources for disabled IPv6 tests * Added Trove (database) version API tests * Extending quota support for neutron LBaaS entities * Add noqa support to no\_setupclass\_for\_unit\_tests * Neutron Allowed Address Pair API test * Revert "add server personality files test" * Updated from global requirements * Add a common class for Nova v2/v3 API tests * Add test for subnet gateway IPv4 and IPv6 * Separate common 'start\_up' definition from v2 schema * Add unit tests for verifying extensions list * Add unit tests for api\_version detection * Add unit tests for \_get\_unversioned\_endpoint * Add cmd entry point for verify\_tempest\_config * Add support for updating the config file * Enforces the use of Credentials (part1) * Use auth data to fill credentials * Define V3 Credentials * Skip lbaas test test\_load\_balancer\_basic * Fix stress runner not stopping on first fail * Stress runner/driver prints stats after SIGINT * Remove {begin,roll}\_detaching volume API tests * Add a lacking message format letter 's' * Verify Set/Get/Delete server meta item attributes * Use fixed ips for lbaas pool members * Add V3 Test to get Spice & RDP console of server * Sahara: preparations for data source tests * ssh\_floating verify reboot * Updated from global requirements * Add tests for CINDER services v1 APIs * Verify create/get flavor attributes of Nova APIs 1 - * Add a response header validation * Verify more information for member in lbaas api tests * fix test\_compute\_with\_volumes * Remove unused arguments * Adds more testcases to test\_telemetry\_alarming\_api * Account failure to the tearDown instead of setUp * Check for 'Generated' in SSH key comment * Verify 'list-server-metadata' APIs attributes * Verify the attributes of 'set-server-metadata' API * Defines a Credentials class * Verify addrs only against ports with fixed IPs * Verify os-migration API response attributes * Verify attributes through Nova os-quota-sets API * orchestration api add test for volume retain deletion policy * orchestration api add basic volume resource test * orchestration api refactor access to stack outputs * Verify tenant usages API response attributes * Verify certificate API response attributes * Verify delete aggregate attributes of Nova APIs * Remove Nova v3 XML test skip * Delete OrchestrationManager, and its unusual credentials * skip the quota enforcement test for swift * Add parametric tests of Swift object API, part 2 * Verify flavor extra specs attributes of Nova APIs * Fix stress runner signal related issues * ssh instance validation add options for Neutron * Verify list agents attributes of V2/V3 APIs * Added cleanup in some tests * Check detail list Images attributes of Nova V2 API * Check attributes of attach/detach volume Nova APIs * Make large-ops test boot a group of instances 3 times * Verify delete server attributes of Nova API * Missing baremetal NodeState API test * fix dict reference error * Verify delete agent response of V2/V3 APIs * VPNaaS API tests cleanup * skip test\_stack\_update\_add\_remove because of race * orchestration convert API test Timeouts to respect build\_timeout * Avoid passing empty string as AZ name * List multiple fields tests for networks/ports * move out ironic exception from common import * add quotas tests * Skip more pause tests if pause is not available * Up the default timeout for stack builds * Remove test\_create\_port\_with\_no\_ip * Validate list\_instance\_usage\_audit\_log Nova V2 API * Fix test\_verify\_created\_server\_ephemeral\_disk * Move exceptions back into one place * Remove the unused \_get\_unused\_flavor\_id() * Consider state in firewall create/delete test * Add Sahara client for scenarios tests * fix import for default clients in clients.py * Swift formpost cleanup * Implementing XML client for VPNaaS * Fix import group ordering in test\_utils.py * Add load\_test mechanism for InputScenarioUtils * Move the test\_utils module to scenario test dir * Add cinder api version detection to verify\_tempest\_config * Fix service list in verify\_tempest\_config * Fix url parsing for api version check * Inspect listed ports for created port with binding * Verify the response attributes of 'update-aggregate' API * Check get\_vnc\_console attributes of Nova APIs * Remove test\_create\_server\_response * Updated from global requirements * Verify delete interface response of V2/V3 APIs * remove n-sch from the watch list * Unit Tests for glance\_http * Add keystone api version detection to verify\_tempest\_config * Add check that service tag isn't in path name * Add unit tests for commands * Improve test\_load\_balancer\_basic * Add unit tests for all generators * safe\_setup preserve original trace * Rename instance\_actions v3 plugin tests to server\_actions * Don't output auth tokens with trace output * Verify attributes through Nova os-security-groups API * orchestration API add coverage for stack update API * orchestration API refactor test\_list\_resources * Verify delete quota response of V2/V3 APIs * Verify "get az list" API response attributes * Add opportunity to directly update headers * Verify the response attributes of 'shutdown/reboot\_host' API * Change to use absolute path in load\_template() * Stop swift resource leaking even if an error occurs * Verify list\_virtual\_interfaces attributes of API * Add V2 Nova API os-agents tests * Improve the extra routes test on router * Remove created routers as part of test cleanup * Make heat-slow job run in parallel * Unskip load balancer basic scenario test * Verify the response attributes of 'aggregate-set-metadata' API * Waiting for ACTIVE state in rescue tests * Enable security\_groups\_basic\_ops blocking tests * Network fwaas API test * Volume size could be specified to create volume * orchestration API base class rename clear\* functions * Multiple fixes to test\_server\_basic\_ops * Trailing '/' throws error * Add network API test to create/update a port with 2 IP addresses * Adds unit test for negative class decorator * Sahara: preparations for new tests * Missing baremetal driver API test * Server create - JSON schema validation: adminPass is optional * Check attributes of get server password Nova APIs * Check attributes of create/delete sec groups rule * unskip test\_list\_servers\_filtered\_by\_ip * add trace\_requests option to debug section * Remove python25 workaround from glance\_http * Move run\_ssh class variable into skip decorators * Honor suspend/pause config switches in scenario tests * Verify the response attributes of 'startup\_host' API * Remove singleton pattern in base\_generator * Enable one flavor tests * Verify detail\_list flavor attributes of V2/V3 APIs * Adds Ironic test\_baremetal\_basic\_ops scenario test * Add and use a StackResourceBuildErrorException * Use OS::Nova::Server for NeutronResourcesTestJSON * Move resize\_available class variable into skip decorators * Move pause\_available class variable into skip decorators * Move suspend\_available class variable into skip decorators * Disable other suspend/resume tests if not supported * Stress action for volume attach verify * Expand baremetal port coverage * Verify more information for pools in lbaas tests * Split certificate API test * Check add/remove flavor access APIs attributes * Remove variable part of agent dict to fix AgentManagementTest * orchestration add resource limit API test * orchestration remove unused invalid\_template\_url * orchestration api tests remove duplicate client assignment * orchestration api tests, docstring cleanups * Verify list extensions attributes of V2/V3 APIs * Verify "get version" API response attributes * Verify the create/delete volume APIs attributes * Add os-migration tests for Nova v3 API * Add os-migration tests for Nova v2 API * Remove unused create\_image of Nova v2 API * Check attributes of image meta item Nova APIs * Validate image metadata attributes of Nova APIs * Verify the response status of create delete Image * Enable private flavors tests * Verify hypervisor uptime attributes of Nova API * Check list & search hypervisor attributes of Nova * Validate hypervisors\_servers Nova V2/V3 API * Introduce .coveragerc * Verify the response attributes of 'get\_aggregate' * Updated from global requirements * Check show\_hypervisor attributes of Nova V2/V3 API * fix sahara base class * Verify list\_hypervisors\_detail Nova V2/V3 API * Stop neutron resource leaking even if an error occurs * Validate get\_instance\_usage\_audit\_log Nova V2 API * ceilometer-collector now has errors * Move xml common code into the common dir * Verify "get quotas detail" API response attributes * Deduplicate negative test calls * Check hypervisor statistics attributes of Nova API * Removing unnecessary pieces of code from network client * factor out templates to yaml files * Add list roles api to identity v3 * Update documentation for negative testing * Validate for list flavor attributes of V2/V3 APIs * Stop leaking in images tests * Cinder client does not honor disable\_ssl\_certificate\_validation * Typo in config.py * simplify heat test\_limits * Add config fixture support to unit tests * Skip volume snapshot tests if feature is not enabled * Verify attributes through Nova "list security groups" API * Adds more verification in list alarms * Add unit tests for cli.output\_parser * Add a new exception for invalid structure * Add unit tests for NegativeRestClient class * Check reserve/unreserve fixed-ips APIs attributes * Fail a test if stack delete failed * Add sahara edp cli commands tests * Cleanup sahara cli tests * Some keystone V3 API tests throw incorrect errors * Verify the response attributes of 'list\_aggregates' * Add sahara to list of clients for T102 * Renew token before expiry time * Check create/delete keypair attribute of Nova APIs * Add Keystone role and service test cases * Verify the list volume attributes of Nova APIs * Stop volume leaking * Test current tenant not added to private flavor * Check attach-detach FIP & get FIP pool attributes * Validate list\_keypair attribute of Nova V2/V3 APIs * Validate get keypair attributes of Nova V2/V3 API * Add "delete the volume-attached server" tests * Verify list Image attributes through Nova V2 API * Verify Nova create & get Floating IP attributes * Define 'links' as a common parameter type * Move to the python-saharaclient * Remove resize-revert workaround for bug 924371 * don't log cli output on success * add request timing * add \_find\_caller to the request log * Adds "add\_dhcp\_agent" to test\_dhcp\_agent\_scheduler * add back empty whitelist * Add Cinder quota negatives * API test for 'create server with scheduler hints' * simplify rest\_client logging * Translate xml server tenantId /userId * Skip loadbalancer basic scenario test * Verify quotas attributes through Nova os-quota-sets API * unskip test\_integration\_1 * Add error handling if testscenarios aren't supported * Add parametric tests of Swift object API, part 3 * Verify "create a server" API response attributes * Make add\_remove\_fixed\_ip tests executable in Nova gate * Fix cinder quota cleanup * Remove usage of internal library function in basic generator * Add server to clean up even in case of errors * fix cinder quota equality * Verify attributes through Nova list flavor-access API * Stop keystone resource leaking even if an error occurs * Stop heat resource leaking even if an error occurs * Make the checks of identity status code strict * Add tests for external network extension * Add validation test in identity v3 test\_role * Use HTTP\_SUCCESS for checking success status code * Verify "enable a service" API response attributes * Verify Image attributes through Nova V2 GET API * change dirty logs to work off a whitelist * Verify the response attributes of 'show\_host\_detail' * Verify the response attributes of 'list\_hosts' * Validate of Nova list Floating IPs attributes * Validate get limits attributes of Nova V2 API * Networks,Ports: delete with subnet, port with no IP * Add unit tests for configurable network resources * Add slow tag for test\_ceilometer\_resource\_list * Update Oslo config sample generator * Volume boot test uses new block device syntax * Introduce load\_tests mechanism for negative tests * Stop test server leaking even if an error happens * Refactor create\_ and update\_ methods for floating ips * add volume list tests for cinder v2 * Cleanup common.debug * Add unit tests for debug * add server personality files test * add compute quotas test * Validate get fixed-ips attributes of Nova V2 API * Ignore .coverage\* files * Add utils.misc unit tests * Enhance test to rescope token using v2 * Enhance rescope token test using v3 * Add test to rescope token using v3 * Add missing client names to T102 hacking check * Add unit tests for wait\_for\_resource\_deletion * Add test to rescope token using v2 * Add unit tests for the tempest.common.utils.file\_utils * Add network credential unit testing * Add unit basic unit tests for tenant\_isolation * Support endpoint type in CLI tests * Remove test\_can\_log\_into\_created\_server * Add V2 Nova API os-quota-sets tests * Add cinder tempest cases for encryption-type * Modified test case for nova security group * Add api tests for load balancer's VIPs and health monitors * change teardown check to LOG.error * Add return value of classmethod in network base.py * Move verification of response attributes into service client * Stop volume leaking even if an error is occurred * Add list user groups api to identity v3 * Fix error trace induced by dhcp test actions * Rename Savanna to Sahara * Do not assert router l3 agent association * Wait properly for server deletion in test\_volumes\_actions * Add tests for binding extended attributes for ports * Object storage tests to use default auth\_provider * Volume xml client translate json attribute names * Fix issue with pep8 gate job * Validate Volume attributes of Nova POST & GET API * Fix V3 image tests according to new image\_client * Make imports proper order * Convert fake\_config class to use config fixture * Add agents tests for Nova v3 API * Add basic Delete Queue Marconi test * Make test\_volume\_quotas force tenant isolation * Stop leaking test\_update\_images * Move network test\_quotas to admin directory * Skip Nova API attribute tests if an XML response * Move negative tests for test\_images * Verify service attributes through Nova "get services" API * Updated from global requirements * Unset the imageRef when booting from volume * Verify more information in API tests for LBaaS * Add quota\_set delete test for Nova v3 API * API test for Get Image Member(s) Schema * V2 API Test to get the VNC console of a Server * Improve readability of test\_networks * Adds basic Marconi test * Support disabling suspend/resume for compute api * Support disabling server pausing for compute API * Updated from global requirements * Prepare for enabling H302 rule(services/telemetry, object\_storage, etc) * Add unit test for data\_utils * fix flake8 errors * Removed teardown method from image test * add token get tests * Remove unused build\_url function in data\_utils * Add negative tests for endpoints * Increase testing of network connectivity * Fix problem of deleting dhcp port * Allow failing logs with errors on a per-log-file basis * Enable H302 check (tempest/services/volume) * Add shelve\_offload test for Nova API * Fixed \_error\_checker in rest client * Make tempest accounts independent from devstack * cleanup resources in setUpClass if exception raised * Volume backup details API test * Raise orchestration build\_timeout to 600 seconds * Add ignore files related to coverage to .gitignore * ServerCfnInitTestJSON wait for stack complete before ssh * Skip nova cli tests with volumes if Cinder unavailable * Fix RemoteClient usage in scenario tests * Enable H302 check (api/compute/{c\*,f\*,i\*,s\*,t\*}) * Add support for negative tests with admin client * Add multiple negative test generator support * Add Trove (database) Flavor API Tests * Fix test by waiting to lbaas entity delete * Stop volume leaking test\_server\_rescue\_negative * Do not setUpClass or tearDownClass V3 tests if API is disabled * Prepare for enabling H302 rule(services/identity) * Cleanup \_interface class variables in compute * Prepare for enabling H302 rule(api/image) * Prepare for enabling H302 rule(services/compute/v3/json) * Separate negative tests for test\_images * Prepare for enabling H302 rule(services/compute/json) * Add status code checks for Nova attach\_interfaces API * Nova V3 API test for resetNetwork/injectNetworkInfo * Assign floating ip to a server in load balancer scenario * Prepare for enabling H302 rule (api/volume,tempest/\*) * Prepare for enabling H302 rule (api/compute/admin) * image(s) schema image metadata API test * Removes gate test annotation in security\_groups * Fix invalid syntax in HOT templates * Move ipv6 config option into network-feature-enabled * Cleanup subnet creation in network api tests * Stop running CONF getattr during import * Seperate negative tests for V3 test\_server\_rescue * Updated from global requirements * Add Cinder tests for quota sets * Add parametric tests of Swift account API * Add "delete the resized server" tests * Remove unused definition in network base.py * Add "Nova V3 API test for add-fixed-IP/remove-fixed-IP" * replace basetring/xrange * Add V3 API Test to get the VNC console of server * Add service/endpoint discover to verify\_tempest\_config * Add swift discoverable\_api support to verify\_tempest\_config * Make reseller admin role configurable * Split out config option registration * Add unit tests for image waiter * Configure the ec2 zone explicitly * Adds VM connectivity check after advanced VM operations * Add Nova V2 API test for resetNetwork/injectNetworkInfo * Remove unused \_parse\_array functions * XML endpoint client use correct string for enabled * Cleanup \_interface class variables in compute/v3 * unskip test\_security\_group\_rules\_negative tests * Use pretty tox with the \`\`all\`\` env * Revert "always use sitepackages" * Fix setup\_test\_v3\_user method in identity base.py * Add "V2 API- add-fixed-IP and remove-fixed-IP server action" tests * Separate negative tests for test\_server\_addresses * Skip test\_get\_instance\_action test for nova v3 * Prepare for enabling H302 rule (thirdparty) * Extend network debug infos on network failures * Add endpoint\_type option to Savanna,Ironic and Ceilometer groups * stress/actions/ssh\_floating.py n-net compatibility * Test to update neutron security group * Improve tempest auth tests * Fix api\_version filter for KeystoneV3AuthProvider * Adds L3 agent test case to test\_l3\_agent\_scheduler.py * Add namespaces to xml in Network client * Adds scenario for hotplug nic in network\_basic\_ops * NeutronResourcesTestJSON Use the correct resource name for console log * Check server terminations on "delete server" tests * Removal of methods from json/xml network\_client files * Move admin "delete a server" tests to test\_servers * Separate negative tests for test\_instance\_actions * Improved tests in tempest.api.image.v2.\* * Fix the usage error for config * Clean network scenarios * Test creating router setting tenant\_id * Scenario : Start instances using fixed network when possible * Prepare for enabling H302 rule (scenario) * Prepare for enabling H302 rule (common,services,stress) * Prepare for enabling H302 rule (api/compute) * Stress runner friendly logging.conf.sample * Add unit tests to tempest auth file * Add security group deletion to the cleanup util * Implement pluggability for tempest (exceptions) * Separate negative tests for V3 test\_quotas * Fix author information * Log ip Information on ssh failures in the minimum scenario * Fix stress runner exit issues * Refactor Managers to a common base class * Preventing overlapping subnets in network scenarios * Fix a typo of debug log in scenario/manager.py * Removed RestClientXML class * Avoid masking an assert failure with a KeyError Exception * Use Python 2.6.x compatible syntax for dict comprehension * add cli.has\_manage option * Fix InvalidInstanceID.NotFound handling * Clarify admin\_delete\_server\_of\_others test case * EC2 client token is not reuseable for new instance creation * Add tests for Swift in orchestration * Add qcow2 image support * Clean up scenario functions * Add "delete a shelved server" tests * Separate the deleting server tests * fix duplicate api\_extensions * Improve ServerCfnInitTestJSON asserts and debugging * Skip EC2VolumesTest if Cinder is not available * Allow IPv6 tests to be disabled * add TRACE level to the items that are being flagged * Separate negative tests for test\_projects * Makes some Swift API tests optional * Separate negative tests for test\_multiple\_create * Separate non-admin AZ tests from admin test files * Remove unsed client variables * Adds cinder backup functional tests * Fix misspellings in tempest * Splits network\_basic\_ops to fully isolated test cases * Refactor cross\_tenant to security\_groups\_basic\_ops * Minor changes to scenario manager * Prepare for enabling H302 (identity,volume,etc.) * Introduce T106 rule for vi modelines * Seperate negative tests for test\_server\_rescue * Mildly wound the interlopers * Couple of fixes to tempest/auth * Add python-savannaclient to requirements * remove some unused code in nova image tests * fix quotas client in admin/test\_quotas.py * Fix test classes name of test\_tokens.py * Migrate negative test to a different file * Skip test\_list\_server\_addresses if neutron until bug 1210483 is fixed * Restore heat tests running with admin user, demo tenant * Fix get\_versions string parsing * Fix get versions call in verify\_nova\_api\_versions() * Fix containers with expired objects deletion problem * Add quota\_set detail test for Nova v3 API * Remove using of deprecated self.headers (part2) * Make endpoint type configurable * unskip test\_list\_non\_public\_flavor * Removes vim headers 5th round * Add basic Savanna CLI tests * Refactor create\_ and update\_ methods for VIPs and health\_monitors * Remove using of deprecated self.headers (part1) * Add extra list servers by host test * Prepare for enabling H302 rule (api/object\_storage) * port nova v2 images related tests into nova v3 part2 * Neutron Extra DHCP Options API test * Neutron LBaaS Agent Scheduler API test * Add api tests for neutron router * Replace assertEqual(None, \*) with assertIsNone in tests * Switch over to oslosphinx * Fix request id log messages * Add a note about python2.6 support to the README * Ensure that bug number is actually a number for skip\_because * Test tempest decorators used on integration tests * unskip neutron\_meter\_label cli tests * cleanUp() removing all test resources as an admin * Rename Openstack to OpenStack * Add CRUD tests for telemetry alarming * clean up security groups tests * port flavors and server\_password tests into nova v3 part2 * Define py27 assertFoo methods for py26 * Add test case for snapshot-create with in-use volume * Test updating port's admin\_state\_up * Separate negative tests for test\_list\_image\_filters * Fix coverage option for run\_tests.sh * Adapt documentation for negative testing * Remove tests for Nova V3 API os-simple-tenant-usage * Adds list pool stat test case * Refactor rest-client and identity v2-client * Add some cases about volume-create and volume-rename * Remove tests for Nova V3 API os-instance-usage-audit-log * Fix test\_create\_list\_delete\_volume\_transfer without tenant isolation * Fix usage of NotImplementedError exceptions in auth classes * Rename images variable in TempestConfigPrivate * Add a serial full tox job to tox.ini * auth.py is too verbose * port flavors and server\_password tests into nova v3 part1 * Remove tox locale overrides * Use testtools.matchers.GreaterThan in test\_volumes\_transfer * Use service catalog\_type when getting the heat/glance clients * Fix syntax in test\_load\_balancer\_basics.\_check\_load\_balancing * Let the soft reboot really happen in the minimum scenario * enable volume list tests for cinder v2 - part2 * enable volume list tests for cinder v2 - part1 * fix base\_url in auth.py * Network API: default to ipv4, add ipv6 tests * Fix volume transfer tests with tenant isolation disabled * Fix images tests with tenant isolation disabled * Add support for special char in volume metadata * Fix client usage in multibackend volume test * Add nova migration-list CLI test * InputScenarioUtils load images if glance available * Skip failing test load balancing test * Test to list ports filtered by router ID * Support building wheels (PEP-427) * network: add metering api operations * Fixed misspellings in Tempest * Add more information to BuildErrorException * Make v2 and v3 identity apis configurable * The rescue tests requires neutron resources * Negative tests: Add result check for resources * Savanna: add API client and tests for plugins * Fix admin tenant credential * Multiversion authentication part2 * Add check\_uptodate.sh to run\_tests.sh -p * Use isolation credentials for neutron api tests * Add the keyword "Test" to some test class names * Clean/leave OpenStack after a stress test * Add unit tests for negative test framework * Multiversion authentication part1 * Removes leftover self.conf from isolated creds * Add logging config values * make testing neutron api extensions optional * Remove unnecessary volume creation in test\_server\_rescue * Correctly call client inits from test\_multi\_backend * Change import of exceptions for keystoneclient * Add log info for tempest SSH connection issues * Negative test autogeneration framework * Add scenario test for load balancer * Fix attach\_interfaces tests of Nova v3 * Remove suffix "JSON" from Nova v3 API test classes * Remove suffix "JSON" from Nova v3 API last test class * Add parametric tests of Swift container API * Add skips to the services decorators * Fix services decorator to use object\_storage * Fix stress test README.rst * Don't run extensions list if service isn't available * Updated from global requirements * Add test for OS::Neutron::Router Heat resource * Add missing isolated\_cred cleanup to savanna tests * Remove last uses of config without global CONF object * Add key -n for sudo utility * Convert all service clients to global CONF object * Convert cli tests to use global CONF object * Convert scenario tests to use global CONF object * stop leaking tempdirs * Enable tenant isolation for the boto tests * Convert thirdparty and stress tests to use global CONF object * Convert volume api tests to use global CONF object * Convert ironic, swift, and heat api tests to use global CONF object * Matches one flavor and one image by default * Remove network resources created in scenario tests * Forbid availability\_zone None in aggregate Nova API * Removes vim headers 4th round * Add alias as prefix for flavor\_rxtx v3 * remove unused variable * Add bool and integer support to XML parser * Convert network api tests to use global CONF object * Convert image and identity api tests to use global CONF object * Convert compute api tests to global CONF object * test\_rescued\_vm\_add\_remove\_security\_group missing addCleanup * Return body output after given status reached * Skip negative tests of v3 server metadata * Move negative tests for v3 test\_server\_metadata * Revert "skip test\_volume\_boot\_pattern" * Removed unnecessary lock from a aggregate test * Move common \_delete\_volume cleanup method in base compute test class * Add tempest test for heat resource OS::Nova::KeyPair * Remove super call in overloaded close() * Separate negative tests for test\_templates * tighten up isolated creds create * Fixed up an error message * Remove Nova v3 XML tests completely * Use BaseComputeTest.create\_test\_server * Run SNAT specific test cases only with ext-gw-mode extension * Specify 'active' status for deleting situations * Fix glance\_http HTTPS response issues * Remove unused method basic\_auth from rest client * Removes vim headers 3rd round * Use unittest2.TestSuite with py26 * Cleanup exceptions * Increase failure details for network\_basic\_ops * Update 'Member' with option 'operator\_role' in tempest.conf * add tests for force delete snapshot: * Add volume cleanup to test\_volume\_transfer test * Exclude heartbeat timestamp from agent list checks * Add more descriptive assertion message for test\_create\_backup * Separate negative tests for Swift * Do not assume volume metadata is identical to POST request * Remove baremetal xml support * Increase ping timeout from 60 to 120 seconds * Skip test\_list\_servers\_by\_admin\_with\_all\_tenants test * Create telemetry client * Change status available to active * remove the nova v3 xml tests from the code * remove Nova v3 XML testing * stop validating user IDs on role assignment operations * Avoid using assertGreater * Separate negative tests for test\_quotas * Call super class after extension check in vpnaas api test * Remove vim headers 2nd round * Add test for volume list with all-tenants * Fix class inheritance for volume\_types tests * Fix attach\_interfaces tests * Check if service is available for cli tests * Add simple node group tmpl API test for Savanna * Use automated create and update methods for core network resources * Fix logic for config file env variables * Make --debug flag on run\_tests and run\_tempest use testtools.run * Fix parameter in auth error message * Sync Patch and PatchObject fixtures from oslo-incubator * Python 2.6 do not has unittest.loader, we import unittest2 instead * skip test\_volume\_boot\_pattern * Remove X-Storage-Stoken from object\_storage * Add tests for testing swift bulk middleware * Rewrite RemoteClient.get\_boot\_time() * text-html type is absent in TXT\_ENC * Remove vim headers * Avoid expiration before updating metadata * Fix log\_console\_output call in test\_network\_basicops * Add a couple log errors to whitelist.yaml * Move console output log place in test\_cross\_tenant\_connectivity * Only create necessary resources for compute v3 tests * Add unit tests for SSH client functionality * Create only necessary resources for compute v2 and network tests * Make network resource creation in isolation configurable * Fix typo in imagev2 class name : Memeber should be Member * Make BaseV2MemeberImageTest inherits from BaseV2ImageTest * AttachInterfacesTest: use build\_timeout on iterface deletion * Fixing typo in test\_volumes\_get.py * Fix parameter type spelling tempest HOT template * Remove user creation in object service and healthcheck test * Fixing typo "existant". Must be "existent" * Create only necessary networks resources for scenario tests * Create only necessary resources for image, object\_storage, volume * Forbid admin\_password None in rescue Nova API * Add to whitelist two errors that slipped through * Add more console output log for scenario tests * Remove test\_network\_quotas\_scenario * Fix host\_update test case for losing essential parameter * Create security group rule with additional args * Fix parameter of nova v3 flavors API * Add missing whitespace in config description * Add a check for compute api versions to verify\_tempest\_config * Add neutron extension support to verify\_tempest\_config * Rework extension verification in verify\_tempest\_config * Moves negative tests from test\_server\_metadata * Fix an error when executing run\_tests.sh * Use install\_venv from oslo to fix no post\_process issue * Test server create with ephemeral disk * Remove copyright from empty files * Improve testing of list\_extensions for compute * Update README.rst with details about unit tests * Add aggregates scenario test * Tests object\_storage extension listing * Fix volume metadata validation of identical to request * Add delete specific status server test * cinder v2 api tests - fix volume client * Input scenario capability for tempest * Add coverage option to run\_tests.sh * Fix v3 test\_list\_servers\_negative * Fixes handling of arrays in XML to JSON conversion * trusts API test, avoid creating duplicate user * Neutron Agent Management List Agents Non Admin * Remove Swift container-sync test skipping * Adds test\_list\_XX\_fields cases to list some fields * log console\_output when test\_network\_basic\_ops failed * Add a config option for trusts * Fix typo in ssh\_floating.json for stress tests * Add a discoverable\_apis option for swift * Adds test\_show\_XX\_fields cases to show some fields * Refactor usage of custom\_match tests * Fix bug in validating Swift transaction ID * List Nova and Cinder extensions in debug log * Delete a BuildError server * Add list server filter with extra limits * Add a version API test for Nova v3 API * API tests for Ironic * Fix typo in ceilometer and neutron tests * Remove skip\_because in swift object expiry test * Moves negative tests from test\_instance\_actions * port test\_server\_metadata and test\_server\_personality into v3 part2 * add keystone group tests * Add heat stack action test case * Add a unit test coverage tox job * Add base class for Telemetry tests * Add python-swift client to the requirements * Add extra list server by status test * fix a minor bug when detecting server image flavor * Wait for the server before validate the rotation result * Add a run\_tempest.sh script * Make run\_tests.sh for running unit tests * Add basic read-only tests for heat cli * Fix reference config before initialization in cli tests * Skip simple\_tenant\_usage related tests * Skip flavor\_access\_add/remove related tests * Add tests for snapshot\_metadata * remove eventlet from requirements * always use sitepackages * keystone trusts API test, move delete into tearDown handler * port nova v2 images related tests into nova v3 part1 * sync oslo to current * Catch ssh exception and detach in min scenario * Remove test\_auth\_token.py * Increase exception log details * add tests for security\_group updating * Kill finally, use addCleanUp * Add test for HEAD queries on Swift tempurl MW * Dump all log errors to console * Moves negative tests from test\_absolute\_limits * port admin/test\_servers\* into Nova V3 tests part2 * port admin/test\_servers\* into Nova V3 tests part1 * port instance\_usage\_audit\_log tests into nova v3 part2 * port test\_live\_block\_migration into nova v3 part2 * port test\_live\_block\_migration into nova v3 part1 * port test\_quotas into v3 part2 * Add tests for volume\_metadata * Add config for Telemetry * port some servers tests into nova v3 part2 * Moves negative tests from test\_server\_addresses * port test\_aggregates and test\_hosts into nova v3 part2 * port test\_aggregates and test\_hosts into nova v3 part1 * Remove unused wait\_for function * Use wrapper create\_volume() in volume tests * Refactor network client: add create\_ and update\_ methods * Moves negative tests from test\_multiple\_create * Separate negative tests for test\_image\_metadata * port servers negative tests into v3 Part2 * port some flavor tests into nova v3 part2 * Separate negative tests for test\_simple\_tenant\_usage * Make testing neutron agents optional * Fix the example of testr testing * Fix OS\_TEST\_PATH in .testr.conf * allow hypervisors to be down but still pass * keystone OS-TRUST extension, test with expiry * keystone OS-TRUST extension, test list operations * Add negative tests for routers * Add 2 tests to the Floating IP test case * Add create\_floating\_ip function * Use create\_router\_interface and create\_port * Serialize plurals correctly in neutron xml client * Add version test for Ceilometer cli * remove last errant file parse warning * remove unneeded \_\_init\_\_ file * rename old config object to TempestConfigPrivate * Remove FloatingIPChecker from network\_basic\_ops * Fix AutoScalingTest test suite error * moving to global lazy loaded config * cinder v2 api tests - part1 * port test\_keypairs into nova v3 part2 * Refactor cross\_tenant\_connectivity for tenant isolation * Remove unnecessary spaces from messages * Add unit tests for rest\_client * Fix AttributeError on BadRequest in scenario tests * scenario/network\_basic\_ops: reassociate floating-ip * Enable HostsAdminTestXML * Add swift scenario tests * Increase support for isolated tenants in scenario * Add testcases for images * move negative tests out of test\_services in nova v3 * Skip extraroute tests if extension is not enabled * Add test\_discover module to provide a load\_tests hook * scenario cross\_tenant\_connectivity * attach\_interfaces as smoke * SSH connection related cleanups * Separate negative tests for test\_fixed\_ips * Add negative tests for network * Fix cinder test cases when cinder extensions are in use * Use rand\_uuid and remove unused client for clean-up * Fix the help message for run\_ssh * Fix three accidentally formatted paragraphs * clean up invalid\_multibyte test * provide a valid utf8 multibyte test for nova images * Refactor network client * port certificates tests into nova v3 part2 * port certificates tests into nova v3 part1 * port instance\_usage\_audit tests into nova v3 part1 * port related volumes tests into nova v3 part2 * port related volumes tests into nova v3 part1 * add some tests for aggregates * add some negative tests for flavor * Add volume extensions tests * Add Savanna client for node group templates * Add Savanna-related configs for testing * Adds ping method to remote client * Separate negative tests for test\_availability\_zone * Test for the agent management extension API * scenario/network\_basic\_ops: detach floating-ip * Make negative snapshot tests faster * Move common wait\_for\_image\_status from compute images\_client to waiters * Set pipefail for wrapper scripts * actually turn on neutron cli tests * Add whitelist entry for s-proxy 'Timeout talking to memcached' * don't fail on dirty logs with grenade * Run test\_service\_type\_management test only if extension is available * Fix failures when l3\_agent\_scheduler ext is not available * Add a control point for floating IP assignment * Add new env variable to specify test path * Remove duplicate negative test of flavor\_id * port test\_keypairs into nova v3 part1 * avoid resource leaks in keypairs tests * remove a spurious wait that could get us into trouble * Only initialize the glance\_http if service is enabled * add both v2 and v3 tests for get specified extension * Wait for backup images to be ACTIVE in test\_create\_backup * Make the wait\_for\_server\_status timeout message a bit more clear * Tighten ERROR regexp in log checker * Don't have tox install pre-release software * Fix the scope to share a server between tests * Add testcases for security groups * Change unstable test which gets console output * Heat: check response fields * Make Heat's non\_empty\_stack usable without a server * Fix Neutron VPNaaS Test * Remove generic\_setup\_package() function * Updated from global requirements * Add tests for keystone OS-TRUST v3 API * Separate negative tests for list\_floating\_ips * Fix the upper values of test\_network\_quotas * Add the external gateway interface to vpn router * Race condition in ListImageFilters tests * Separate negative tests in flavors/test\_flavors * Add config options for enabled extensions * Handle rest client 500 response if non-json body * Fix display\_name of volume for test\_volumes\_list * Test for the update extra route * Modify the name of a negative test class * add admin server tests * Add testcases for volume * Moves negative tests from api/compute/servers/test\_virtual\_interfaces * Separate negative tests for test\_services in Tempest * remove test\_service\_enable\_disable in nova V3 tests * port test\_server\_rescue into v3 part2 * Add mock to test-requirements.txt * Update tempest hacking regarding unit tests * Remove redundant whitelist for DHCP agent * VPNaas IKE policies tests * Check HTTP response headers of Swift middleware API in detail * Skip autoscaling test until more reliable * Move import to import block again * Fix a minor error in method \_get\_alternative\_flavor * Add tests for testing swift slo middleware * port test\_simple\_tenant\_usage into nova v3 part2 * port some flavor tests into nova v3 part1 * remove the test: test\_service\_enable\_disable * Remove unused allow\_tenant\_reuse flag * update to hacking 0.8 * Adds paramiko logs to console output * Rip out the coverage extension client from tempest * port test\_server\_metadata and test\_server\_personality into v3 part1 * port test\_quotas into v3 part1 * Negative tests separate file for floating\_ips\_actions * port some server tests into nova v3 part1 * Add internal testing for the stress test framework * port test\_server\_rescue into v3 part1 * add some test for force delete: * Fix rebuild\_server() function * Adds "list hosts" test case - Cinder * Adds improvements to the Swift TempURL test * Use correct types for thresholds * Revert "Add test\_cases for cinder cli in v2 version" * Start failing logs with errors except neutron * Fix file print logic bug and update whitelist * port test\_hypervisor into nova v3 part2 * Moves negative tests from api/compute/keypairs/test\_keypairs * Some tests for dhcp agent scheduler * Support Neutron security groups in scenario testing * More stuff in the whitelist * Dump all error messages for neutron * Add missing "interface" argument to AltManager * Use channel\_timeout for SSH connection timeout * Add two more tests for Swift staticweb middleware * Add some tests for os\_update\_readonly\_flag * Add test\_cases for cinder cli in v2 version * Ensure no dangling resources are left if tests are skipped * port test\_simple\_tenant\_usage into nova v3 part1 * port attach\_interfaces and server\_address tests into v3 part2 * Add hopefully last batch to the whitelist * Enable a uuid flavor * Improve the UX around sample config generation * Add hard reboot for outputting console log * Adds delete api test to glance * Check HTTP response headers of Swift API in detail * port servers\_negative tests into v3 part1 * Add migrate negative tests * Enable the Nova V3 API Tests * Add sample config check to tox pep8 job * Move admin client initialization into stress-openstack * Cleanup using about the data\_utils module and functions again * port test\_availability\_zone into nova v3 part2 * Cleanup using about the data\_utils module and functions * Make sure ssh retries on paramiko.SSHException * Add test for Swift formpost middleware * Add test for update/reset\_snapshot\_status API * Avoid deleting ports assigned to router interfaces * Rename object storage features section * Enhance tests for flavor\_extra\_spec API * Skip test\_create\_backup * Disable V3 tests * Forced isolation for the tests in test\_list\_servers\_negative.py * port instance\_actions and server\_list tests into nova v3 part2 * port instance\_actions and server\_list tests into nova v3 part1 * Don't use duplicate IP addresses * add some negative tests for security group: * Moves negative tests to test\_images\_oneserver\_negative * add tests for server\_password * Update openstack/common/lockutils * port test\_hypervisor into nova v3 part1 * Add test cases for volume-transfer * Add some tests for security\_group\_rules api * Remove unused code in user test * Test for service type management * add role negative tests * Make smoke tests parallel * Add special serial smoke option * Adds negative tests to glance api's * port test\_extensions into nova v3 part2 * Reuse a server instance in test\_disk\_config * Remove unused code in tempest files * Enable all nova v3 tests * Adds tests to cover Swift's crossdomain middleware * Fixing ImageKilledException raising * Add negative resize server action test * Added images support and existing config support * add tests for InstanceUsageAuditLog * port attach\_interfaces and server\_address tests into v3 part1 * Add shelve/unshelve test of nova APIs * add negative volumes tests * Remove unused code in api/compute/admin/test\_quotas.py * Remove skips for bug 1182384 * removes a duplicate volume type test * add tests for certificates * Test image member is enforced * Added some tests for reserve and unreserve volume * Skip all nova v3 tests temporarily * Remove unused run\_ssh variable * Add additional documentation for stress tests * port test\_availability\_zone into nova v3 part1 * port test\_services into nova v3 part2 * port test\_services into nova v3 part1 * port test\_extensions into v3 part1 * Rename to create\_test\_server in API tests * removing dead docs * remove old README files that are now redundant * move nova v3 delete image tests into glance testing * Adapt scenario readme with real-life aspect * port test\_images and test\_server\_actions into v3 part2 * Configure scenario clients with region * port test\_images and test\_server\_actions into v3 part1 * remove redundant code in fixed\_ips\_client for xml * glance v2 image sharing tests * Test for l3 agent scheduler API * Add design principles to docs * Change all non-slow scenario tests to smoke * add positive tests for volume * Switch base unit test class to oslo mox fixture * Fix NameError exception * Make test\_show\_host\_detail work with multi host installation * Add tempest unit test to verify the test list * remove skip bug 1233026 * Add missing CLI Neutron tests * add test for create\_backup * add debugging for when changes-sinces fails * add tests for show and update of Flavor Extra Spec API extension * Add test case to api/compute/test\_quotas * Adds api test to test\_images * Sync config file generator from oslo * Fix default values so they work in a devstack run * Fix incorrect config option types * Add api version detection to verify\_tempest\_config * Add config feature verification script * Stop auto-detecting glance API versions * Reorganize project feature config options * Adds test to cover Swift healthcheck middleware * Stop testing deprecated command nova-manage instance\_type * Test for flavor-access-list with private/public flavor * edit inheritence tree api/network/security\_groups * Move decision\_maker() call into setUpClass * Add base test class for unit tests * Update mailmap for Joe Gordon * Fix bad classname * add some negative tests for force\_delete/restore * Replace assertLessEqual - is not in py26 testtools * assertTrue to assertIn * Raise specific exceptions on tearDownClass failure * Preserve the configured log level * Add some test\_cases for glance\_cli * Fix bad classname * Sync latest module versions from oslo-incubator * Sync fixtures from oslo and use LockFixture * Updated from global requirements * Update to latest pbr * Set max\_template\_size to heat's default value * Added some test for image tags * Early die if on image gets killed * Revert "Use isolation credentials for neutron api tests" * Add more stuff to the whitelist * RunTimeError on tearDownClass explained * add extend volume tests: * add tests for set\_metadata in aggregate * fix DeletableSubnet in api/network/common * Refactor duplicate isolated creds code * api/network/security\_groups\_negative add testcases * Adds initial ceilometerclient testing code * add negative test cases for create snapshots: * Fix issue with ImagesOneServerJSON test * Use isolation credentials for neutron api tests * Simplify xml/json client selection * Add filenames to skip\_tracker.py output * API tests for neutron router gateway * Use lower case true/false in the sample config file * use "" instead of "None" in xml file * Inject "-tempest-" string to rand\_name * Forced isolation for the nova quota test * add BaseV2ComputeTest as the base class of nova v2 api tests * Changed the exception name * server status remained in unexpected state * Set tempest version for icehouse * Add a skip for meter-label cli tests * VPNaas vpnservice test cases * Separate negative tests api/network/security\_groups * Skip negative compute quota tests that don't work with Neutron * uses skip\_because where appropriate instead of regular skip * Fixing ImagesOneServerTestXml issue * add test for updating server's disk\_config test * Skip get\_server\_diagnostics test until bug 1240043 is fixed * add some tests for host and seperate negative ones * Fix to use proper random values * add some tests for hypervisor operation * add volume tests * Reduce vm creations in negative tests * Change six to match global requirements * remove tearDownClass in test\_quotas.py * add more test cases to api/network/security\_groups * Add a flavor test for getting a specified key value API * Restores passing additional parameters to nosetest * Move negative action tests to right place * Script to filter logs for ERRORs based on whitelist * Rename cli tests from compute to nova * Add section for negative tests to HACKING.rst * Add some test-cases for cinder cli * add token test for token-api * add some negative tests for tenant * Test for server create with IPv6 address only * Accept gzip files in find\_stack\_traces.py * Add a create\_router utility function * Update lockutils from oslo-incubator * Add security group to large\_ops test * Use built in cleanup for servers in test\_large\_ops * Split out unit tests as separate tox jobs * Update to latest tox * glance image v1 member test cleaup * delete the duplicated variable definition * Increase heat's default max\_template\_size * add some tests for user api * Add CLI tests for Neutron's metering agent * Add some test\_cases for compute-cli * skip test\_lock\_unlock\_server * add some negative tests for volume updating: * remove code duplication in tempest/config.py * add volume list tests * Add addtional logging and catch exceptions * Placeholder log check script to be called by devstack-gate * Cleanup existing instances in setup for test\_list\_servers\_negative.py * Fix skip\_because decorator * Test for the nova diagnostics API * Use predictable instance/volume names in test\_volumes\_actions * Cleanup test\_list\_server\_filters setup/teardown * add two negative tests for flavor-access * Remove unused CONF variable and import statement * add a negative test for flavor\_extra\_specs * introduces skip\_because decorator * pass stop\_on\_error to \_has\_error\_in\_logs * Clean up existing instances when not using tenant isolation * Orchestration autoscaling improve status polling * Allow \_status\_timeout to be used for non-nova polling * TEMPEST\_PY26\_NOSE\_COMPAT in py26 jobs * Handle test\_list\_servers\_by\_changes\_since without tenant isolation * Add xml support to the floating ip and router * add server delete/unpause tests * add server suspend/resume negative tests * Revert "unskipping bug related to test\_stamp\_pattern.py" * Update style guide link in HACKING.rst * meta should be metadata in rebuild server * Unskip test\_tokens and update expected status to 404 from 401 * Do not check for id in the keystone output * Initial basic setup of openstack and tempest config file * Make smoketests working again with Python 2.6/nose * Add test for admin deleting servers of others * add server reset\_state tests * Heat: use first\_address instead of first\_private\_address * Explicity specify network for heat slow tests * \_error\_in\_logs function of driver.py shall check all nodes * Bump oslo.config version * \_get\_tenant\_by\_name doesn't return correctly * tempest/config.py parse some sections incorrectly * \_create\_creds function shouldn't call rand\_name twice * Add tempest tests for os-aggregate update * Adds more test to cover Swift tempURL middleware * Added Negative tests for image member * driver.py should log errors when ssh operations fail * fix typo in config.py * add \_\_str\_\_ function to RestClient class * Fix problem with never stopping stress tests * Print the lekaed 'boto' tags on Failure * The debug configuration group is not registered * Update docs config * Add positive tests for os-floating-ip-pools * Handling network resources in tenant isolation * Don't log 'Not Found' ERROR during image cleanup * makes passing the client optional to utilities in scenario/manager.py * Add test for nova API of VM lock/unlock * Fix json version of bootable check * Add locking to test\_list\_hosts\_with\_zone * Move LockFixture to tempest.common * Fix typos in tempest/HACKING.rst Edit * README.rst of tempest stress is misleading * add server resizing tests * add negative volume tests * Added test to check list/show extensions-neutron * Adds test\_update\_all\_metadata\_field\_not\_included negative test * Replace OpenStack LLC with OpenStack Foundation * Test that Heat max\_template\_size is applied * Sync install\_venv\_common.py from oslo-incubator * Consider lc string for bootable True/False * unskipping bug related to test\_stamp\_pattern.py * Raise OS\_TEST\_TIMEOUT for heat slow test * Add tempest tests for os-host/{host-name} api * Test cases for V3 Project Actions * Skip test tokens * Remove claim that scenario tests need 2 services * Adding health monitoring test case * Adding LB member operation test cases * LBaaS client functions and pool testcases * Add update-volume test * Use built-in print() instead of print statement * Add 'Field' to the title of the Field Guides * Update README.rst to use testr instead of nose * fix pep8 errors: E231, E128 * Adds tests covering Swift's container quotas middleware * Add a function for creating a server on a given network * Move \_check\_tenant\_network\_connectivity to the end * Add update-snapshot test * Add keystone user-update test * Fix misused assertTrue in unit tests * Fix a test case of 35chars+ * assertEquals is deprecated, use assertEqual * Add "region" config for each service * Add parallel test execution section to HACKING.rst * Sync latest versions of oslo incubator * Fix overlapping subnet creation * test\_fixed\_ips unsafe xml/json resource sharing * Enable the quota test cases * Adding xml support for load balancer * Network load balancer testing * Fixes some typos in tempest * add required python modules(six,iso8601,fixtures) to requirements.txt * Fixes typos in tempest/api * Add missing import of 'subunit' in test-requirements.txt * Remove wait\_for\_image\_resp\_code * Use common create\_server method for advanced\_ops * VolumesListTest may hide configuration issues * Add tearDownClass to base swift test class * Adding negative test for port * Bulk Subnets and Ports creation * Make the admin role configurable within tempest * Remove Whitebox tests * Remove bin/tempest script * Add service tag section to HACKING.rst * Add hacking check for service tags in scenario * Add services tags to scenario tests * added an api test for security\_groups * Adds disk\_format parameter to upload\_volume method in volumes client * task\_state must be consider before many action * Dump basic network info in the test\_network\_basic\_ops * Adds verfication for Bootable Volume * Translate server extension attributes to json * Add service tags to api.volume * Add @services decorator * Remove order dependence from network\_basic\_ops * Fix import grouping in scenario tests * Fix tempest config usage in test\_attach\_volume * Fix race condition for test\_flavors\_extra\_specs * Eliminate the impact of "wait\_until='BUILD'" * fix race condition between addCleanup and lockutils * Credentials Keystone V3 API Tests * Add a create\_server test for flavor authorization * Add device name, ssh password to Tempest config * Fix colon in create volume logging output * Append some operations to boot from volume pattern * Added test case to check floating IP API operations * Switch heat template to use native nova resource * Add tests for Swift's StaticWeb middelware * fix test\_flavors\_extra\_specs failure * Restrict Volume type deletion with volumes assoc * add flavor creation tests * Added missing xml tests of volume * raise assertion error if output is falsy * Add handling for inherited stress attributes * Remove unused LOG variable in scenario tests * Add more detailed message about the volumes missing * Increase default test timeout for timeout fixture * Log request ids from glance and nova * moves addCleanup few lines upper to avoid potential leftovers * Fix logging problem for stress test wrappers * KeyError when tearDownClass called from setUpClass * Add common "create\_server\_snapshot" method * Fix test\_admin\_catalog\_list * Always log stdout and stderr of CLI commands * Create discovery option for stress tests * emit warning while running flake8 without virtual env * Switch run\_tests.sh to run in parallel by default * Add unittest framework + tests for wrapper scripts * Move the network api tests to smoke * Add logging to the python-clients * removes self.fail as suggested by HACKING.rst * Rename heat logical\_resource\_id to resource\_name * Add large-ops option to tox * Updating HACKING.rst * Stress ssh\_floating test * Adds tests for Heat * Update requirements from global requirements * uniforms skip messages * Use common create\_keypair method for autoscaling * Skip orchestration scenario tests if heat service not available * Switch gating tox jobs to testr parallel * Provide tox entry for running slow heat tests * close http connections * Fixed up a missing space in an error message * refactor - \_is\_timed\_out using instance timeout * Fix ssh timeout issue * Heat autoscaling scenario test * cli: add messages to assertTrue * Cleanup: Add common "ssh-login server" method * Cleanup: Add common "create volume" method * use assertIsNotNone instead of assertNotEqual(\*, None) * Skip os-fixed-ips test since neutron has not implemented it * Cleanup: Add common "create security rule" method * Add tenant isolation to the swift tests * Adding network api xml support * Set missing attribute self.server in \_create\_and\_attach utility * Mismatch dictionary key in the process of parsing XML * Remove skip of neutron connectivity check * Fix skip tracker regex for multi-line skips * Add more tests for Swift Account Quota * Fixing format\_flavor to handle flavor extra\_specs * Cleanup: Add common "create keypair" method * Fixing the rest of the comment spacing issues * per test\_method logging * Remove identity race condition * Protected matcher import * Cleanup: Add common "create\_server" method * Fixed test for non-public flavor * Add nose to run\_tests and tox for python 2.6 * skip test\_list\_servers\_filtered\_by\_ip\_regex on neutron gate * Fix typos in tempest/api/README.rst * Unit tests as stress tests * Cleanup try/except/finally blocks in several tests * setUpClass/tearDownClass full chain * Testcase to create bulk networks in one API call * Added 3 Routers related testcases for Neutron API * Adding extra\_specs to flavor format * Add tenant isolation for scenario tests * Switch use of select() to poll() * Remove positive tag * Fix return code for pretty\_tox\_serial.sh * Fix posargs usage for tox jobs * Add tests for swift container listing filters * Switch to testr serial instead of nose * Rework class inheritance for scenario tests * Change logging in stress test * Added negative tests for server * scenario test involving glance, cinder and nova functionalities * Flag InstanceCfnInitTestJSON as the first slow heat test * Add test for swift ACLs * Adds tests covering Swift's Account Quota middleware * Remove duplicate image tests for tenant authZ * Skip more security group tests until bug 1182384 is fixed * Skip secgroup invalid name/desc tests until bug 1161411 is fixed * Make CLI timeout tests configurable * Added a test for stop and start a server * Add locks for all aggregates tests with hosts * Test cases for Roles V3 Actions * Add network api test cases * Whitebox server expects deleted > 0 * Negative tests added for network * Negative attribute added in server negative test * Skip secgroup rules invalid id tests until bug 1182384 is fixed * Make testr full runs skip tests with 'slow' tag * renames the stress test class to include the Volume keyword * Generate temepst API doc from source * White space after # in tempest/api files * White space after # in the tempest/services * White space after # in thirdparty * Disable logging to the stderr * add image tests v2 * Add argument to stop stress test on first error * Add testing of Neutron per tenant quotas API * Switching to oslo importutils in the stress tests * Make test\_neutron\_dhcp\_agent\_list\_hosting\_net use net name from conf * Sync up the default value for the network\_for\_ssh option * Sorting services list before asserting in test\_get\_service\_list test * Negative test added for rescuing a paused vm * Add tox job for serial testr * Add eventlet to requirements.txt * Fix fail logic for server of another tenant test * Add locking to test\_aggregates * Switch to using Oslo logging * Fix neutron cli tests skip for testr * Use state transition checker wait function in the ec2 image tests * Add wait\_for\_volume\_status in upload\_to\_image test * Handle a possible volume attachment visibility wait/race issue * Use assertIn and assertNotIn instead of assertTrue/assertFalse * Add stress test to attach volumes to vm's * Negative attribute added in volume negative test * Revert "Add jsonschema to requirements.txt" * Allow to run swift and keystone api tests standalone * skip stamp test until race in nova can be debugged * Sync lockutils and log from oslo * Add environmental variables to test.conf * fix race condition in service list compares * Add posargs to testr-full tox job * Add Neutron CLI tests to tempest * Unskip test\_register\_get\_deregister\_ari\_image * Add jsonschema to requirements.txt * Simplify whitebox/manager * Added a server-pause test * Negative test for rescuing a non-existent server * Increase ping timeout on scenario testing * Basic starter scenario for testing the dashboard * Fixing man page generation * Use nose skip exception conditionally * add service tests * Add tenant isolation for glance api tests * Move isolated credential code to BaseTestCase * Add isolated tenants for admin compute tests * Add exit codes if run\_stress.py detects an error * Move heat\_available option to service\_available * Add nova config option to service\_available group * Add swift config option to service\_available group * Add glance to service\_available config group * Move neutron\_available option to service\_available * Ignore the default quota values * Updating HACKING with some test writing recommendations * update hacking to 0.6 * Add global statistic for stress tests * Add scenario test of instance/volume snapshot * add image tests * Add cinder\_available config option * Adds tests for tags in boto (EC2 API) * add missing apache2 license headers * fix use of locals() in strings * Skip test that is not relevant to Neutron * Add boto tests for idempotent RunInstances calls * Remove unneeded class filter from .testr.conf * Document E125 as a won't fix * Add unittest like output for testr-full in tox * Use subunit colorizer from nova for run\_tests.sh * run\_stress.py -h doesn't work without a connection * Remove duplicate flaky test * Remove skip code for test\_servers\_whitebox as bug had been fixed * Fix stress-tox-job.json action file * Switch to using testr as the test runner for everything non-gating * Rework stress to be more UnitTest like * Add an option to run\_stress.py to run tests serially * Fix tox job for stress tests * Change path for "Run one test" example in README.rst * Make tenant\_network\_mask\_bits default setting consistent * Remove version caps from python-fooclient * Cleanup and make HACKING.rst DRYer * Test Tokens - V3 * Fix SSH host to floating IP from fixed IP * Configure a heat security group for testing ssh * Add large\_ops scenario test * improve minDisk checking * Add new test for the volume upload in Glance functionality * Add cinder CLI tests to tempest * Fixed test - segmented upload of file * Domain Actions Test Case-V3 * update Quantum to Neutron * Using relative path for personality file in rebuild server test * Sync install\_venv\_common from oslo * Add tests for server actions * Add exception handling doc * Postgresql is an Optional dependency * Use the same logger class in all test cases * add tests - list hosts tests using admin user * Proposing a change to remove user validation tests * Fixes a test case name * Add a tox job to run stress tests * Orchestration tests to use admin user, demo tenant * Use build\_timeout & build\_interval from heat config * Create stack in class setup, rather than instance * Implement update\_stack client method * Always include credentials for heat stack create * Use print\_function * Fix index link in footer bar * add tests - list servers using admin user * Clear keypairs on teardown * Heat test ssh to the server * Skip fixed\_ip\_details test with Quantum * Skip list\_vifs test with Quantum * Revert "Update a test to work with Quantum SecGroup" * Use os.path.join to form the cli command * Skip test that is not relevant to Quantum * CLI doc missing colon for code block * Configurable volume storage\_protocol + vendor\_name * Add scenario test of instance snapshot and boot * Remove dependency on MySQL-python * Use Python 3.x compatible syntax constructs * Oslo Merge * Remove automated skip decsion from compute * Avoid potential race condition in list\_stacks assert * Fix create\_security\_group\_rule to work with python 2.6 * Fix test\_volumes\_get volumes cleanup * Add a volume create/delete stress test * Ensures testtools 0.9.32 * Actually raise BadRequest if create\_subnet fails * Fix XML security group rule client * Loosen constraints on Swift status codes * Add posargs to tox coverage job * skip test\_ec2\_instance\_run.InstanceRunTest.test\_integration\_1 * Object client adds content-length if PUT is empty * Fix a doc indentation bug * add create\_image\_from\_server to base class for auto cleanup * Implement a new test case for volume cloning functionality * Remove TODO note * actually enable our no tempest/tests check * Raise BadRequest if unable to create subnet * Remove basic\_auth strategy * Add negative tests label * Increase to 255 the length of the user name * Remove quota whitebox tests * Tempest Coding Guide * Quantum client should not be conditional * Fix: WhiteBox server leak * Tests for os-hypervisors API extension of Nova * Fixed Typos * Add logic to tox.ini to run all tempest tests * Enhance the validation of the quotas update * Fixes list\_snapshots and \_with\_details methods in snap XML client * ensure no code merges to tempest/tests * Fixed last merge file from "tests" folder to new "api" folder * TestServerAdvancedOps server leaking * Multi-server handling in base.py * Fix a race condition in test\_create\_delete\_image() * Remove duplicate appends to image list * Add keystone client optional arg tests * List servers by changes since with dynamic date * Default to m1.micro for heat test flavor * Add python-cinderclient to requirements.txt * Removed invalid skipped tests * Fixes the multi-backend skip bug and the cleanup steps * Remove executable bit on some files * Makes run\_tests.sh exit code match the nosetests exit value * Unskipped object storage test * Added test - conditional object download * identity v3 token * List Domains Test Case-V3 * Rename requires files to standard names * Remove skip mark to test\_create\_image\_when\_server\_is\_rebooting * Removes 'positive' tag from tests * Adding test\_server\_sequence\_suspend\_resume * Implement minimum basic scenario * Add ssh check to quantum smoke test * Heat test to launch a heat-cfntools based instance * Merged 2 tests dependent on each other * Fixes bugs in test\_s3\_ec2\_images * Test cases for Policy V3 API-New * Add Flake8 extension for python client import checks * update docs for consistency * Moved swift container cleanup to a class method * Configure logging format flexibly * Update skip\_tracker test directory * Update README file * Modify hacking flake8 extension * Limit tests should pass correct image and flavor * Update a test to work with Quantum SecGroup * Set smoke/gate attributes for tests in "compute" * initial seed for tempest doc directory * Add some basic snapshots listing test * Initial heat orchestration tests * Add tests list tenant usage * Remove unnecessary tags/attributes from our tests * rename tests -> api * Smoke attribute implies gate attribute * Moved part of test cases to another class * break out whitebox tests * move boto tests to thirdparty directory - part 1 * add scenario directory * Create Flake8 extension for tempest checks * Code cleanup of object storage tests * Removing redundant, possibly flaky test * Add a sleep with back-off to retries * Add tests list the services to compute * Set smoke/gate attributes for tests in "image" * Set smoke/gate attributes for tests in "identity" * Set smoke/gate attributes for tests in "volume" * A Heat client which does basic stack operations * make test non executable, otherwise nose skips it * Permits a list of values for the "type=" tests attribute * Adding new test for iSCSI live block migration * Clean up tenants created in test\_users.py * Adding negative test to check limits of Security Groups and rules * collapse tox.ini test commands for readability * move cli directory into new tree structure * make status\_timeout a method * Enhance the validation of the quotas update * only install things in the venv for pep8 * proposal for tempest directory restructure * Do super()'s tearDowns last (bug 1178337) * Put the logic from devstack gate into tox * Remove old stress tests * Simplified stress tests * Enabling assertions disabled for bug #1074901 * Declare the config attribute in a simpler way * Configurable fixed network name * Add Aggregate XML client and tests * Make sure isolated tenants are not left behind * Add setup failure logging in tearDown method * change test\_register\_http\_image to use explicit url * Ignore .testrepository/ * Adds Cinder Multi-Backend Test * Update service test case - V3 * Add a test for creation of volumes' snapshots * Remove reference to dead script configure\_tempest.sh * Migrate to pbr from openstack.common.setup * Finish up flake8 conversion * Fixes volumes cleanup in base class * Removed duplicate usage of TempestConfig() * Fix status\_timeout incorrectly referencing self * Added 2 user related testcases for Keystone V3API * Fix to enable negative test image\_invalid\_metadata * Missing image-del func in test\_create\_delete\_image * Clean up servers created in test\_multiple\_create.py * Add tests list actions on a specified instance * Remove skips in test server rescue * Added tests to list instances by regexp * Adding test\_max\_image\_meta\_exceed\_limit * Add tests list the availability zone * Make CLI tests python2.6 compatible * Exclude etc/logging.conf from versioning * fix E122 and E126 flake8 issues * attempt to get to flake8/hacking plugins * Add fixed ip client and tests * Fix to create an image with given min\_ram value * Add logging configuration * Add aggregate json client and tests * Configure quantum basic ops tests as smoke * Fix to use an API to get the default quota set * raise the first exception in flavors and security\_group test * Fix HACKING compliance of test\_network\_basic\_ops * Remove smoke.py and clean up base test classes * make stack traces tool find individual traces * Re-enable detach volume from unrescued VM * Fix docs for admin user config in conf sample * Create tests for multiple server creation * Fix AttachInterfacesTest error * Adds create volume from image test * xenapi:live-block-migration - fix XML client test * Add tests for adding/removing flavor access * Test cases for Endpoints V3 API * RestClient:keystone\_auth hides requests and errors * Removes redundant tearDownClass methods * Fix IBM copyright strings * Set version to 2013.2 * Remove skips in quota tests * Fix typo for run\_tests.sh -S option * Replace try/except/else with self.assertRaises * Add basic quota tests * Update test\_networks.py to v2 of Quantum API * refine \_get\_isolated\_creds * delete servers in setUpClass if ecxeption raised * Reduce chance of name collision for resources * cleanup resource in setUpClass if ecxeption raised * Add missing exceptions import * Remove unused variables * Add glance register image from http service test case * Use the same style checking everywhere * Xml Support for Image Test scripts * Addition of XML support to test\_list\_servers\_negative * In CLI tests set merge\_stderr to False by default * Fix SimpleReadOnlyNovaManageTest.test\_flavor\_list CLI test * Move tempest runtime dependencies to the pip-requires * Remove unused imports * Create and delete flavor as regular user * Make volume attach and detach rescue tests negative * Add basic read only glance cli tests * Add properties to CreateRegisterImagesTest case * enable xml tests test\_disk\_config * Update hacking.py for @testtools.skip() formatting * Normalize skip bug format * Refactor of test\_network\_basic\_ops -prep new tests * correct the reduplicate tests for list\_severs\_with\_detail({'limit':1}) * Remove skips in volume types tests * Adds XML support to test\_live\_block\_migration.py * Remove skips from bugs marked as fixed * add test for creating a floating IP specifying an non-existent pool * Updating the try/except blocks to assertRaises * Add bug number for skips in CLI tests * Add basic Keystone CLI tests * Add service cleanup handler for test\_list\_services * clean up trys in test\_servers * Replace try/except/else with self.assertRaises * Extend compute-manage cli tests * Make skip\_tracker bug keyword detection more robust * Expand CLI test * Tweak quotas in tempest to include new fixed ip quota * Change server create to use tracked create\_server * Convert try/expect/else as per new Tempest style * fix the confused issue in server\_client about list\_server * Fix exception name in test\_server\_actions * Fix skip formats to trigger skip\_tracker * Remove unnecessary asserts from test\_images.py * Add api version detection support to glance tests * xml\_to\_json should not convert xmlns into attribute * Add base classes for image tests * Convert try/expect/else as per new Tempest style * Add a test to list the security group rules * Disable test\_rescued\_vm\_attach\_volume * Replace try/except/else with self.assertRaises * add find\_stack\_traces tool * Remove skip decorator from keypairs tests * Switch to final 1.1.0 oslo.config release * Add duplicate bug detection to skip\_tracker.py * fix sever not deleted issue in test\_attach\_detach\_volume * Replace try/except/else with self.assertRaises * Fix rate limit handling and logging * Tests to verify Nova VM Rescue operations * Fix test\_flavor\_extra\_specs * Remove skips for fixed bugs * Add image members tests * Use oslo.config-1.1.0b1 * Have paramico to register the event pipe in time * Remove skip from test\_invalid\_host\_for\_migration() * Add glance api v2 testing * RestClient remove wait parameter from the get method * Small fixes around variable usage * Enable XML testing for test\_server\_addresses * Correct getchildren() usage in list\_addresses() * Adding test\_security\_group\_rules\_create\_with\_invalid\_port\_range * Adding test\_delete\_the\_default\_security\_group negative test * Move glance image client and tests into v1 dirs * skip ec2 test until it can be debugged * Add tests for nova's os-attach-interfaces extension * Add quantum\_available config option * Update live migration test to use new syntax for create\_server * enable test\_servers\_negative * Fix endpoint usage for glance\_http in image client * Sync latest setup.py from oslo * Add negative test in test\_quotas.py * Update defaults for s3 materials paths/names * Catching new exception while disassociating a disassociated floating ip * More assertions for test\_integration\_1 * Add basic image filtering tests * add the version requirement for testtools * Standardises expected exception layout * convert to resource tracked create\_server * create\_server cleanup * test\_server\_metadata.py - BP add-xml-support * add database drivers for whitebox testing * Sync latest install\_venv\_common.py * Update stress tests to properly use tempest.config * Prepare base test class for CLI tests * Implements test\_update\_all\_metadata\_field\_error * Catching new exception while disassociating a disassociated floating ip * Add tests for server metadata * Move the console tests to the other server actions tests * Simplify xml-json inheritance in identity * test\_live\_block\_migration cleanup * Implement assertRaises assertions on all tests * Small server action code compression * Handle XML body of server's virtual interfaces correctly * tempest.tests.boto merge to tempest.testboto * Cleanup of identity/admin/test\_users.py * Handle error in test\_create\_get\_delete\_service * Expand read only cli compute test * Add negative test for get server in test\_servers\_negative.py * Add negative test for set server metadata * Clean up logging from glance\_http.py * Have all test case to use a single base class * Simplify xml-json inheritance in compute * update identity to handle new table attributes * Fix update option for run\_tests.sh * Compare ipv6 only with canonized form * Adding list\_virtual\_interfaces method to the servers\_client * Stress improts * T4xx fixes * Add object based wait capability to boto tests * Fix compute tests init * Add an update option to run\_tests.sh * Modify roles tests to deal with a default role * Imports in alphabetic order * Remove skipped test for bug 1061738 * By default the features are not skipped * Configurable Tempest config file location * First commit for python client test suite * Fix init of test\_volume\_type\_extra\_specs\_list * Sync in latest version of oslo * Remove duplicate calls to clear\_servers * Simplify volume test classes inheritance * Add missing import to the image\_client * Remove unnecessary client alias in console tests * Correction in quota\_client's condition logic * Fix 'if' in the clear\_isolated\_creds * json name usage * Update HACKING file * Fixes test\_resize\_server\_(confirm|revert) methods * Add an images client * Break out RestClient error checking * Fixes around variable usage * Remove unused imports * Add negative test for create server * Change quota tests to use assertEqual * Remove duplicated wait * Convert to use tempest attr implementation 2/2 * Convert to use tempest attr implementation 1/2 * Fix MismatchError error when checking flavor value * Implements test\_(create|update)\_metadata\_key\_error * Implements test\_flavors.test\_invalid\_min(Ram|Disk)\_filter * Enable test\_absolute\_limits.test\_absLimits\_get * Add generic nose/testtools attr decorator * Add support for testrepository * Implements test\_flavors.test\_invalid\_is\_public\_string * Change test\_get\_default\_quotas to use assertEqual * Sync latest install\_venv\_common from oslo * Implements test\_flavors.test\_is\_public\_string\_variations * Removes use of nose.tools.raises * Small Bug fixes * Verbose logging on error * Remove variable assignment that appears twice * Add a volume from snapshot test case * Make isolated volume tests have unique tenant * clean the unittest2 * Fixes "not in" usage * Fixes duplicate "-s" option in run\_tests.sh * Use real capabilites for all volume type instead of fake ones * Move out http response checking from the request * Adds unitest2 and keyring to pip-requires * Fix boto initialization * Fix volume XML tests * Proposed EC2 OpenStack extension * Remove use of detailed-errors nose plugin * Removes assertGreaterEqual for Python 2.6 backward compat * Enable boto keyapir test * Testcase for keystone - List services * bug 1110343 Fix missing config.network.username * Derivate most of the RestClient's exception from the failureException * Update TEMPEST\_README.txt * Move the singleton to a common location * Addition of XML support to test\_quotas.py * Use install\_venv\_common.py from oslo * Fix Py2.6 dict comprehension SyntaxError * Handling rate-limit for JSON request- rest\_client * Use testtools as the base testcase class * Add whitebox section * Add NotImplementedError to the abstract method * Remove not used configuration variables * Fixes whitebox testing for deleted type change * Credentials Configuration changes * update whitebox testing for deleted type change * Remove few unnecessary backslashes in ObjectTest * Ensure package-wide test init is done with testr * Flavor min memory tests * Test to check container synchronization * Fixes test name typo * TestCase to check set/get/unset flavor extraspecs * Add back missing return in ObjectClient * bug 1101184 add new test: verify new n/w visible * Fix flavors tests so they can be run in parallel * Test to upload object in segments and download it * Fix PEP8 compliance problems * Refactor identity * change ipv6 format to pass on postgresql * Revert "Split up XML and JSON testing." * Split up XML and JSON testing * Test to check temp url support * Fix install\_venv-get\_distro failure on Fedora * Only create 1 server for server actions tests * Allows identity endpoint to be specified as URI * Add skips for tests when dependency not present * Adds setting to disable SSL cert validation * Removes paramiko dependency from test-requires * Object write/read ACL and few security testcases * Fix test\_authentication\_with\_invalid\_tenant * Fix test\_authentication\_when\_tenant\_is\_disabled * Add CentOS-specific OpenSSL package installation * Remove skips that are no longer necessary * boto: instance teardown wait until instance is gone * Don't ignore exceptions * ensure setup\_test\_user has been called before using test\_user * Convert these tests of ServerBasicOps into one test * make skip\_tracker directly executable * Refactor compute image tests * Use '-m' for tempest\_coverage.py in tox.ini * Remove skip from Server Filters Tests * Fix list images xml deserialization * Add missing methods to xml admin\_client * ensure we wait for server deletes * Disable test\_run\_terminate\_instance * Use real capabilites for volume type instead of fake ones * Add wait for resource deletion on volume teardown * Update gitignore because of oslo setup.py * Modified Server Actions Create Image test * Fix tenant leaking in test\_tenants.py * Adds 3 additional tests to test\_flavor.py script * Add tools/tempest\_coverage.py script * Fix parsing of addresses. lp#1074039 * Fix T401 and T402 errors * tempest error codes should start with T * Fix boto lib config * ensure isolated test cases run with an isolated tenant * add hacking.py rule to prevent docstrings * fix file injection test to not assume /etc is present * convert docstrings to comments * convert docstrings to comments * convert docstrings to comments * convert docstrings to comments * Fixes PEP8 error E121 * Fix venv for ./run\_tests.sh -p * Use oslo cfg module for tempest config.py * Initial Oslo sync for Tempest * Add python-quantumclient to pip-requires * add run\_tests.sh option to not capture stdout * Addition of XML support to test\_console\_output.py * Logic in rest\_client incorrect "resp.status=413" * Tests to check few object actions anonymously * Remove unused imports * Add error handling to smoke test cleanup * Do not limit the max versions in the requirements * Negative Cinder tests for Volume Types,extra specs * Fix sample conf for compatability with devstack * Specify region by name * Fix use of venv in Tempest * Test Case to check "swift object expiry" * RestClient: Don't hard code volume service name * Add smoke tests for quantum * Add admin credential config for network client * Fix smoke tests to delete resources synchronously * Test to GET public-readable container's object * ensure servers are deleted between tests * add create\_server\_with\_extras * Check images by ids, not by count. lp#1088515 * Add num\_retries configuration option * Tempest should ignore SSL certificate validation * Adds paramiko to pip-requires * Ensure we check for the right body * Add ability to skip disk\_config tests regardless of extension status * Improve pep8 checks to be similar to those in nova * Fix pep8 failures in tests for Volume Types and extra specs * fix formatting errors to help debugging * Fix typo that causes NameError: global name 'exception' exception * Enable EC2 Create volume from snapshot * Enabling the tests of floating ip script test\_floating\_ips\_actions.py that were skipped due to bug #957706 * Fix pep8 violations in stress tests * Assign TODO to committer * Fix use of except in tempest * Fix import order to comply with import ordering rules * Reenable security group related test case * Remove tempest.conf.tpl * Adds Cinder tests for Volume Types and extra specs * early failures would prevent cleanup * Make parameter list generation consistent using urlencode() * Check for the canonical form as well, either is valid * Skip tests broken by nova b u g 1086980 * Start making setup.py similar to other OpenStack Projects * Empty Body testing according to the RFC2616 * Remove unused configuration variables * Test Case to check "copy object across container" * Started consolidation of disk config tests * Fix and simplify arbitrary\_string. lp#1085048 * Add swift object versioning test case * Simplify parse\_image\_id * Fixed potential unbound varialble errors on test failures * Don't try to cleanup volume that doesn't exist * Fix issue with 404 logs on wait for delete * Fix pep8 failures in test\_ec2\_security\_groups.py * Don't log stack trace on S3/EC2 client errors * test\_absolute\_limits.py to check limits response * Adds a Quotas client for Nova * Spelling: executng=>executing * fix some typo * Add start of the EC2/S3 API testing to tempest * Adds JSON client for servers admin API * fix for Bug1078481 * use deleted=False instead of deleted=0 in queries * be specific about metadata too long error * make it possible to run only one test in tempest * exclude venv directories from local pep8 * Make assertion failures more informative * Added Swift tests: \* account: delete account metadata \* container: retrieve/delete container metadata \* object: retrieve/copy(2 ways) object Syntax bug fix in container\_client.py: return resp. body => return resp, body Fixed passing headers parameter in head method in rest\_client.py: return self.request('HEAD', url, headers) Removed unused imports * Remove unnecessary test. Fixes bug 1072841 * Fix SyntaxError: invalid syntax - comma * Remove kong. Fixes bug 1052511 * General cleanup/organization of compute tests * flavors with disk sizes of 10 10 20 30 would fail unexpectedly before since flavor[1]['disk'] == flavor[0]['disk] * Initial add of Swift tests * Fix 'message' is not defined errors * Handle ImportError's when quantumclient is missing * Added missing import for SkipTest in test\_authorization * Tempest tests to cover live-block-migration * Refactor list servers negative test and fix remaining gate bugs * Put skip at top level * Add XML support for test\_security\_groups.py and test\_security\_group\_rules.py * Clean up pep8 E125 violations * Clean up pep8 E123 and E124 violations * Clean up pep8 E127 violations * Clean up pep8 E128 violations * Clean up pep8 E711 violations * Passing endpoint makes authenticate lazy so getting catalog fails * Add XML support for test cases under identity admin * Clean up pep8 E502 violations * Add XML support to the cinder client * make the rand\_name value shorter * Update test\_associate\_already\_associated\_floating\_ip * Add support to XML in images\_client and its tests * Add XML support for test\_list\_floating\_ips.py * Adds client API and tests for volume attachments * Fix test\_rebuild\_nonexistent\_server * Refactor status\_timeout() methods in tempest.test * Add XML support to the volumes tests * Add XML support to the volumes client * Add lxml to pip-requires * Add .tox to .gitignore * Replace glance.client with python-glanceclient * Fix ssh.Client retval and deadlock danger LP#1038561 * Fix test\_create\_server\_with\_unauthorized\_image * Re-enable 3 flavor tests * Fix bug 1005397 * Fix undefined name 'nose' * Added missing imports in tempest.tests.compute.base * Fix forgotten import of \`exceptions\` * Provide more default clients for smoke tests * Comment out flakey failing tests * Adds Cinder client * Ensure token refresh. Fixes bug 1044316 * Add \*.egg-info to .gitignore * Add XML support for extensions\_client * Add XML support for flavors\_client * Add XML support for test\_attach\_volume * Add XML support for test\_security\_groups.py * Prevent stale isolated tenants from blocking test runs * Add xml support to keypairs\_client and its tests * Resolves lp#1042541 * Add XML support to the server personality test * Add XML support to the limits client * Fix XML formatting for create server personality * Add XML support for test\_create\_server.py * Add XML support for test\_server\_actions.py * Add XML support for test\_list\_server\_filters.py * Add XML support for test\_servers.py * Add XML support for test\_images.py * Add an xml/servers\_client.py implementation * Attempt to clean up any servers we left behind after a test * Add a RestClient variant that sends and expects XML * Skip tests that are causing tempest gate to fail * Fixes LP Bug# 930482 - Test for security -tenanid by pass * Skip whitebox tests until they are fixed * Resolves lp#1033757 * Match name of test class to filename. Fixes bug 1006193 * Addresses lp#1004971 * Addition of base Smoke and Whitebox tests * Fixes bug 902402- Integration Testcases for Keystone - users, Roles, and tenants * Fixes Bug 1031639: admin\_client.py- 'Assign and Remove role to user' points to a different URI * Tolerate set\_admin\_password not implemented * Fix for bug 1025552- Modifies test\_servers\_negative.py script * Fix Bug1029936 :SKIP TEST removal and change of Bug ID * Fix Bug1029334 :Skip Test removed from test\_volumes\_negative * Fix for Bug 1029792. Added Documentation Strings to test cases in test\_console\_output.py * fix for Bug 1029015.Added single quotes to remove unnecessary space in msg * Disable ConfigParser interpolation (lp#1025993) * Fix checks in server listing only lok for an ID * Remove skip for bug #984762 * Remove skips for resolved bugs and fixed some coding errors in tests * Fix logic on alt user tenant/password check * Fix an unbound variable in setup\_package * Fix NameError in list image filters test * Adds a script for tracking bug skips in tempest * Optimized and reduced the scope of smoke tests * Add skip for disabled user test until associated bug is fixed * Fixes bug #1016042 - New tests for security groups * Add tests for volume attach and detach * Remove duplicate line cls.floating\_ips\_client = os.floating\_ips\_client from tempest/tests/compute/base.py * Refactor Tempest to be parallel-test friendly * Add BaseComputeTest.wait\_for and use it to fix bug 1017932 * Skip slow/buggy soft reboot test until bug 1014647 is dealt with * Comment out broken test involving soft reboot * Step 1 of refactoring compute tests * Wait for resource deletion after 202 response. Fixes bug 1007447 * Fix LP #1006198 - Network tests should be skipped if no Quantum * Fixes lp:1003476. Adds negative tests for images * Setup Nose multiprocessor config for: * Fixes bug 1006405-Additional test cases to be added to test\_volumes\_negative.py * Wait for server to be deleted before reboot/rebuild. Fixes bug 1006586 * Fixes bug 992088- Testcases for Console Output and one test case to test\_authorization.py * Deleted flavors can be viewed ... but not listed * Adds admin tests for roles and roleRef API * First cut of Network client and positive tests * Fixes bug 992721- Metadata testcases for authorization testcases * Fixes bug 972130- Testcases to CREATE, GET, DELETE, FILTER volumes * Added an AddImageException to exceptions.py and modified images\_client to use this exception rather than BuildErrorException * Fix floating ip tests by adding missing an import * Fixes Bug 1004138 - Fix for Bug 992275 Breaks on Python < 2.7 * Added tests for servers API as per bug/992299 * Fixes Bug 992167-Some new tests to be added to test\_security\_groups.py and test\_authorization.py * Fixes Bug 992215-Some new tests to be added to test\_images.py and test\_authorization.py * Fixes lp:1002135. Minor re-factor to rest client * Fixes bug/999647. Adds negative tests for LIST and GET servers API * Adds instance\_utils library and initial SSH tests * Fixes bug 992275-Some new tests to test\_floating\_ips\_actions.py * Fixes bug 972673-Incorrect named parameters in BuildErrorException in test\_volumes\_list * Fixes bug 992149-Some new tests to test\_keypairs.py and test\_authorization.py * Fix LP #992228 - Test rebuild/reboot of deleted server * Adds negative tests for Identity Tenants API * Fixes bug/902405. Adds users tests and methods to admin client * Add resources for floating\_ip, keypair, volume. Add floating\_ip test * Skip blank role name test until resolved upsteam * Minor fixes and docstrings updates * Test cases for keystone tenant operations * Fixes LP #992640 - Volumes sometimes not cleaned * Moved parse\_image\_id() to data\_utils * Fixed the LP bug 993754. Ensure that the server created in the test is destroyed in finally: block of the test * Fixed the LP bug 993739 * Adds key\_name parameter in create\_server * fix for bug 992877 * Ensures that floating IP created in test is destroyed in a finally: block * Fixed LP bug 991806. Ensures that floating IP created in test is destroyed in a finally: block * Adds an identity admin client and API tests for keystone roles * Fixes LP #992096 - Add configure\_via\_auth=False * Addresses lp#948243 - Tempest handles misconfig better * Remind user about log\_level * fixed the bug 983856. Pep8 complient made * Added keypairs negative tests, removed unused client objects * bug 985867: remove conf\_from\_devstack in favor of devstack.git/tools/configure\_tempest.sh * Clients subclass the RestClient to allow attributes to be overrided by each client and allow better code reuse * Do not assume network names * Don't pass None for any values in post body * Allow skip of disk\_config tests that require resize * Don't pass disk config as None. Fix for bug 980119 * Add License to Tempest * Use \`username' in ImagesConfig * Fixes LP 973338 - Add custom alt and admin manager * Adds config file template (for use in gate script) * Convert to UNIX line endings * Remove obsolete config file * Enabling flavor marker tests * Fixes lp##971527 * Properly handle skipping if no alt user * Fix and simplify reboot stress test. Add new env stuff * Refactor configuration setup and document config * Adds basic tests for disk config extension * Fixes bug 960864- Testcases for the action list Volumes and list Volumes with Detail * Avoid new bug 963248 * Fix unbound local variable 'password error * Generalize configuration for controller access * Fixes lp#960647 * Fixes bug 902374-Negative tests for Volumes * Addresses lp#940832 * Remove glance dependency. Fixes bug 944410 * Fixes bug 938953 parsing of image id * Fixes LP Bug# 955349 - No init file in compute tests * Fixes LP Bug#953450 - Remove vestigial ssh\_timeout * Initial checkin of Stress Test for nova * Lowercase boolean configs before comparison * Addresses lp#942382 - refactor configuration for clarity * Intermediate improvement of Tempest quickstart * Narrow race in wait\_for\_server\_status() * Redrive rate limited API calls * test\_rebuild\_server tolerant of imageRef as URI * Fixes lp#945419 - use\_ssl value is ignored * Fixes lp#945803 * Remove trailing whitespaces in regular file * Pass credentials to glance client * Fixes bug 902352 – New tests for security groups * Addresses lp#902371 * Fixed Bug#943092 * Fixes lp#940532 * Resolves lp#941705 * Addresses lp#933845 \* Changed config to use catalog type instead of catalog name * Removed expected failure from negative access IP tests * Fix hardcoding of status bug * Skip test that is failing due to nova bug 940500 * Fix for bug 931712. Make keypair test work * Fixes lp#932320 \* Made catalog name configurable * Make floating ip test work. Fixes bug 929765 * Fix for critical part of bug 929765 * Fixes LP#921409 \* Adds /servers filter tests \* Re-ordered resource building in fixtures to improve execution time * Fixes LP Bug#903977 - Boundary tests for list servers * Fixes LP#922784 \* Removed duplicate test \* Re-ordered resource allocation * Addresses LP#917976 \* Adds basic logging when exceptions occur * Fixes LP#902358 - Test case for Floating IPs * Fixes LP#903978 - Write testcases for test\_server\_actions (boundary) * Added flavor filter tests: lp899979, lp899980, lp899982 \* Modified flavor service to return results in line with other services \* Added bug flag to tests that are failing due to known issues * Fixes LP #920782 - Malformed request URL * Fixes LP#920812 - KeyError: 'overLimit' on 413 return * KeyPairsClient: Configure client to query nova service from Keystone catalog * Added Keypair extension (os-keypairs) client and tests LP#900139 * Adds Images API tests * Don't set multiple images if image\_ref\_alt is the same as image\_ref. Fix typo in skip\_unless\_addr * Fixes LP Bug#903969 - Image metadata boundary tests * Rework exceptions in Tempest * import error on security groups clientfix 917867 * incorporated Jay's suggestion bug915695 security groups client - to list * Fix for bug917490 \* Adds a wait for the image to become active before deleting it * Removed an invalid test Added nonexistant tests for list images operations * Minor style related change in strings Changed indentation to 4 space width Added nonexistant tests for server metadata operations * LP Bug#914770 - NameError in test\_images * Patch4 corrected typo - for server. not changing try except block Patch3 removed wait time Patch 2. Incorporated Jay's review comments fix for bug903970 create image from deleted server. Verified Pep8 formats. Verified that the test run fine . No image created * Fixes LP Bug #912596 - image\_ref\_alt not found * Added test\_create\_server\_metadata\_key\_too\_long * Create a Tempest conf from a Devstack env * RestClient to target specific services in Keystone catalog * Added filter tests to list images tests, addresses lp bug 900088 * Fixed issue with white space after pep8 review Code review changes for Fixes for lp:903989 Change-Id: Ic345f0b30f24764a6f933684577323042fdeb8aa * Negative test for Flavor - testcase bug 903967. Test run successfully fixed Pep8 issues. Ran Pep8, and it is fine now. Change-Id: I23f04adcbffef4ec67a996e406aec544fa2deb5b * Code review changes for Bugfix for lp904407, /extensions tests * Fixing revert/confirm resize tests * Fixes lp:903466 * General test cleanup * Fixes Bug lp:900910 * Fixed bug 902058 (review comments fixed) * PEP8 fixes * Removed storm references from README.rst * Daryl's latest renaming commit needed some fixes * Changes the namespace from storm to tempest, as well as adding addition tests and improvements \* Changed namespace from storm to tempest \* Added absolute limits service and server personality file tests \* Optimized run time for image metadata tests \* Added additional assertions for create server and rebuild server tests \* Removed any SSH verification until further decisions are made * Fixes LP Bug#899383 - Cleanup config file search * \* Added build\_url() utility that returns an endpoint URL based on config parameters \* Updated storm.conf \* Added more properties to Nova config object \* Fixed pep8 and the 'set' typo that came from a vi editor 'set list' fumble * Removed unnecessary 'self' reference * Update .gitreview with new project name * Added image metadata tests, fixed minor bug in servers service with metadata * Fix numeric header values for kernel\_id and ramdisk\_id * Added server metadata and image tests. Also added a README for storm.conf * Added negative tests for servers * Assert we receive a scoped token & the correct user * Removed unused imports; whitespace normalization * Documented availability of 'auth' tag * Added server details tests. Also re-added several files that somehow missed the initial commit * Fix rabbitmq login * Initial import of tests from the Zodiac project. On suggestion from Westmaas, imported tests under the nova directory (final naming TBD) to more quickly get them imported. To run these tests, execute 'nosetests nova/tests'. I've also only submitted the most stable of the tests. More to come * Tests were not passing for test\_servers in Kong tests. Kong was expecting too much information back from the POST * Cleaning up kong.tests.test\_server\_actions * Adding missing kong.common.utils module * Removing link doctoring in test\_images * Adding identity api v2.0 tests * Updating images tests * Further optimize kong.tests.test\_servers * Removing unnecessary printing of config file * Removed non-testing suggestions * Stop kong/run\_tests.sh from building venv each run * Relaxing FlavorsTest entity checking * Adding paramiko and unittest2 to pip-requires * Updating sample config with required values * The servers test deal with the new uuid params * Add .gitreview config file for gerrit * test supporting API v1.1 * Making run\_test.sh python version and directory agnostic * Adding generic run\_tests.sh * Add rfc.sh to help with gerrit workflow * Consolidate configuration some more * Make ServersTest.setUpClass just setUp to make sure we have read the config * Annotate old stacktester tests so that they get run by run\_tests.sh --whatever * Output request to create server call for easier debugging * Output response from create server call for easier debugging * Make the use of a ramdisk optional for tests * Pull in changes from stacktester * Skip Rabbit tests if pika is not available * Initialise openstack.Manager() with config from self.nova * Make all tests inherit from the same base class * Import all the stacktester stuff as-is (s/stacktester/kong/, though) * Add \_\_init\_\_.py * Move everything under the kong/ namespace * PEP-8 compliance * Remove a bunch of unused imports * No need for xmlrpclib.Server * This is not exactly Kong * Add sample instructions for fetching images. Make sample config match these instructions * cleanup of self.asserts * cleanup self.asserts * moved sample\_vm directory under include dir. Added image/kernel/initrd declaration to config file. Updated glance tests to reference config variables for image/kernel/initrd * Successfully deleting a server returns a 202 now instead of a 204 * Modified the logic to determine which ip to ping during build\_check routine * Initial Release * Add README tempest-17.2.0/LICENSE0000666000175100017510000002363713207044712014330 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. tempest-17.2.0/REVIEWING.rst0000666000175100017510000001305713207044712015407 0ustar zuulzuul00000000000000Reviewing Tempest Code ====================== To start read the `OpenStack Common Review Checklist `_ Ensuring code is executed ------------------------- For any new or change to a test it has to be verified in the gate. This means that the first thing to check with any change is that a gate job actually runs it. Tests which aren't executed either because of configuration or skips should not be accepted. If a new test is added that depends on a new config option (like a feature flag), the commit message must reference a change in DevStack or DevStack-Gate that enables the execution of this newly introduced test. This reference could either be a `Cross-Repository Dependency `_ or a simple link to a Gerrit review. Execution time -------------- While checking in the job logs that a new test is actually executed, also pay attention to the execution time of that test. Keep in mind that each test is going to be executed hundreds of time each day, because Tempest tests run in many OpenStack projects. It's worth considering how important/critical the feature under test is with how costly the new test is. Unit Tests ---------- For any change that adds new functionality to either common functionality or an out-of-band tool unit tests are required. This is to ensure we don't introduce future regressions and to test conditions which we may not hit in the gate runs. Tests, and service clients aren't required to have unit tests since they should be self verifying by running them in the gate. API Stability ------------- Tests should only be added for a published stable APIs. If a patch contains tests for an API which hasn't been marked as stable or for an API that which doesn't conform to the `API stability guidelines `_ then it should not be approved. Reject Copy and Paste Test Code ------------------------------- When creating new tests that are similar to existing tests it is tempting to simply copy the code and make a few modifications. This increases code size and the maintenance burden. Such changes should not be approved if it is easy to abstract the duplicated code into a function or method. Tests overlap ------------- When a new test is being proposed, question whether this feature is not already tested with Tempest. Tempest has more than 1200 tests, spread amongst many directories, so it's easy to introduce test duplication. For example, testing volume attachment to a server could be a compute test or a volume test, depending on how you see it. So one must look carefully in the entire code base for possible overlap. As a rule of thumb, the older a feature is, the more likely it's already tested. Being explicit -------------- When tests are being added that depend on a configurable feature or extension, polling the API to discover that it is enabled should not be done. This will just result in bugs being masked because the test can be skipped automatically. Instead the config file should be used to determine whether a test should be skipped or not. Do not approve changes that depend on an API call to determine whether to skip or not. Configuration Options --------------------- With the introduction of the Tempest external test plugin interface we needed to provide a stable contract for Tempest's configuration options. This means we can no longer simply remove a configuration option when it's no longer used. Patches proposed that remove options without a deprecation cycle should not be approved. Similarly when changing default values with configuration we need to similarly be careful that we don't break existing functionality. Also, when adding options, just as before, we need to weigh the benefit of adding an additional option against the complexity and maintenance overhead having it costs. Test Documentation ------------------ When a new test is being added refer to the :ref:`TestDocumentation` section in hacking to see if the requirements are being met. With the exception of a class level docstring linking to the API ref doc in the API tests and a docstring for scenario tests this is up to the reviewers discretion whether a docstring is required or not. Release Notes ------------- Release notes are how we indicate to users and other consumers of Tempest what has changed in a given release. Since Tempest 10.0.0 we've been using `reno`_ to manage and build the release notes. There are certain types of changes that require release notes and we should not approve them without including a release note. These include but aren't limited to, any addition, deprecation or removal from the lib interface, any change to configuration options (including deprecation), CLI additions or deprecations, major feature additions, and anything backwards incompatible or would require a user to take note or do something extra. .. _reno: https://docs.openstack.org/reno/latest/ Deprecated Code --------------- Sometimes we have some bugs in deprecated code. Basically, we leave it. Because we don't need to maintain it. However, if the bug is critical, we might need to fix it. When it will happen, we will deal with it on a case-by-case basis. When to approve --------------- * Every patch needs two +2s before being approved. * Its ok to hold off on an approval until a subject matter expert reviews it * If a patch has already been approved but requires a trivial rebase to merge, you do not have to wait for a second +2, since the patch has already had two +2s. tempest-17.2.0/.zuul.yaml0000666000175100017510000000553313207044712015257 0ustar zuulzuul00000000000000- job: name: devstack-tempest parent: devstack description: Base Tempest job. required-projects: - openstack/tempest timeout: 7200 roles: - zuul: openstack-dev/devstack vars: devstack_services: tempest: True run: playbooks/devstack-tempest.yaml post-run: playbooks/post-tempest.yaml - job: name: tempest-tox-plugin-sanity-check parent: tox description: | Run tempest plugin sanity check script using tox. nodeset: ubuntu-xenial vars: tox_envlist: plugin-sanity-check voting: false timeout: 5000 required-projects: - openstack/almanach - openstack/aodh - openstack/barbican-tempest-plugin - openstack/ceilometer - openstack/cinder - openstack/congress - openstack/designate-tempest-plugin - openstack/ec2-api - openstack/freezer - openstack/freezer-api - openstack/freezer-tempest-plugin - openstack/gce-api - openstack/glare - openstack/heat - openstack/intel-nfv-ci-tests - openstack/ironic - openstack/ironic-inspector - openstack/keystone-tempest-plugin - openstack/kingbird - openstack/kuryr-tempest-plugin - openstack/magnum - openstack/magnum-tempest-plugin - openstack/manila - openstack/manila-tempest-plugin - openstack/mistral - openstack/mogan - openstack/monasca-api - openstack/monasca-log-api - openstack/murano - openstack/networking-bgpvpn - openstack/networking-cisco - openstack/networking-fortinet - openstack/networking-generic-switch - openstack/networking-l2gw - openstack/networking-midonet - openstack/networking-plumgrid - openstack/networking-sfc - openstack/neutron - openstack/neutron-dynamic-routing - openstack/neutron-fwaas - openstack/neutron-lbaas - openstack/neutron-tempest-plugin - openstack/neutron-vpnaas - openstack/nova-lxd - openstack/novajoin-tempest-plugin - openstack/octavia - openstack/oswin-tempest-plugin - openstack/panko - openstack/patrole - openstack/qinling - openstack/requirements - openstack/sahara-tests - openstack/senlin - openstack/senlin-tempest-plugin - openstack/tap-as-a-service - openstack/tempest-horizon - openstack/trio2o - openstack/trove - openstack/valet - openstack/vitrage - openstack/vmware-nsx-tempest-plugin - openstack/watcher-tempest-plugin - openstack/zaqar-tempest-plugin - openstack/zun-tempest-plugin - project: name: openstack/tempest check: jobs: - devstack-tempest: files: - ^playbooks/ - ^roles/ - ^.zuul.yaml$ - tempest-tox-plugin-sanity-check tempest-17.2.0/.coveragerc0000666000175100017510000000014513207044712015431 0ustar zuulzuul00000000000000[run] branch = True source = tempest omit = tempest/tests/*,tempest/scenario/test_*.py,tempest/api/* tempest-17.2.0/.mailmap0000666000175100017510000000332013207044712014727 0ustar zuulzuul00000000000000 Adam Gandelman Andrea Frittoli Andrea Frittoli Daryl Walleck David Kranz David Kranz Ghanshyam Ghanshyam Jay Pipes Joe Gordon Ken'ichi Ohmichi Ken'ichi Ohmichi Marc Koderer Masayuki Igawa Masayuki Igawa Masayuki Igawa Matthew Treinish Nayna Patel ravikumar-venkatesan ravikumar-venkatesan Rohit Karajgi Sean Dague Sean Dague Yuiko Takada Zhi Kun Liu tempest-17.2.0/tempest/0000775000175100017510000000000013207045130014762 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/manager.py0000666000175100017510000000517313207044712016763 0ustar zuulzuul00000000000000# 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_log import log as logging from tempest import clients as tempest_clients from tempest import config from tempest.lib.services import clients CONF = config.CONF LOG = logging.getLogger(__name__) class Manager(clients.ServiceClients): """Service client manager class for backward compatibility The former manager.Manager is not a stable interface in Tempest, nonetheless it is consumed by a number of plugins already. This class exists to provide some grace time for the move to tempest.lib. """ def __init__(self, credentials, scope='project'): msg = ("tempest.manager.Manager is not a stable interface and as such " "it should not imported directly. It will be removed as " "soon as the client manager becomes available in tempest.lib.") LOG.warning(msg) dscv = CONF.identity.disable_ssl_certificate_validation _, uri = tempest_clients.get_auth_provider_class(credentials) super(Manager, self).__init__( credentials=credentials, scope=scope, identity_uri=uri, disable_ssl_certificate_validation=dscv, ca_certs=CONF.identity.ca_certificates_file, trace_requests=CONF.debug.trace_requests) def get_auth_provider(credentials, pre_auth=False, scope='project'): """Shim to get_auth_provider in clients.py get_auth_provider used to be hosted in this module, but it has been moved to clients.py now as a more permanent location. This module will be removed eventually, and this shim is only maintained for the benefit of plugins already consuming this interface. """ msg = ("tempest.manager.get_auth_provider is not a stable interface and " "as such it should not imported directly. It will be removed as " "the client manager becomes available in tempest.lib.") LOG.warning(msg) return tempest_clients.get_auth_provider(credentials=credentials, pre_auth=pre_auth, scope=scope) tempest-17.2.0/tempest/tests/0000775000175100017510000000000013207045130016124 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/test_decorators.py0000666000175100017510000001574113207044712021721 0ustar zuulzuul00000000000000# 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 fixtures from oslo_config import cfg import testtools from tempest.common import utils from tempest import config from tempest import exceptions from tempest.lib.common.utils import data_utils from tempest import test from tempest.tests import base from tempest.tests import fake_config class BaseDecoratorsTest(base.TestCase): def setUp(self): super(BaseDecoratorsTest, self).setUp() self.config_fixture = self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) # NOTE: The test module is for tempest.test.idempotent_id. # After all projects switch to use decorators.idempotent_id, # we can remove tempest.test.idempotent_id as well as this # test module class TestIdempotentIdDecorator(BaseDecoratorsTest): def _test_helper(self, _id, **decorator_args): @test.idempotent_id(_id) def foo(): """Docstring""" pass return foo def _test_helper_without_doc(self, _id, **decorator_args): @test.idempotent_id(_id) def foo(): pass return foo def test_positive(self): _id = data_utils.rand_uuid() 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 = data_utils.rand_uuid() 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 TestServicesDecorator(BaseDecoratorsTest): def _test_services_helper(self, *decorator_args): class TestFoo(test.BaseTestCase): @utils.services(*decorator_args) def test_bar(self): return 0 t = TestFoo('test_bar') self.assertEqual(set(decorator_args), getattr(t.test_bar, '__testtools_attrs')) self.assertEqual(t.test_bar(), 0) def test_services_decorator_with_single_service(self): self._test_services_helper('compute') def test_services_decorator_with_multiple_services(self): self._test_services_helper('compute', 'network') def test_services_decorator_with_duplicated_service(self): self._test_services_helper('compute', 'compute') def test_services_decorator_with_invalid_service(self): self.assertRaises(exceptions.InvalidServiceTag, self._test_services_helper, 'compute', 'bad_service') def test_services_decorator_with_service_valid_and_unavailable(self): self.useFixture(fixtures.MockPatchObject(test.CONF.service_available, 'cinder', False)) self.assertRaises(testtools.TestCase.skipException, self._test_services_helper, 'compute', 'volume') def test_services_list(self): service_list = utils.get_service_list() for service in service_list: try: self._test_services_helper(service) except exceptions.InvalidServiceTag: self.fail('%s is not listed in the valid service tag list' % service) except KeyError: # NOTE(mtreinish): This condition is to test for an entry in # the outer decorator list but not in the service_list dict. # However, because we're looping over the service_list dict # it's unlikely we'll trigger this. So manual review is still # need for the list in the outer decorator. self.fail('%s is in the list of valid service tags but there ' 'is no corresponding entry in the dict returned from' ' get_service_list()' % service) except testtools.TestCase.skipException: # Test didn't raise an exception because of an incorrect list # entry so move onto the next entry continue class TestRequiresExtDecorator(BaseDecoratorsTest): def setUp(self): super(TestRequiresExtDecorator, self).setUp() cfg.CONF.set_default('api_extensions', ['enabled_ext', 'another_ext'], 'compute-feature-enabled') def _test_requires_ext_helper(self, expected_to_skip=True, **decorator_args): class TestFoo(test.BaseTestCase): @utils.requires_ext(**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: try: self.assertEqual(t.test_bar(), 0) except testtools.TestCase.skipException: # We caught a skipException but we didn't expect to skip # this test so raise a hard test failure instead. raise testtools.TestCase.failureException( "Not supposed to skip") def test_requires_ext_decorator(self): self._test_requires_ext_helper(expected_to_skip=False, extension='enabled_ext', service='compute') def test_requires_ext_decorator_disabled_ext(self): self._test_requires_ext_helper(extension='disabled_ext', service='compute') def test_requires_ext_decorator_with_all_ext_enabled(self): cfg.CONF.set_default('api_extensions', ['all'], group='compute-feature-enabled') self._test_requires_ext_helper(expected_to_skip=False, extension='random_ext', service='compute') def test_requires_ext_decorator_bad_service(self): self.assertRaises(KeyError, self._test_requires_ext_helper, extension='enabled_ext', service='bad_service') tempest-17.2.0/tempest/tests/README.rst0000666000175100017510000000203713207044712017624 0ustar zuulzuul00000000000000.. _unit_tests_field_guide: Tempest Field Guide to Unit tests ================================= What are these tests? --------------------- Unit tests are the self checks for Tempest. They provide functional verification and regression checking for the internal components of Tempest. They should be used to just verify that the individual pieces of Tempest are working as expected. They should not require an external service to be running and should be able to run solely from the Tempest tree. Why are these tests in Tempest? ------------------------------- These tests exist to make sure that the mechanisms that we use inside of Tempest are valid and remain functional. They are only here for self validation of Tempest. Scope of these tests -------------------- Unit tests should not require an external service to be running or any extra configuration to run. Any state that is required for a test should either be mocked out or created in a temporary test directory. (see test_wrappers.py for an example of using a temporary test directory) tempest-17.2.0/tempest/tests/test_config.py0000666000175100017510000001003313207044712021006 0ustar zuulzuul00000000000000# 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 testtools from tempest import config from tempest.lib import exceptions from tempest.tests import base from tempest.tests import fake_config class TestServiceClientConfig(base.TestCase): expected_common_params = set(['disable_ssl_certificate_validation', 'ca_certs', 'trace_requests']) expected_extra_params = set(['service', 'endpoint_type', 'region', 'build_timeout', 'build_interval']) def setUp(self): super(TestServiceClientConfig, self).setUp() self.useFixture(fake_config.ServiceClientsConfigFixture()) self.patchobject(config, 'CONF', fake_config.ServiceClientsFakePrivate()) self.CONF = config.CONF def test_service_client_config_no_service(self): params = config.service_client_config() for param_name in self.expected_common_params: self.assertIn(param_name, params) for param_name in self.expected_extra_params: self.assertNotIn(param_name, params) self.assertEqual( self.CONF.identity.disable_ssl_certificate_validation, params['disable_ssl_certificate_validation']) self.assertEqual(self.CONF.identity.ca_certificates_file, params['ca_certs']) self.assertEqual(self.CONF.debug.trace_requests, params['trace_requests']) def test_service_client_config_service_all(self): params = config.service_client_config( service_client_name='fake-service1') for param_name in self.expected_common_params: self.assertIn(param_name, params) for param_name in self.expected_extra_params: self.assertIn(param_name, params) self.assertEqual(self.CONF.fake_service1.catalog_type, params['service']) self.assertEqual(self.CONF.fake_service1.endpoint_type, params['endpoint_type']) self.assertEqual(self.CONF.fake_service1.region, params['region']) self.assertEqual(self.CONF.fake_service1.build_timeout, params['build_timeout']) self.assertEqual(self.CONF.fake_service1.build_interval, params['build_interval']) def test_service_client_config_service_minimal(self): params = config.service_client_config( service_client_name='fake-service2') for param_name in self.expected_common_params: self.assertIn(param_name, params) for param_name in self.expected_extra_params: self.assertIn(param_name, params) self.assertEqual(self.CONF.fake_service2.catalog_type, params['service']) self.assertEqual(self.CONF.fake_service2.endpoint_type, params['endpoint_type']) self.assertEqual(self.CONF.identity.region, params['region']) self.assertEqual(self.CONF.compute.build_timeout, params['build_timeout']) self.assertEqual(self.CONF.compute.build_interval, params['build_interval']) def test_service_client_config_service_unknown(self): unknown_service = 'unknown_service' with testtools.ExpectedException(exceptions.UnknownServiceClient, '.*' + unknown_service + '.*'): config.service_client_config(service_client_name=unknown_service) tempest-17.2.0/tempest/tests/fake_config.py0000666000175100017510000001042713207044712020744 0ustar zuulzuul00000000000000# 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 os from oslo_concurrency import lockutils from oslo_config import cfg from oslo_config import fixture as conf_fixture from tempest import config class ConfigFixture(conf_fixture.Config): def __init__(self): cfg.CONF([], default_config_files=[]) config.register_opts() super(ConfigFixture, self).__init__() def setUp(self): super(ConfigFixture, self).setUp() self.conf.set_default('build_interval', 10, group='compute') self.conf.set_default('build_timeout', 10, group='compute') self.conf.set_default('disable_ssl_certificate_validation', True, group='identity') self.conf.set_default('uri', 'http://fake_uri.com/auth', group='identity') self.conf.set_default('uri_v3', 'http://fake_uri_v3.com/auth', group='identity') self.conf.set_default('neutron', True, group='service_available') self.conf.set_default('heat', True, group='service_available') if not os.path.exists(str(os.environ.get('OS_TEST_LOCK_PATH'))): os.mkdir(str(os.environ.get('OS_TEST_LOCK_PATH'))) lockutils.set_defaults( lock_path=str(os.environ.get('OS_TEST_LOCK_PATH')), ) self.conf.set_default('auth_version', 'v2', group='identity') for config_option in ['username', 'password', 'project_name']: # Identity group items self.conf.set_default('admin_' + config_option, 'fake_' + config_option, group='auth') class FakePrivate(config.TempestConfigPrivate): def __init__(self, parse_conf=True, config_path=None): self._set_attrs() self.lock_path = cfg.CONF.oslo_concurrency.lock_path fake_service1_group = cfg.OptGroup(name='fake-service1', title='Fake service1') FakeService1Group = [ cfg.StrOpt('catalog_type', default='fake-service1'), cfg.StrOpt('endpoint_type', default='faketype'), cfg.StrOpt('region', default='fake_region'), cfg.IntOpt('build_timeout', default=99), cfg.IntOpt('build_interval', default=9)] fake_service2_group = cfg.OptGroup(name='fake-service2', title='Fake service2') FakeService2Group = [ cfg.StrOpt('catalog_type', default='fake-service2'), cfg.StrOpt('endpoint_type', default='faketype')] class ServiceClientsConfigFixture(conf_fixture.Config): def __init__(self): cfg.CONF([], default_config_files=[]) config._opts.append((fake_service1_group, FakeService1Group)) config._opts.append((fake_service2_group, FakeService2Group)) config.register_opts() super(ServiceClientsConfigFixture, self).__init__() def setUp(self): super(ServiceClientsConfigFixture, self).setUp() # Debug default values self.conf.set_default('trace_requests', 'fake_module', 'debug') # Identity default values self.conf.set_default('disable_ssl_certificate_validation', True, group='identity') self.conf.set_default('ca_certificates_file', '/fake/certificates', group='identity') self.conf.set_default('region', 'fake_region', 'identity') # Compute default values self.conf.set_default('build_interval', 88, group='compute') self.conf.set_default('build_timeout', 8, group='compute') class ServiceClientsFakePrivate(config.TempestConfigPrivate): def __init__(self, parse_conf=True, config_path=None): self._set_attrs() self.fake_service1 = cfg.CONF['fake-service1'] self.fake_service2 = cfg.CONF['fake-service2'] self.lock_path = cfg.CONF.oslo_concurrency.lock_path tempest-17.2.0/tempest/tests/fake_tempest_plugin.py0000666000175100017510000000321213207044712022530 0ustar zuulzuul00000000000000# Copyright (c) 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. from tempest.test_discover import plugins class FakePlugin(plugins.TempestPlugin): expected_load_test = ["my/test/path", "/home/dir"] expected_service_clients = [{'foo': 'bar'}] def load_tests(self): return self.expected_load_test def register_opts(self, conf): return def get_opt_lists(self): return [] def get_service_clients(self): return self.expected_service_clients class FakeStevedoreObj(object): obj = FakePlugin() @property def name(self): return self._name def __init__(self, name='Test1'): self._name = name class FakePluginNoServiceClients(plugins.TempestPlugin): def load_tests(self): return [] def register_opts(self, conf): return def get_opt_lists(self): return [] class FakeStevedoreObjNoServiceClients(object): obj = FakePluginNoServiceClients() @property def name(self): return self._name def __init__(self, name='Test2'): self._name = name tempest-17.2.0/tempest/tests/test_test.py0000666000175100017510000005755113207044712020540 0ustar zuulzuul00000000000000# Copyright 2017 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 os import sys import mock from oslo_config import cfg import testtools from tempest import clients from tempest import config from tempest.lib.common import validation_resources as vr from tempest.lib import exceptions as lib_exc from tempest import test from tempest.tests import base from tempest.tests import fake_config from tempest.tests.lib import fake_credentials from tempest.tests.lib.services import registry_fixture if sys.version_info >= (2, 7): import unittest else: import unittest2 as unittest class LoggingTestResult(testtools.TestResult): def __init__(self, log, *args, **kwargs): super(LoggingTestResult, self).__init__(*args, **kwargs) self.log = log def addError(self, test, err=None, details=None): self.log.append((test, err, details)) class TestValidationResources(base.TestCase): validation_resources_module = 'tempest.lib.common.validation_resources' def setUp(self): super(TestValidationResources, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.useFixture(registry_fixture.RegistryFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) class TestTestClass(test.BaseTestCase): pass self.test_test_class = TestTestClass def test_validation_resources_no_validation(self): cfg.CONF.set_default('run_validation', False, 'validation') creds = fake_credentials.FakeKeystoneV3Credentials() osclients = clients.Manager(creds) vr = self.test_test_class.get_class_validation_resources(osclients) self.assertIsNone(vr) def test_validation_resources_exists(self): cfg.CONF.set_default('run_validation', True, 'validation') creds = fake_credentials.FakeKeystoneV3Credentials() osclients = clients.Manager(creds) expected_vr = 'expected_validation_resources' self.test_test_class._validation_resources[osclients] = expected_vr obtained_vr = self.test_test_class.get_class_validation_resources( osclients) self.assertEqual(expected_vr, obtained_vr) @mock.patch(validation_resources_module + '.create_validation_resources', autospec=True) def test_validation_resources_new(self, mock_create_vr): cfg.CONF.set_default('run_validation', True, 'validation') cfg.CONF.set_default('neutron', True, 'service_available') creds = fake_credentials.FakeKeystoneV3Credentials() osclients = clients.Manager(creds) expected_vr = {'expected_validation_resources': None} mock_create_vr.return_value = expected_vr with mock.patch.object( self.test_test_class, 'addClassResourceCleanup') as mock_add_class_cleanup: obtained_vr = self.test_test_class.get_class_validation_resources( osclients) self.assertEqual(1, mock_add_class_cleanup.call_count) self.assertEqual(mock.call(vr.clear_validation_resources, osclients, use_neutron=True, **expected_vr), mock_add_class_cleanup.call_args) self.assertEqual(mock_create_vr.call_count, 1) self.assertIn(osclients, mock_create_vr.call_args_list[0][0]) self.assertEqual(expected_vr, obtained_vr) self.assertIn(osclients, self.test_test_class._validation_resources) self.assertEqual(expected_vr, self.test_test_class._validation_resources[osclients]) def test_validation_resources_invalid_config(self): invalid_version = 999 cfg.CONF.set_default('run_validation', True, 'validation') cfg.CONF.set_default('ip_version_for_ssh', invalid_version, 'validation') cfg.CONF.set_default('neutron', True, 'service_available') creds = fake_credentials.FakeKeystoneV3Credentials() osclients = clients.Manager(creds) with testtools.ExpectedException( lib_exc.InvalidConfiguration, value_re='^.*\n.*' + str(invalid_version)): self.test_test_class.get_class_validation_resources(osclients) @mock.patch(validation_resources_module + '.create_validation_resources', autospec=True) def test_validation_resources_invalid_config_nova_net(self, mock_create_vr): invalid_version = 999 cfg.CONF.set_default('run_validation', True, 'validation') cfg.CONF.set_default('ip_version_for_ssh', invalid_version, 'validation') cfg.CONF.set_default('neutron', False, 'service_available') creds = fake_credentials.FakeKeystoneV3Credentials() osclients = clients.Manager(creds) expected_vr = {'expected_validation_resources': None} mock_create_vr.return_value = expected_vr obtained_vr = self.test_test_class.get_class_validation_resources( osclients) self.assertEqual(mock_create_vr.call_count, 1) self.assertIn(osclients, mock_create_vr.call_args_list[0][0]) self.assertEqual(expected_vr, obtained_vr) self.assertIn(osclients, self.test_test_class._validation_resources) self.assertEqual(expected_vr, self.test_test_class._validation_resources[osclients]) @mock.patch(validation_resources_module + '.create_validation_resources', autospec=True) @mock.patch(validation_resources_module + '.clear_validation_resources', autospec=True) def test_validation_resources_fixture(self, mock_clean_vr, mock_create_vr): class TestWithRun(self.test_test_class): def runTest(self): pass cfg.CONF.set_default('run_validation', True, 'validation') test_case = TestWithRun() creds = fake_credentials.FakeKeystoneV3Credentials() osclients = clients.Manager(creds) test_case.get_test_validation_resources(osclients) self.assertEqual(1, mock_create_vr.call_count) self.assertEqual(0, mock_clean_vr.call_count) class TestSetNetworkResources(base.TestCase): def setUp(self): super(TestSetNetworkResources, self).setUp() class ParentTest(test.BaseTestCase): @classmethod def setup_credentials(cls): cls.set_network_resources(dhcp=True) super(ParentTest, cls).setup_credentials() def runTest(self): pass self.parent_class = ParentTest def test_set_network_resources_child_only(self): class ChildTest(self.parent_class): @classmethod def setup_credentials(cls): cls.set_network_resources(router=True) super(ChildTest, cls).setup_credentials() child_test = ChildTest() child_test.setUpClass() # Assert that the parents network resources are not set self.assertFalse(child_test._network_resources['dhcp']) # Assert that the child network resources are set self.assertTrue(child_test._network_resources['router']) def test_set_network_resources_right_order(self): class ChildTest(self.parent_class): @classmethod def setup_credentials(cls): super(ChildTest, cls).setup_credentials() cls.set_network_resources(router=True) child_test = ChildTest() with testtools.ExpectedException(RuntimeError, value_re='set_network_resources'): child_test.setUpClass() def test_set_network_resources_children(self): class ChildTest(self.parent_class): @classmethod def setup_credentials(cls): cls.set_network_resources(router=True) super(ChildTest, cls).setup_credentials() class GrandChildTest(ChildTest): pass # Invoke setupClass on both and check that the setup_credentials # call check mechanism does not report any false negative. child_test = ChildTest() child_test.setUpClass() grandchild_test = GrandChildTest() grandchild_test.setUpClass() class TestTempestBaseTestClass(base.TestCase): def setUp(self): super(TestTempestBaseTestClass, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) class ParentTest(test.BaseTestCase): def runTest(self): pass self.parent_test = ParentTest def test_resource_cleanup(self): cfg.CONF.set_default('neutron', False, 'service_available') exp_args = (1, 2,) exp_kwargs = {'a': 1, 'b': 2} mock1 = mock.Mock() mock2 = mock.Mock() exp_functions = [mock1, mock2] class TestWithCleanups(self.parent_test): @classmethod def resource_setup(cls): for fn in exp_functions: cls.addClassResourceCleanup(fn, *exp_args, **exp_kwargs) test_cleanups = TestWithCleanups() suite = unittest.TestSuite((test_cleanups,)) log = [] result = LoggingTestResult(log) suite.run(result) # No exception raised - error log is empty self.assertFalse(log) # All stacked resource cleanups invoked mock1.assert_called_once_with(*exp_args, **exp_kwargs) mock2.assert_called_once_with(*exp_args, **exp_kwargs) # Cleanup stack is empty self.assertEqual(0, len(test_cleanups._class_cleanups)) def test_resource_cleanup_failures(self): cfg.CONF.set_default('neutron', False, 'service_available') exp_args = (1, 2,) exp_kwargs = {'a': 1, 'b': 2} mock1 = mock.Mock() mock1.side_effect = Exception('mock1 resource cleanup failure') mock2 = mock.Mock() mock3 = mock.Mock() mock3.side_effect = Exception('mock3 resource cleanup failure') exp_functions = [mock1, mock2, mock3] class TestWithFailingCleanups(self.parent_test): @classmethod def resource_setup(cls): for fn in exp_functions: cls.addClassResourceCleanup(fn, *exp_args, **exp_kwargs) test_cleanups = TestWithFailingCleanups() suite = unittest.TestSuite((test_cleanups,)) log = [] result = LoggingTestResult(log) suite.run(result) # One multiple exception captured self.assertEqual(1, len(log)) # [0]: test, err, details [1] -> exc_info # Type, Exception, traceback [1] -> MultipleException found_exc = log[0][1][1] self.assertTrue(isinstance(found_exc, testtools.MultipleExceptions)) self.assertEqual(2, len(found_exc.args)) # Each arg is exc_info - match messages and order self.assertIn('mock3 resource', str(found_exc.args[0][1])) self.assertIn('mock1 resource', str(found_exc.args[1][1])) # All stacked resource cleanups invoked mock1.assert_called_once_with(*exp_args, **exp_kwargs) mock2.assert_called_once_with(*exp_args, **exp_kwargs) # Cleanup stack is empty self.assertEqual(0, len(test_cleanups._class_cleanups)) def test_super_resource_cleanup_not_invoked(self): class BadResourceCleanup(self.parent_test): @classmethod def resource_cleanup(cls): pass bad_class = BadResourceCleanup() suite = unittest.TestSuite((bad_class,)) log = [] result = LoggingTestResult(log) suite.run(result) # One multiple exception captured self.assertEqual(1, len(log)) # [0]: test, err, details [1] -> exc_info # Type, Exception, traceback [1] -> RuntimeError found_exc = log[0][1][1] self.assertTrue(isinstance(found_exc, RuntimeError)) self.assertIn(BadResourceCleanup.__name__, str(found_exc)) def test_super_skip_checks_not_invoked(self): class BadSkipChecks(self.parent_test): @classmethod def skip_checks(cls): pass bad_class = BadSkipChecks() with testtools.ExpectedException( RuntimeError, value_re='^.* ' + BadSkipChecks.__name__): bad_class.setUpClass() def test_super_setup_credentials_not_invoked(self): class BadSetupCredentials(self.parent_test): @classmethod def skip_checks(cls): pass bad_class = BadSetupCredentials() with testtools.ExpectedException( RuntimeError, value_re='^.* ' + BadSetupCredentials.__name__): bad_class.setUpClass() def test_grandparent_skip_checks_not_invoked(self): class BadSkipChecks(self.parent_test): @classmethod def skip_checks(cls): pass class SonOfBadSkipChecks(BadSkipChecks): pass bad_class = SonOfBadSkipChecks() with testtools.ExpectedException( RuntimeError, value_re='^.* ' + SonOfBadSkipChecks.__name__): bad_class.setUpClass() @mock.patch('tempest.common.credentials_factory.is_admin_available', autospec=True, return_value=True) def test_skip_checks_admin(self, mock_iaa): identity_version = 'identity_version' class NeedAdmin(self.parent_test): credentials = ['admin'] @classmethod def get_identity_version(cls): return identity_version NeedAdmin().skip_checks() mock_iaa.assert_called_once_with('identity_version') @mock.patch('tempest.common.credentials_factory.is_admin_available', autospec=True, return_value=False) def test_skip_checks_admin_not_available(self, mock_iaa): identity_version = 'identity_version' class NeedAdmin(self.parent_test): credentials = ['admin'] @classmethod def get_identity_version(cls): return identity_version with testtools.ExpectedException(testtools.testcase.TestSkipped): NeedAdmin().skip_checks() mock_iaa.assert_called_once_with('identity_version') def test_skip_checks_identity_v2_not_available(self): cfg.CONF.set_default('api_v2', False, 'identity-feature-enabled') class NeedV2(self.parent_test): identity_version = 'v2' with testtools.ExpectedException(testtools.testcase.TestSkipped): NeedV2().skip_checks() def test_skip_checks_identity_v3_not_available(self): cfg.CONF.set_default('api_v3', False, 'identity-feature-enabled') class NeedV3(self.parent_test): identity_version = 'v3' with testtools.ExpectedException(testtools.testcase.TestSkipped): NeedV3().skip_checks() def test_setup_credentials_all(self): expected_creds = ['string', ['list', 'role1', 'role2']] class AllCredentials(self.parent_test): credentials = expected_creds expected_clients = 'clients' with mock.patch.object( AllCredentials, 'get_client_manager') as mock_get_client_manager: mock_get_client_manager.return_value = expected_clients all_creds = AllCredentials() all_creds.setup_credentials() self.assertTrue(hasattr(all_creds, 'os_string')) self.assertEqual(expected_clients, all_creds.os_string) self.assertTrue(hasattr(all_creds, 'os_roles_list')) self.assertEqual(expected_clients, all_creds.os_roles_list) self.assertEqual(2, mock_get_client_manager.call_count) self.assertEqual( expected_creds[0], mock_get_client_manager.mock_calls[0][2]['credential_type']) self.assertEqual( expected_creds[1][1:], mock_get_client_manager.mock_calls[1][2]['roles']) def test_setup_class_overwritten(self): class OverridesSetup(self.parent_test): @classmethod def setUpClass(cls): # noqa pass overrides_setup = OverridesSetup() suite = unittest.TestSuite((overrides_setup,)) log = [] result = LoggingTestResult(log) suite.run(result) # Record 0, test (error holder). The error generates during test run. self.assertIn('runTest', str(log[0][0])) # Record 0, traceback self.assertRegex( str(log[0][2]['traceback']).replace('\n', ' '), RuntimeError.__name__ + ': .* ' + OverridesSetup.__name__) class TestTempestBaseTestClassFixtures(base.TestCase): SETUP_FIXTURES = [test.BaseTestCase.setUpClass.__name__, test.BaseTestCase.skip_checks.__name__, test.BaseTestCase.setup_credentials.__name__, test.BaseTestCase.setup_clients.__name__, test.BaseTestCase.resource_setup.__name__] TEARDOWN_FIXTURES = [test.BaseTestCase.tearDownClass.__name__, test.BaseTestCase.resource_cleanup.__name__, test.BaseTestCase.clear_credentials.__name__] def setUp(self): super(TestTempestBaseTestClassFixtures, self).setUp() self.mocks = {} for fix in self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES: self.mocks[fix] = mock.Mock() def tracker_builder(name): def tracker(cls): # Track that the fixture was invoked cls.fixtures_invoked.append(name) # Run the fixture getattr(super(TestWithClassFixtures, cls), name)() # Run a mock we can use for side effects self.mocks[name]() return tracker class TestWithClassFixtures(test.BaseTestCase): credentials = [] fixtures_invoked = [] def runTest(_self): pass # Decorate all test class fixtures with tracker_builder for method_name in self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES: setattr(TestWithClassFixtures, method_name, classmethod(tracker_builder(method_name))) self.test = TestWithClassFixtures() def test_no_error_flow(self): # If all setup fixtures are executed, all cleanup fixtures are # executed too suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES, self.test.fixtures_invoked) def test_skip_only(self): # If a skip condition is hit in the test, no credentials or resource # is provisioned / cleaned-up self.mocks['skip_checks'].side_effect = ( testtools.testcase.TestSkipped()) suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) # If we trigger a skip condition, teardown is not invoked at all self.assertEqual(self.SETUP_FIXTURES[:2], self.test.fixtures_invoked) def test_skip_credentials_fails(self): expected_exc = 'sc exploded' self.mocks['setup_credentials'].side_effect = Exception(expected_exc) suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) # If setup_credentials explodes, we invoked teardown class and # clear credentials, and re-raise self.assertEqual((self.SETUP_FIXTURES[:3] + [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]), self.test.fixtures_invoked) found_exc = log[0][1][1] self.assertIn(expected_exc, str(found_exc)) def test_skip_credentials_fails_clear_fails(self): # If cleanup fails on failure, we log the exception and do not # re-raise it. Note that since the exception happens outside of # the Tempest test setUp, logging is not captured on the Tempest # test side, it will be captured by the unit test instead. expected_exc = 'sc exploded' clear_exc = 'clear exploded' self.mocks['setup_credentials'].side_effect = Exception(expected_exc) self.mocks['clear_credentials'].side_effect = Exception(clear_exc) suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) # If setup_credentials explodes, we invoked teardown class and # clear credentials, and re-raise self.assertEqual((self.SETUP_FIXTURES[:3] + [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]), self.test.fixtures_invoked) found_exc = log[0][1][1] self.assertIn(expected_exc, str(found_exc)) # Since log capture depends on OS_LOG_CAPTURE, we can only assert if # logging was captured if os.environ.get('OS_LOG_CAPTURE'): self.assertIn(clear_exc, self.log_fixture.logger.output) def test_skip_credentials_clients_resources_credentials_clear_fails(self): # If cleanup fails with no previous failure, we re-raise the exception. expected_exc = 'clear exploded' self.mocks['clear_credentials'].side_effect = Exception(expected_exc) suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) # If setup_credentials explodes, we invoked teardown class and # clear credentials, and re-raise self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES, self.test.fixtures_invoked) found_exc = log[0][1][1] self.assertIn(expected_exc, str(found_exc)) def test_skip_credentials_clients_fails(self): expected_exc = 'clients exploded' self.mocks['setup_clients'].side_effect = Exception(expected_exc) suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) # If setup_clients explodes, we invoked teardown class and # clear credentials, and re-raise self.assertEqual((self.SETUP_FIXTURES[:4] + [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]), self.test.fixtures_invoked) found_exc = log[0][1][1] self.assertIn(expected_exc, str(found_exc)) def test_skip_credentials_clients_resources_fails(self): expected_exc = 'resource setup exploded' self.mocks['resource_setup'].side_effect = Exception(expected_exc) suite = unittest.TestSuite((self.test,)) log = [] result = LoggingTestResult(log) suite.run(result) # If resource_setup explodes, we invoked teardown class and # clear credentials and resource cleanup, and re-raise self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES, self.test.fixtures_invoked) found_exc = log[0][1][1] self.assertIn(expected_exc, str(found_exc)) tempest-17.2.0/tempest/tests/test_base_test.py0000666000175100017510000001170413207044712021520 0ustar zuulzuul00000000000000# Copyright 2016 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 oslo_config import cfg from tempest import clients from tempest.common import credentials_factory as credentials from tempest import config from tempest.lib.common import fixed_network from tempest import test from tempest.tests import base from tempest.tests import fake_config class TestBaseTestCase(base.TestCase): def setUp(self): super(TestBaseTestCase, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.fixed_network_name = 'fixed-net' cfg.CONF.set_default('fixed_network_name', self.fixed_network_name, 'compute') cfg.CONF.set_default('neutron', True, 'service_available') @mock.patch.object(test.BaseTestCase, 'get_client_manager') @mock.patch.object(test.BaseTestCase, '_get_credentials_provider') @mock.patch.object(fixed_network, 'get_tenant_network') def test_get_tenant_network(self, mock_gtn, mock_gprov, mock_gcm): net_client = mock.Mock() mock_prov = mock.Mock() mock_gcm.return_value.compute_networks_client = net_client mock_gprov.return_value = mock_prov test.BaseTestCase.get_tenant_network() mock_gcm.assert_called_once_with(credential_type='primary') mock_gprov.assert_called_once_with() mock_gtn.assert_called_once_with(mock_prov, net_client, self.fixed_network_name) @mock.patch.object(test.BaseTestCase, 'get_client_manager') @mock.patch.object(test.BaseTestCase, '_get_credentials_provider') @mock.patch.object(fixed_network, 'get_tenant_network') @mock.patch.object(test.BaseTestCase, 'get_identity_version') @mock.patch.object(credentials, 'is_admin_available') @mock.patch.object(clients, 'Manager') def test_get_tenant_network_with_nova_net(self, mock_man, mock_iaa, mock_giv, mock_gtn, mock_gcp, mock_gcm): cfg.CONF.set_default('neutron', False, 'service_available') mock_prov = mock.Mock() mock_admin_man = mock.Mock() mock_iaa.return_value = True mock_gcp.return_value = mock_prov mock_man.return_value = mock_admin_man test.BaseTestCase.get_tenant_network() mock_man.assert_called_once_with( mock_prov.get_admin_creds.return_value.credentials) mock_iaa.assert_called_once_with( identity_version=mock_giv.return_value) mock_gcp.assert_called_once_with() mock_gtn.assert_called_once_with( mock_prov, mock_admin_man.compute_networks_client, self.fixed_network_name) @mock.patch.object(test.BaseTestCase, 'get_client_manager') @mock.patch.object(test.BaseTestCase, '_get_credentials_provider') @mock.patch.object(fixed_network, 'get_tenant_network') def test_get_tenant_network_with_alt_creds(self, mock_gtn, mock_gprov, mock_gcm): net_client = mock.Mock() mock_prov = mock.Mock() mock_gcm.return_value.compute_networks_client = net_client mock_gprov.return_value = mock_prov test.BaseTestCase.get_tenant_network(credentials_type='alt') mock_gcm.assert_called_once_with(credential_type='alt') mock_gprov.assert_called_once_with() mock_gtn.assert_called_once_with(mock_prov, net_client, self.fixed_network_name) @mock.patch.object(test.BaseTestCase, 'get_client_manager') @mock.patch.object(test.BaseTestCase, '_get_credentials_provider') @mock.patch.object(fixed_network, 'get_tenant_network') def test_get_tenant_network_with_role_creds(self, mock_gtn, mock_gprov, mock_gcm): net_client = mock.Mock() mock_prov = mock.Mock() mock_gcm.return_value.compute_networks_client = net_client mock_gprov.return_value = mock_prov creds = ['foo_type', 'role1'] test.BaseTestCase.get_tenant_network(credentials_type=creds) mock_gcm.assert_called_once_with(roles=['role1']) mock_gprov.assert_called_once_with() mock_gtn.assert_called_once_with(mock_prov, net_client, self.fixed_network_name) tempest-17.2.0/tempest/tests/files/0000775000175100017510000000000013207045130017226 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/files/failing-tests0000666000175100017510000000147413207044712021737 0ustar zuulzuul00000000000000# 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 testtools class FakeTestClass(testtools.TestCase): def test_pass(self): self.assertTrue(False) def test_pass_list(self): test_list = ['test', 'a', 'b'] self.assertIn('fail', test_list) tempest-17.2.0/tempest/tests/files/passing-tests0000666000175100017510000000147313207044712021771 0ustar zuulzuul00000000000000# 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 testtools class FakeTestClass(testtools.TestCase): def test_pass(self): self.assertTrue(True) def test_pass_list(self): test_list = ['test', 'a', 'b'] self.assertIn('test', test_list) tempest-17.2.0/tempest/tests/files/setup.cfg0000666000175100017510000000114213207044712021054 0ustar zuulzuul00000000000000[metadata] name = tempest_unit_tests version = 1 summary = Fake Project for testing wrapper scripts author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 [global] setup-hooks = pbr.hooks.setup_hook tempest-17.2.0/tempest/tests/files/__init__.py0000666000175100017510000000000013207044712021334 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/files/testr-conf0000666000175100017510000000026513207044712021247 0ustar zuulzuul00000000000000[DEFAULT] test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list group_regex=([^\.]*\.)* tempest-17.2.0/tempest/tests/common/0000775000175100017510000000000013207045130017414 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/common/test_custom_matchers.py0000666000175100017510000000672113207044712024242 0ustar zuulzuul00000000000000# 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.common import custom_matchers from tempest.tests import base # Stolen from testtools/testtools/tests/matchers/helpers.py class TestMatchersInterface(object): def test_matches_match(self): matcher = self.matches_matcher matches = self.matches_matches mismatches = self.matches_mismatches for candidate in matches: self.assertIsNone(matcher.match(candidate)) for candidate in mismatches: mismatch = matcher.match(candidate) self.assertIsNotNone(mismatch) self.assertIsNotNone(getattr(mismatch, 'describe', None)) def test__str__(self): # [(expected, object to __str__)]. from testtools.matchers._doctest import DocTestMatches examples = self.str_examples for expected, matcher in examples: self.assertThat(matcher, DocTestMatches(expected)) def test_describe_difference(self): # [(expected, matchee, matcher), ...] examples = self.describe_examples for difference, matchee, matcher in examples: mismatch = matcher.match(matchee) self.assertEqual(difference, mismatch.describe()) def test_mismatch_details(self): # The mismatch object must provide get_details, which must return a # dictionary mapping names to Content objects. examples = self.describe_examples for difference, matchee, matcher in examples: mismatch = matcher.match(matchee) details = mismatch.get_details() self.assertEqual(dict(details), details) class TestMatchesDictExceptForKeys(base.TestCase, TestMatchersInterface): matches_matcher = custom_matchers.MatchesDictExceptForKeys( {'a': 1, 'b': 2, 'c': 3, 'd': 4}, ['c', 'd']) matches_matches = [ {'a': 1, 'b': 2, 'c': 3, 'd': 4}, {'a': 1, 'b': 2, 'c': 5}, {'a': 1, 'b': 2}, ] matches_mismatches = [ {}, {'foo': 1}, {'a': 1, 'b': 3}, {'a': 1, 'b': 2, 'foo': 1}, {'a': 1, 'b': None, 'foo': 1}, ] str_examples = [] describe_examples = [ ("Only in expected:\n" " {'a': 1, 'b': 2}\n", {}, matches_matcher), ("Only in expected:\n" " {'a': 1, 'b': 2}\n" "Only in actual:\n" " {'foo': 1}\n", {'foo': 1}, matches_matcher), ("Differences:\n" " b: expected 2, actual 3\n", {'a': 1, 'b': 3}, matches_matcher), ("Only in actual:\n" " {'foo': 1}\n", {'a': 1, 'b': 2, 'foo': 1}, matches_matcher), ("Only in actual:\n" " {'foo': 1}\n" "Differences:\n" " b: expected 2, actual None\n", {'a': 1, 'b': None, 'foo': 1}, matches_matcher) ] tempest-17.2.0/tempest/tests/common/test_alt_available.py0000666000175100017510000000613313207044712023617 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures from oslo_config import cfg from tempest.common import credentials_factory as credentials from tempest import config from tempest.tests import base from tempest.tests import fake_config class TestAltAvailable(base.TestCase): identity_version = 'v2' def setUp(self): super(TestAltAvailable, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) def run_test(self, dynamic_creds, use_accounts_file, creds): cfg.CONF.set_default('use_dynamic_credentials', dynamic_creds, group='auth') if use_accounts_file: accounts = [dict(username="u%s" % ii, project_name="t%s" % ii, password="p") for ii in creds] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=accounts)) cfg.CONF.set_default('test_accounts_file', use_accounts_file, group='auth') self.useFixture(fixtures.MockPatch('os.path.isfile', return_value=True)) else: self.useFixture(fixtures.MockPatch('os.path.isfile', return_value=False)) expected = len(set(creds)) > 1 or dynamic_creds observed = credentials.is_alt_available( identity_version=self.identity_version) self.assertEqual(expected, observed) # Dynamic credentials implies alt so only one test case for True def test__dynamic_creds__accounts_file__one_user(self): self.run_test(dynamic_creds=True, use_accounts_file=False, creds=['1', '2']) def test__no_dynamic_creds__accounts_file__one_user(self): self.run_test(dynamic_creds=False, use_accounts_file=True, creds=['1']) def test__no_dynamic_creds__accounts_file__two_users(self): self.run_test(dynamic_creds=False, use_accounts_file=True, creds=['1', '2']) def test__no_dynamic_creds__accounts_file__two_users_identical(self): self.run_test(dynamic_creds=False, use_accounts_file=True, creds=['1', '1']) class TestAltAvailableV3(TestAltAvailable): identity_version = 'v3' tempest-17.2.0/tempest/tests/common/test_image.py0000666000175100017510000000432313207044712022120 0ustar zuulzuul00000000000000# 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.common import image from tempest.lib.common import rest_client from tempest.tests import base class TestImage(base.TestCase): def test_get_image_meta_from_headers(self): resp = { 'x-image-meta-id': 'ea30c926-0629-4400-bb6e-f8a8da6a4e56', 'x-image-meta-owner': '8f421f9470e645b1b10f5d2db7804924', 'x-image-meta-status': 'queued', 'x-image-meta-name': 'New Http Image' } respbody = rest_client.ResponseBody(resp) observed = image.get_image_meta_from_headers(respbody) expected = { 'properties': {}, 'id': 'ea30c926-0629-4400-bb6e-f8a8da6a4e56', 'owner': '8f421f9470e645b1b10f5d2db7804924', 'status': 'queued', 'name': 'New Http Image' } self.assertEqual(expected, observed) def test_image_meta_to_headers(self): observed = image.image_meta_to_headers( name='test', container_format='wrong', disk_format='vhd', copy_from='http://localhost/images/10', properties={'foo': 'bar'}, api={'abc': 'def'}, purge_props=True) expected = { 'x-image-meta-name': 'test', 'x-image-meta-container_format': 'wrong', 'x-image-meta-disk_format': 'vhd', 'x-glance-api-copy-from': 'http://localhost/images/10', 'x-image-meta-property-foo': 'bar', 'x-glance-api-property-abc': 'def', 'x-glance-registry-purge-props': True } self.assertEqual(expected, observed) tempest-17.2.0/tempest/tests/common/test_waiters.py0000666000175100017510000000620413207044712022514 0ustar zuulzuul00000000000000# Copyright 2014 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 time import mock from tempest.common import waiters from tempest import exceptions from tempest.lib import exceptions as lib_exc from tempest.lib.services.volume.v2 import volumes_client from tempest.tests import base import tempest.tests.utils as utils class TestImageWaiters(base.TestCase): def setUp(self): super(TestImageWaiters, self).setUp() self.client = mock.MagicMock() self.client.build_timeout = 1 self.client.build_interval = 1 def test_wait_for_image_status(self): self.client.show_image.return_value = ({'status': 'active'}) start_time = int(time.time()) waiters.wait_for_image_status(self.client, 'fake_image_id', 'active') end_time = int(time.time()) # Ensure waiter returns before build_timeout self.assertLess((end_time - start_time), 10) def test_wait_for_image_status_timeout(self): 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(lib_exc.TimeoutException, waiters.wait_for_image_status, self.client, 'fake_image_id', 'active') def test_wait_for_image_status_error_on_image_create(self): self.client.show_image.return_value = ({'status': 'ERROR'}) self.assertRaises(exceptions.AddImageException, waiters.wait_for_image_status, self.client, 'fake_image_id', 'active') @mock.patch.object(time, 'sleep') def test_wait_for_volume_status_error_restoring(self, mock_sleep): # Tests that the wait method raises VolumeRestoreErrorException if # the volume status is 'error_restoring'. client = mock.Mock(spec=volumes_client.VolumesClient, resource_type="volume", build_interval=1) volume1 = {'volume': {'status': 'restoring-backup'}} volume2 = {'volume': {'status': 'error_restoring'}} mock_show = mock.Mock(side_effect=(volume1, volume2)) client.show_volume = mock_show volume_id = '7532b91e-aa0a-4e06-b3e5-20c0c5ee1caa' self.assertRaises(exceptions.VolumeRestoreErrorException, waiters.wait_for_volume_resource_status, client, volume_id, 'available') mock_show.assert_has_calls([mock.call(volume_id), mock.call(volume_id)]) mock_sleep.assert_called_once_with(1) tempest-17.2.0/tempest/tests/common/test_credentials_factory.py0000666000175100017510000003254413207044712025070 0ustar zuulzuul00000000000000# Copyright 2017 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 mock from oslo_config import cfg import testtools from tempest.common import credentials_factory as cf from tempest import config from tempest.lib.common import dynamic_creds from tempest.lib.common import preprov_creds from tempest.lib import exceptions from tempest.tests import base from tempest.tests import fake_config from tempest.tests.lib import fake_credentials class TestCredentialsFactory(base.TestCase): def setUp(self): super(TestCredentialsFactory, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) def test_get_dynamic_provider_params_creds_v2(self): expected_uri = 'EXPECTED_V2_URI' cfg.CONF.set_default('uri', expected_uri, group='identity') admin_creds = fake_credentials.FakeCredentials() params = cf.get_dynamic_provider_params('v2', admin_creds=admin_creds) expected_params = dict(identity_uri=expected_uri, admin_creds=admin_creds) for key in expected_params: self.assertIn(key, params) self.assertEqual(expected_params[key], params[key]) def test_get_dynamic_provider_params_creds_v3(self): expected_uri = 'EXPECTED_V3_URI' cfg.CONF.set_default('uri_v3', expected_uri, group='identity') admin_creds = fake_credentials.FakeCredentials() params = cf.get_dynamic_provider_params('v3', admin_creds=admin_creds) expected_params = dict(identity_uri=expected_uri, admin_creds=admin_creds) for key in expected_params: self.assertIn(key, params) self.assertEqual(expected_params[key], params[key]) def test_get_dynamic_provider_params_creds_vx(self): admin_creds = fake_credentials.FakeCredentials() invalid_version = 'invalid_version_x' with testtools.ExpectedException( exc_type=exceptions.InvalidIdentityVersion, value_re='Invalid version ' + invalid_version): cf.get_dynamic_provider_params(invalid_version, admin_creds=admin_creds) def test_get_dynamic_provider_params_no_creds(self): expected_identity_version = 'v3' with mock.patch.object( cf, 'get_configured_admin_credentials') as admin_creds_mock: cf.get_dynamic_provider_params(expected_identity_version) admin_creds_mock.assert_called_once_with( fill_in=True, identity_version=expected_identity_version) def test_get_preprov_provider_params_creds_v2(self): expected_uri = 'EXPECTED_V2_URI' cfg.CONF.set_default('uri', expected_uri, group='identity') params = cf.get_preprov_provider_params('v2') self.assertIn('identity_uri', params) self.assertEqual(expected_uri, params['identity_uri']) def test_get_preprov_provider_params_creds_v3(self): expected_uri = 'EXPECTED_V3_URI' cfg.CONF.set_default('uri_v3', expected_uri, group='identity') params = cf.get_preprov_provider_params('v3') self.assertIn('identity_uri', params) self.assertEqual(expected_uri, params['identity_uri']) def test_get_preprov_provider_params_creds_vx(self): invalid_version = 'invalid_version_x' with testtools.ExpectedException( exc_type=exceptions.InvalidIdentityVersion, value_re='Invalid version ' + invalid_version): cf.get_dynamic_provider_params(invalid_version) @mock.patch.object(dynamic_creds, 'DynamicCredentialProvider') @mock.patch.object(cf, 'get_dynamic_provider_params') def test_get_credentials_provider_dynamic( self, mock_dynamic_provider_params, mock_dynamic_credentials_provider_class): cfg.CONF.set_default('use_dynamic_credentials', True, group='auth') expected_params = {'foo': 'bar'} mock_dynamic_provider_params.return_value = expected_params expected_name = 'my_name' expected_network_resources = {'network': 'resources'} expected_identity_version = 'identity_version' cf.get_credentials_provider( expected_name, network_resources=expected_network_resources, force_tenant_isolation=False, identity_version=expected_identity_version) mock_dynamic_provider_params.assert_called_once_with( expected_identity_version) mock_dynamic_credentials_provider_class.assert_called_once_with( name=expected_name, network_resources=expected_network_resources, **expected_params) @mock.patch.object(preprov_creds, 'PreProvisionedCredentialProvider') @mock.patch.object(cf, 'get_preprov_provider_params') def test_get_credentials_provider_preprov( self, mock_preprov_provider_params, mock_preprov_credentials_provider_class): cfg.CONF.set_default('use_dynamic_credentials', False, group='auth') cfg.CONF.set_default('test_accounts_file', '/some/file', group='auth') expected_params = {'foo': 'bar'} mock_preprov_provider_params.return_value = expected_params expected_name = 'my_name' expected_identity_version = 'identity_version' cf.get_credentials_provider( expected_name, force_tenant_isolation=False, identity_version=expected_identity_version) mock_preprov_provider_params.assert_called_once_with( expected_identity_version) mock_preprov_credentials_provider_class.assert_called_once_with( name=expected_name, **expected_params) def test_get_credentials_provider_preprov_no_file(self): cfg.CONF.set_default('use_dynamic_credentials', False, group='auth') cfg.CONF.set_default('test_accounts_file', None, group='auth') with testtools.ExpectedException( exc_type=exceptions.InvalidConfiguration): cf.get_credentials_provider( 'some_name', force_tenant_isolation=False, identity_version='some_version') @mock.patch.object(dynamic_creds, 'DynamicCredentialProvider') @mock.patch.object(cf, 'get_dynamic_provider_params') def test_get_credentials_provider_force_dynamic( self, mock_dynamic_provider_params, mock_dynamic_credentials_provider_class): cfg.CONF.set_default('use_dynamic_credentials', False, group='auth') expected_params = {'foo': 'bar'} mock_dynamic_provider_params.return_value = expected_params expected_name = 'my_name' expected_network_resources = {'network': 'resources'} expected_identity_version = 'identity_version' cf.get_credentials_provider( expected_name, network_resources=expected_network_resources, force_tenant_isolation=True, identity_version=expected_identity_version) mock_dynamic_provider_params.assert_called_once_with( expected_identity_version) mock_dynamic_credentials_provider_class.assert_called_once_with( name=expected_name, network_resources=expected_network_resources, **expected_params) @mock.patch.object(cf, 'get_credentials') def test_get_configured_admin_credentials(self, mock_get_credentials): cfg.CONF.set_default('auth_version', 'v3', 'identity') all_params = [('admin_username', 'username', 'my_name'), ('admin_password', 'password', 'secret'), ('admin_project_name', 'project_name', 'my_pname'), ('admin_domain_name', 'domain_name', 'my_dname')] expected_result = 'my_admin_credentials' mock_get_credentials.return_value = expected_result for config_item, _, value in all_params: cfg.CONF.set_default(config_item, value, 'auth') # Build the expected params expected_params = dict( [(field, value) for _, field, value in all_params]) expected_params.update(config.service_client_config()) admin_creds = cf.get_configured_admin_credentials() mock_get_credentials.assert_called_once_with( fill_in=True, identity_version='v3', **expected_params) self.assertEqual(expected_result, admin_creds) @mock.patch.object(cf, 'get_credentials') def test_get_configured_admin_credentials_not_fill_valid( self, mock_get_credentials): cfg.CONF.set_default('auth_version', 'v2', 'identity') all_params = [('admin_username', 'username', 'my_name'), ('admin_password', 'password', 'secret'), ('admin_project_name', 'project_name', 'my_pname'), ('admin_domain_name', 'domain_name', 'my_dname')] expected_result = mock.Mock() expected_result.is_valid.return_value = True mock_get_credentials.return_value = expected_result for config_item, _, value in all_params: cfg.CONF.set_default(config_item, value, 'auth') # Build the expected params expected_params = dict( [(field, value) for _, field, value in all_params]) expected_params.update(config.service_client_config()) admin_creds = cf.get_configured_admin_credentials( fill_in=False, identity_version='v3') mock_get_credentials.assert_called_once_with( fill_in=False, identity_version='v3', **expected_params) self.assertEqual(expected_result, admin_creds) expected_result.is_valid.assert_called_once() @mock.patch.object(cf, 'get_credentials') def test_get_configured_admin_credentials_not_fill_not_valid( self, mock_get_credentials): cfg.CONF.set_default('auth_version', 'v2', 'identity') expected_result = mock.Mock() expected_result.is_valid.return_value = False mock_get_credentials.return_value = expected_result with testtools.ExpectedException(exceptions.InvalidConfiguration, value_re='.*\n.*identity version v2'): cf.get_configured_admin_credentials(fill_in=False) @mock.patch('tempest.lib.auth.get_credentials') def test_get_credentials_v2(self, mock_auth_get_credentials): expected_uri = 'V2_URI' expected_result = 'my_creds' mock_auth_get_credentials.return_value = expected_result cfg.CONF.set_default('uri', expected_uri, 'identity') params = {'foo': 'bar'} expected_params = params.copy() expected_params.update(config.service_client_config()) result = cf.get_credentials(identity_version='v2', **params) self.assertEqual(expected_result, result) mock_auth_get_credentials.assert_called_once_with( expected_uri, fill_in=True, identity_version='v2', **expected_params) @mock.patch('tempest.lib.auth.get_credentials') def test_get_credentials_v3_no_domain(self, mock_auth_get_credentials): expected_uri = 'V3_URI' expected_result = 'my_creds' expected_domain = 'my_domain' mock_auth_get_credentials.return_value = expected_result cfg.CONF.set_default('uri_v3', expected_uri, 'identity') cfg.CONF.set_default('default_credentials_domain_name', expected_domain, 'auth') params = {'foo': 'bar'} expected_params = params.copy() expected_params['domain_name'] = expected_domain expected_params.update(config.service_client_config()) result = cf.get_credentials(fill_in=False, identity_version='v3', **params) self.assertEqual(expected_result, result) mock_auth_get_credentials.assert_called_once_with( expected_uri, fill_in=False, identity_version='v3', **expected_params) @mock.patch('tempest.lib.auth.get_credentials') def test_get_credentials_v3_domain(self, mock_auth_get_credentials): expected_uri = 'V3_URI' expected_result = 'my_creds' expected_domain = 'my_domain' mock_auth_get_credentials.return_value = expected_result cfg.CONF.set_default('uri_v3', expected_uri, 'identity') cfg.CONF.set_default('default_credentials_domain_name', expected_domain, 'auth') params = {'foo': 'bar', 'user_domain_name': expected_domain} expected_params = params.copy() expected_params.update(config.service_client_config()) result = cf.get_credentials(fill_in=False, identity_version='v3', **params) self.assertEqual(expected_result, result) mock_auth_get_credentials.assert_called_once_with( expected_uri, fill_in=False, identity_version='v3', **expected_params) tempest-17.2.0/tempest/tests/common/utils/0000775000175100017510000000000013207045130020554 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/common/utils/__init__.py0000666000175100017510000000000013207044712022662 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/common/utils/linux/0000775000175100017510000000000013207045130021713 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/common/utils/linux/test_remote_client.py0000666000175100017510000002071513207044712026171 0ustar zuulzuul00000000000000# Copyright 2014 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 time import fixtures from oslo_config import cfg from tempest.common.utils.linux import remote_client from tempest import config from tempest.lib import exceptions as lib_exc from tempest.tests import base from tempest.tests import fake_config SERVER = { 'id': 'server_uuid', 'name': 'fake_server', 'status': 'ACTIVE' } BROKEN_SERVER = { 'id': 'broken_server_uuid', 'name': 'broken_server', 'status': 'ERROR' } class FakeServersClient(object): CONSOLE_OUTPUT = "Console output for %s" def get_console_output(self, server_id): status = 'ERROR' for s in SERVER, BROKEN_SERVER: if s['id'] == server_id: status = s['status'] if status == 'ERROR': raise lib_exc.BadRequest('Server in ERROR state') else: return dict(output=self.CONSOLE_OUTPUT % server_id) class TestRemoteClient(base.TestCase): def setUp(self): super(TestRemoteClient, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) cfg.CONF.set_default('ip_version_for_ssh', 4, group='validation') cfg.CONF.set_default('network_for_ssh', 'public', group='validation') cfg.CONF.set_default('connect_timeout', 1, group='validation') self.conn = remote_client.RemoteClient('127.0.0.1', 'user', 'pass') self.ssh_mock = self.useFixture(fixtures.MockPatchObject(self.conn, 'ssh_client')) def test_write_to_console_regular_str(self): self.conn.write_to_console('test') self._assert_exec_called_with( 'sudo sh -c "echo \\"test\\" >/dev/console"') def _test_write_to_console_helper(self, message, expected_call): self.conn.write_to_console(message) self._assert_exec_called_with(expected_call) def test_write_to_console_special_chars(self): self._test_write_to_console_helper( '\`', 'sudo sh -c "echo \\"\\\\\\`\\" >/dev/console"') self.conn.write_to_console('$') self._assert_exec_called_with( 'sudo sh -c "echo \\"\\\\$\\" >/dev/console"') # NOTE(maurosr): The tests below end up closer to an output format # assurance than a test since it's basically using comand_exec to format # the information using gnu/linux tools. def _assert_exec_called_with(self, cmd): cmd = "set -eu -o pipefail; PATH=$PATH:/sbin; " + cmd self.ssh_mock.mock.exec_command.assert_called_with(cmd) def test_get_disks(self): output_lsblk = """\ NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 128035676160 0 disk sdb 8:16 0 1000204886016 0 disk sr0 11:0 1 1073741312 0 rom""" result = """\ NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 128035676160 0 disk sdb 8:16 0 1000204886016 0 disk""" self.ssh_mock.mock.exec_command.return_value = output_lsblk self.assertEqual(self.conn.get_disks(), result) self._assert_exec_called_with('lsblk -lb --nodeps') def test_get_boot_time(self): booted_at = 10000 uptime_sec = 5000.02 self.ssh_mock.mock.exec_command.return_value = uptime_sec self.useFixture(fixtures.MockPatchObject( time, 'time', return_value=booted_at + uptime_sec)) self.assertEqual(self.conn.get_boot_time(), time.localtime(booted_at)) self._assert_exec_called_with('cut -f1 -d. /proc/uptime') def test_ping_host(self): ping_response = """PING localhost (127.0.0.1) 70(98) bytes of data. 78 bytes from localhost (127.0.0.1): icmp_req=1 ttl=64 time=0.048 ms 78 bytes from localhost (127.0.0.1): icmp_req=2 ttl=64 time=0.048 ms --- localhost ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.048/0.048/0.048/0.000 ms""" self.ssh_mock.mock.exec_command.return_value = ping_response self.assertEqual(self.conn.ping_host('127.0.0.1', count=2, size=70), ping_response) self._assert_exec_called_with('ping -c2 -w2 -s70 127.0.0.1') def test_get_mac_address(self): macs = """0a:0b:0c:0d:0e:0f a0:b0:c0:d0:e0:f0""" self.ssh_mock.mock.exec_command.return_value = macs self.assertEqual(self.conn.get_mac_address(), macs) self._assert_exec_called_with( "ip addr | awk '/ether/ {print $2}'") class TestRemoteClientWithServer(base.TestCase): server = SERVER def setUp(self): super(TestRemoteClientWithServer, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) cfg.CONF.set_default('ip_version_for_ssh', 4, group='validation') cfg.CONF.set_default('network_for_ssh', 'public', group='validation') cfg.CONF.set_default('connect_timeout', 1, group='validation') cfg.CONF.set_default('console_output', True, group='compute-feature-enabled') self.conn = remote_client.RemoteClient( '127.0.0.1', 'user', 'pass', server=self.server, servers_client=FakeServersClient()) self.useFixture(fixtures.MockPatch( 'tempest.lib.common.ssh.Client._get_ssh_connection', side_effect=lib_exc.SSHTimeout(host='127.0.0.1', user='user', password='pass'))) self.log = self.useFixture(fixtures.FakeLogger( name='tempest.lib.common.utils.linux.remote_client', level='DEBUG')) def test_validate_debug_ssh_console(self): self.assertRaises(lib_exc.SSHTimeout, self.conn.validate_authentication) msg = 'Caller: %s. Timeout trying to ssh to server %s' % ( 'TestRemoteClientWithServer:test_validate_debug_ssh_console', self.server) self.assertIn(msg, self.log.output) self.assertIn('Console output for', self.log.output) def test_exec_command_debug_ssh_console(self): self.assertRaises(lib_exc.SSHTimeout, self.conn.exec_command, 'fake command') self.assertIn('fake command', self.log.output) msg = 'Caller: %s. Timeout trying to ssh to server %s' % ( 'TestRemoteClientWithServer:test_exec_command_debug_ssh_console', self.server) self.assertIn(msg, self.log.output) self.assertIn('Console output for', self.log.output) class TestRemoteClientWithBrokenServer(TestRemoteClientWithServer): server = BROKEN_SERVER def test_validate_debug_ssh_console(self): self.assertRaises(lib_exc.SSHTimeout, self.conn.validate_authentication) msg = 'Caller: %s. Timeout trying to ssh to server %s' % ( 'TestRemoteClientWithBrokenServer:test_validate_debug_ssh_console', self.server) self.assertIn(msg, self.log.output) msg = 'Could not get console_log for server %s' % self.server['id'] self.assertIn(msg, self.log.output) def test_exec_command_debug_ssh_console(self): self.assertRaises(lib_exc.SSHTimeout, self.conn.exec_command, 'fake command') self.assertIn('fake command', self.log.output) caller = ":".join(['TestRemoteClientWithBrokenServer', 'test_exec_command_debug_ssh_console']) msg = 'Caller: %s. Timeout trying to ssh to server %s' % ( caller, self.server) self.assertIn(msg, self.log.output) msg = 'Could not get console_log for server %s' % self.server['id'] self.assertIn(msg, self.log.output) tempest-17.2.0/tempest/tests/common/utils/linux/__init__.py0000666000175100017510000000000013207044712024021 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/common/utils/test_net_utils.py0000666000175100017510000000224313207044712024203 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 tempest.common.utils import net_utils from tempest.lib import exceptions as lib_exc from tempest.tests import base class TestGetPingPayloadSize(base.TestCase): def test_ipv4(self): self.assertEqual(1422, net_utils.get_ping_payload_size(1450, 4)) def test_ipv6(self): self.assertEqual(1406, net_utils.get_ping_payload_size(1450, 6)) def test_too_low_mtu(self): self.assertRaises( lib_exc.BadRequest, net_utils.get_ping_payload_size, 10, 4) def test_None(self): self.assertIsNone(net_utils.get_ping_payload_size(None, mock.Mock())) tempest-17.2.0/tempest/tests/common/__init__.py0000666000175100017510000000000013207044712021522 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/common/test_compute.py0000666000175100017510000001013413207044712022507 0ustar zuulzuul00000000000000# Copyright 2017 Citrix Systems # 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 six.moves.urllib import parse as urlparse import mock from tempest.common import compute from tempest.tests import base class TestCompute(base.TestCase): def setUp(self): super(TestCompute, self).setUp() self.client_sock = mock.Mock() self.url = urlparse.urlparse("http://www.fake.com:80") def test_rfp_frame_not_cached(self): # rfp negotiation frame arrived separately after upgrade # response, so it's not cached. RFP_VERSION = b'RFB.003.003\x0a' rfp_frame_header = b'\x82\x0c' self.client_sock.recv.side_effect = [ b'fake response start\r\n', b'fake response end\r\n\r\n', rfp_frame_header, RFP_VERSION] expect_response = b'fake response start\r\nfake response end\r\n\r\n' webSocket = compute._WebSocket(self.client_sock, self.url) self.assertEqual(webSocket.response, expect_response) # no cache self.assertEqual(webSocket.cached_stream, b'') self.client_sock.recv.assert_has_calls([mock.call(4096), mock.call(4096)]) self.client_sock.recv.reset_mock() recv_version = webSocket.receive_frame() self.assertEqual(recv_version, RFP_VERSION) self.client_sock.recv.assert_has_calls([mock.call(2), mock.call(12)]) def test_rfp_frame_fully_cached(self): RFP_VERSION = b'RFB.003.003\x0a' rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION self.client_sock.recv.side_effect = [ b'fake response start\r\n', b'fake response end\r\n\r\n%s' % rfp_version_frame] expect_response = b'fake response start\r\nfake response end\r\n\r\n' webSocket = compute._WebSocket(self.client_sock, self.url) self.client_sock.recv.assert_has_calls([mock.call(4096), mock.call(4096)]) self.assertEqual(webSocket.response, expect_response) self.assertEqual(webSocket.cached_stream, rfp_version_frame) self.client_sock.recv.reset_mock() recv_version = webSocket.receive_frame() self.client_sock.recv.assert_not_called() self.assertEqual(recv_version, RFP_VERSION) # cached_stream should be empty in the end. self.assertEqual(webSocket.cached_stream, b'') def test_rfp_frame_partially_cached(self): RFP_VERSION = b'RFB.003.003\x0a' rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION frame_part1 = rfp_version_frame[:6] frame_part2 = rfp_version_frame[6:] self.client_sock.recv.side_effect = [ b'fake response start\r\n', b'fake response end\r\n\r\n%s' % frame_part1, frame_part2] expect_response = b'fake response start\r\nfake response end\r\n\r\n' webSocket = compute._WebSocket(self.client_sock, self.url) self.client_sock.recv.assert_has_calls([mock.call(4096), mock.call(4096)]) self.assertEqual(webSocket.response, expect_response) self.assertEqual(webSocket.cached_stream, frame_part1) self.client_sock.recv.reset_mock() recv_version = webSocket.receive_frame() self.client_sock.recv.assert_called_once_with(len(frame_part2)) self.assertEqual(recv_version, RFP_VERSION) # cached_stream should be empty in the end. self.assertEqual(webSocket.cached_stream, b'') tempest-17.2.0/tempest/tests/common/test_admin_available.py0000666000175100017510000001126413207044712024130 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures from oslo_config import cfg from tempest.common import credentials_factory as credentials from tempest import config from tempest.tests import base from tempest.tests import fake_config class TestAdminAvailable(base.TestCase): identity_version = 'v2' def setUp(self): super(TestAdminAvailable, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) def run_test(self, dynamic_creds, use_accounts_file, admin_creds): cfg.CONF.set_default('use_dynamic_credentials', dynamic_creds, group='auth') if use_accounts_file: accounts = [{'username': 'u1', 'project_name': 't1', 'password': 'p'}, {'username': 'u2', 'project_name': 't2', 'password': 'p'}] if admin_creds == 'role': accounts.append({'username': 'admin', 'project_name': 'admin', 'password': 'p', 'roles': ['admin']}) elif admin_creds == 'type': accounts.append({'username': 'admin', 'project_name': 'admin', 'password': 'p', 'types': ['admin']}) self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=accounts)) cfg.CONF.set_default('test_accounts_file', use_accounts_file, group='auth') self.useFixture(fixtures.MockPatch('os.path.isfile', return_value=True)) else: self.useFixture(fixtures.MockPatch('os.path.isfile', return_value=False)) if admin_creds: username = 'u' project = 't' password = 'p' domain = 'd' else: username = None project = None password = None domain = None cfg.CONF.set_default('admin_username', username, group='auth') cfg.CONF.set_default('admin_project_name', project, group='auth') cfg.CONF.set_default('admin_password', password, group='auth') cfg.CONF.set_default('admin_domain_name', domain, group='auth') expected = admin_creds is not None or dynamic_creds observed = credentials.is_admin_available( identity_version=self.identity_version) self.assertEqual(expected, observed) # Dynamic credentials implies admin so only one test case for True def test__dynamic_creds__accounts_file__no_admin(self): self.run_test(dynamic_creds=True, use_accounts_file=True, admin_creds=None) def test__no_dynamic_creds__accounts_file__no_admin(self): self.run_test(dynamic_creds=False, use_accounts_file=True, admin_creds=None) def test__no_dynamic_creds__accounts_file__admin_role(self): self.run_test(dynamic_creds=False, use_accounts_file=True, admin_creds='role') def test__no_dynamic_creds__accounts_file__admin_type(self): self.run_test(dynamic_creds=False, use_accounts_file=True, admin_creds='type') def test__no_dynamic_creds__no_accounts_file__no_admin(self): self.run_test(dynamic_creds=False, use_accounts_file=False, admin_creds=None) def test__no_dynamic_creds__no_accounts_file__admin(self): self.run_test(dynamic_creds=False, use_accounts_file=False, admin_creds='role') class TestAdminAvailableV3(TestAdminAvailable): identity_version = 'v3' tempest-17.2.0/tempest/tests/__init__.py0000666000175100017510000000000013207044712020232 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/base.py0000666000175100017510000000327713207044712017430 0ustar zuulzuul00000000000000# 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 class TestCase(base.BaseTestCase): 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 def patchobject(self, target, attribute, new=mock.DEFAULT): """Convenient wrapper around `mock.patch.object` Returns a started mock that will be automatically stopped after the test ran. """ p = mock.patch.object(target, attribute, new) m = p.start() self.addCleanup(p.stop) return m tempest-17.2.0/tempest/tests/test_imports.py0000666000175100017510000000444113207044712021244 0ustar zuulzuul00000000000000# Copyright 2017 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 tempest.tests import base class ConfCounter(object): def __init__(self, *args, **kwargs): self.count = 0 def __getattr__(self, key): self.count += 1 return mock.MagicMock() def get_counts(self): return self.count class TestImports(base.TestCase): def setUp(self): super(TestImports, self).setUp() self.conf_mock = self.patch('tempest.config.CONF', new_callable=ConfCounter) def test_account_generator_command_import(self): from tempest.cmd import account_generator # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_cleanup_command_import(self): from tempest.cmd import cleanup # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_init_command_import(self): from tempest.cmd import init # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_list_plugins_command_import(self): from tempest.cmd import list_plugins # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_run_command_import(self): from tempest.cmd import run # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_subunit_descibe_command_import(self): from tempest.cmd import subunit_describe_calls # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_verify_tempest_config_command_import(self): from tempest.cmd import verify_tempest_config # noqa self.assertEqual(0, self.conf_mock.get_counts()) def test_workspace_command_import(self): from tempest.cmd import workspace # noqa self.assertEqual(0, self.conf_mock.get_counts()) tempest-17.2.0/tempest/tests/services/0000775000175100017510000000000013207045130017747 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/services/object_storage/0000775000175100017510000000000013207045130022741 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/services/object_storage/test_object_client.py0000666000175100017510000001065713207044712027176 0ustar zuulzuul00000000000000# Copyright 2016 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 mock from tempest.lib import exceptions from tempest.services.object_storage import object_client from tempest.tests import base from tempest.tests.lib import fake_auth_provider class TestObjectClient(base.TestCase): def setUp(self): super(TestObjectClient, self).setUp() self.fake_auth = fake_auth_provider.FakeAuthProvider() self.url = self.fake_auth.base_url(None) self.object_client = object_client.ObjectClient(self.fake_auth, 'swift', 'region1') @mock.patch.object(object_client, '_create_connection') def test_create_object_continue_no_data(self, mock_poc): self._validate_create_object_continue(None, mock_poc) @mock.patch.object(object_client, '_create_connection') def test_create_object_continue_with_data(self, mock_poc): self._validate_create_object_continue('hello', mock_poc) @mock.patch.object(object_client, '_create_connection') def test_create_continue_with_no_continue_received(self, mock_poc): self._validate_create_object_continue('hello', mock_poc, initial_status=201) def _validate_create_object_continue(self, req_data, mock_poc, initial_status=100): expected_hdrs = { 'X-Auth-Token': self.fake_auth.get_token(), 'content-length': 0 if req_data is None else len(req_data), 'Expect': '100-continue'} # Setup the Mocks prior to invoking the object creation mock_resp_cls = mock.Mock() mock_resp_cls._read_status.return_value = ("1", initial_status, "OK") mock_poc.return_value.response_class.return_value = mock_resp_cls # This is the final expected return value mock_poc.return_value.getresponse.return_value.status = 201 mock_poc.return_value.getresponse.return_value.reason = 'OK' # Call method to PUT object using expect:100-continue cnt = "container1" obj = "object1" path = "/%s/%s" % (cnt, obj) # If the expected initial status is not 100, then an exception # should be thrown and the connection closed if initial_status is 100: status, reason = \ self.object_client.create_object_continue(cnt, obj, req_data) else: self.assertRaises(exceptions.UnexpectedResponseCode, self.object_client.create_object_continue, cnt, obj, req_data) mock_poc.return_value.close.assert_called_once_with() # Verify that putrequest is called 1 time with the appropriate values mock_poc.return_value.putrequest.assert_called_once_with('PUT', path) # Verify that headers were written, including "Expect:100-continue" calls = [] for header, value in expected_hdrs.items(): calls.append(mock.call(header, value)) mock_poc.return_value.putheader.assert_has_calls(calls, False) mock_poc.return_value.endheaders.assert_called_once_with() # The following steps are only taken if the initial status is 100 if initial_status is 100: # Verify that the method returned what it was supposed to self.assertEqual(status, 201) # Verify that _safe_read was called once to remove the CRLF # after the 100 response mock_rc = mock_poc.return_value.response_class.return_value mock_rc._safe_read.assert_called_once_with(2) # Verify the actual data was written via send mock_poc.return_value.send.assert_called_once_with(req_data) # Verify that the getresponse method was called to receive # the final mock_poc.return_value.getresponse.assert_called_once_with() tempest-17.2.0/tempest/tests/utils.py0000666000175100017510000000212313207044712017643 0ustar zuulzuul00000000000000# 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 tempest-17.2.0/tempest/tests/test_microversions.py0000666000175100017510000001420513207044712022450 0ustar zuulzuul00000000000000# 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_config import cfg import six import testtools from tempest.api.compute import base as compute_base from tempest import config from tempest.lib import exceptions from tempest.tests import base from tempest.tests import fake_config class VersionTestNoneTolatest(compute_base.BaseV2ComputeTest): min_microversion = None max_microversion = 'latest' class VersionTestNoneTo2_2(compute_base.BaseV2ComputeTest): min_microversion = None max_microversion = '2.2' class VersionTest2_3ToLatest(compute_base.BaseV2ComputeTest): min_microversion = '2.3' max_microversion = 'latest' class VersionTest2_5To2_10(compute_base.BaseV2ComputeTest): min_microversion = '2.5' max_microversion = '2.10' class VersionTest2_10To2_10(compute_base.BaseV2ComputeTest): min_microversion = '2.10' max_microversion = '2.10' class InvalidVersionTest(compute_base.BaseV2ComputeTest): min_microversion = '2.11' max_microversion = '2.1' class TestMicroversionsTestsClass(base.TestCase): def setUp(self): super(TestMicroversionsTestsClass, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) def _test_version(self, cfg_min, cfg_max, expected_pass_tests, expected_skip_tests): cfg.CONF.set_default('min_microversion', cfg_min, group='compute') cfg.CONF.set_default('max_microversion', cfg_max, group='compute') try: for test_class in expected_pass_tests: test_class.skip_checks() for test_class in expected_skip_tests: self.assertRaises(testtools.TestCase.skipException, test_class.skip_checks) except testtools.TestCase.skipException as e: raise testtools.TestCase.failureException(six.text_type(e)) def test_config_version_none_none(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2] expected_skip_tests = [VersionTest2_3ToLatest, VersionTest2_5To2_10, VersionTest2_10To2_10] self._test_version(None, None, expected_pass_tests, expected_skip_tests) def test_config_version_none_23(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2, VersionTest2_3ToLatest] expected_skip_tests = [VersionTest2_5To2_10, VersionTest2_10To2_10] self._test_version(None, '2.3', expected_pass_tests, expected_skip_tests) def test_config_version_22_latest(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2, VersionTest2_3ToLatest, VersionTest2_5To2_10, VersionTest2_10To2_10] expected_skip_tests = [] self._test_version('2.2', 'latest', expected_pass_tests, expected_skip_tests) def test_config_version_22_23(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2, VersionTest2_3ToLatest] expected_skip_tests = [VersionTest2_5To2_10, VersionTest2_10To2_10] self._test_version('2.2', '2.3', expected_pass_tests, expected_skip_tests) def test_config_version_210_210(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTest2_3ToLatest, VersionTest2_5To2_10, VersionTest2_10To2_10] expected_skip_tests = [VersionTestNoneTo2_2] self._test_version('2.10', '2.10', expected_pass_tests, expected_skip_tests) def test_config_version_none_latest(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2, VersionTest2_3ToLatest, VersionTest2_5To2_10, VersionTest2_10To2_10] expected_skip_tests = [] self._test_version(None, 'latest', expected_pass_tests, expected_skip_tests) def test_config_version_latest_latest(self): expected_pass_tests = [VersionTestNoneTolatest, VersionTest2_3ToLatest] expected_skip_tests = [VersionTestNoneTo2_2, VersionTest2_5To2_10, VersionTest2_10To2_10] self._test_version('latest', 'latest', expected_pass_tests, expected_skip_tests) def test_config_invalid_version(self): cfg.CONF.set_default('min_microversion', '2.5', group='compute') cfg.CONF.set_default('max_microversion', '2.1', group='compute') self.assertRaises(exceptions.InvalidAPIVersionRange, VersionTestNoneTolatest.skip_checks) def test_config_version_invalid_test_version(self): cfg.CONF.set_default('min_microversion', None, group='compute') cfg.CONF.set_default('max_microversion', '2.13', group='compute') self.assertRaises(exceptions.InvalidAPIVersionRange, InvalidVersionTest.skip_checks) tempest-17.2.0/tempest/tests/api/0000775000175100017510000000000013207045130016675 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/api/__init__.py0000666000175100017510000000000013207044712021003 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/api/compute/0000775000175100017510000000000013207045130020351 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/api/compute/__init__.py0000666000175100017510000000000013207044712022457 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/api/compute/test_base.py0000666000175100017510000002077013207044712022711 0ustar zuulzuul00000000000000# Copyright 2017 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 oslo_utils import uuidutils import six from tempest.api.compute import base as compute_base from tempest.common import waiters from tempest import exceptions from tempest.lib import exceptions as lib_exc from tempest.tests import base class TestBaseV2ComputeTest(base.TestCase): """Unit tests for utility functions in BaseV2ComputeTest.""" @mock.patch.multiple(compute_base.BaseV2ComputeTest, compute_images_client=mock.DEFAULT, images=[], create=True) def test_create_image_from_server_no_wait(self, compute_images_client): """Tests create_image_from_server without the wait_until kwarg.""" # setup mocks image_id = uuidutils.generate_uuid() fake_image = mock.Mock(response={'location': image_id}) compute_images_client.create_image.return_value = fake_image # call the utility method cleanup_path = 'tempest.test.BaseTestCase.addClassResourceCleanup' with mock.patch(cleanup_path) as mock_cleanup: image = compute_base.BaseV2ComputeTest.create_image_from_server( mock.sentinel.server_id, name='fake-snapshot-name') self.assertEqual(fake_image, image) # make our assertions compute_images_client.create_image.assert_called_once_with( mock.sentinel.server_id, name='fake-snapshot-name') mock_cleanup.assert_called_once() self.assertIn(image_id, mock_cleanup.call_args[0]) @mock.patch.multiple(compute_base.BaseV2ComputeTest, compute_images_client=mock.DEFAULT, servers_client=mock.DEFAULT, images=[], create=True) @mock.patch.object(waiters, 'wait_for_image_status') @mock.patch.object(waiters, 'wait_for_server_status') def test_create_image_from_server_wait_until_active(self, wait_for_server_status, wait_for_image_status, servers_client, compute_images_client): """Tests create_image_from_server with wait_until='ACTIVE' kwarg.""" # setup mocks image_id = uuidutils.generate_uuid() fake_image = mock.Mock(response={'location': image_id}) compute_images_client.create_image.return_value = fake_image compute_images_client.show_image.return_value = ( {'image': fake_image}) # call the utility method image = compute_base.BaseV2ComputeTest.create_image_from_server( mock.sentinel.server_id, wait_until='ACTIVE') self.assertEqual(fake_image, image) # make our assertions wait_for_image_status.assert_called_once_with( compute_images_client, image_id, 'ACTIVE') wait_for_server_status.assert_called_once_with( servers_client, mock.sentinel.server_id, 'ACTIVE') compute_images_client.show_image.assert_called_once_with(image_id) @mock.patch.multiple(compute_base.BaseV2ComputeTest, compute_images_client=mock.DEFAULT, servers_client=mock.DEFAULT, images=[], create=True) @mock.patch.object(waiters, 'wait_for_image_status') @mock.patch.object(waiters, 'wait_for_server_status') def test_create_image_from_server_wait_until_active_no_server_wait( self, wait_for_server_status, wait_for_image_status, servers_client, compute_images_client): """Tests create_image_from_server with wait_until='ACTIVE' kwarg.""" # setup mocks image_id = uuidutils.generate_uuid() fake_image = mock.Mock(response={'location': image_id}) compute_images_client.create_image.return_value = fake_image compute_images_client.show_image.return_value = ( {'image': fake_image}) # call the utility method image = compute_base.BaseV2ComputeTest.create_image_from_server( mock.sentinel.server_id, wait_until='ACTIVE', wait_for_server=False) self.assertEqual(fake_image, image) # make our assertions wait_for_image_status.assert_called_once_with( compute_images_client, image_id, 'ACTIVE') self.assertEqual(0, wait_for_server_status.call_count) compute_images_client.show_image.assert_called_once_with(image_id) @mock.patch.multiple(compute_base.BaseV2ComputeTest, compute_images_client=mock.DEFAULT, servers_client=mock.DEFAULT, images=[], create=True) @mock.patch.object(waiters, 'wait_for_image_status', side_effect=lib_exc.NotFound) def _test_create_image_from_server_wait_until_active_not_found( self, wait_for_image_status, compute_images_client, servers_client, fault=None): # setup mocks image_id = uuidutils.generate_uuid() fake_image = mock.Mock(response={'location': image_id}) compute_images_client.create_image.return_value = fake_image fake_server = {'id': mock.sentinel.server_id} if fault: fake_server['fault'] = fault servers_client.show_server.return_value = {'server': fake_server} # call the utility method ex = self.assertRaises( exceptions.SnapshotNotFoundException, compute_base.BaseV2ComputeTest.create_image_from_server, mock.sentinel.server_id, wait_until='active') # make our assertions if fault: self.assertIn(fault, six.text_type(ex)) else: self.assertNotIn(fault, six.text_type(ex)) wait_for_image_status.assert_called_once_with( compute_images_client, image_id, 'active') servers_client.show_server.assert_called_once_with( mock.sentinel.server_id) def test_create_image_from_server_wait_until_active_not_found_no_fault( self): # Tests create_image_from_server with wait_until='active' kwarg and # the a 404 is raised while waiting for the image status to change. In # this test the server does not have a fault associated with it. self._test_create_image_from_server_wait_until_active_not_found() def test_create_image_from_server_wait_until_active_not_found_with_fault( self): # Tests create_image_from_server with wait_until='active' kwarg and # the a 404 is raised while waiting for the image status to change. In # this test the server has a fault associated with it. self._test_create_image_from_server_wait_until_active_not_found( fault='Lost connection to hypervisor!') @mock.patch.multiple(compute_base.BaseV2ComputeTest, compute_images_client=mock.DEFAULT, images=[], create=True) @mock.patch.object(waiters, 'wait_for_image_status', side_effect=lib_exc.NotFound) def test_create_image_from_server_wait_until_saving_not_found( self, wait_for_image_status, compute_images_client): # Tests create_image_from_server with wait_until='SAVING' kwarg and # the a 404 is raised while waiting for the image status to change. In # this case we do not get the server details and just re-raise the 404. # setup mocks image_id = uuidutils.generate_uuid() fake_image = mock.Mock(response={'location': image_id}) compute_images_client.create_image.return_value = fake_image # call the utility method self.assertRaises( lib_exc.NotFound, compute_base.BaseV2ComputeTest.create_image_from_server, mock.sentinel.server_id, wait_until='SAVING') # make our assertions wait_for_image_status.assert_called_once_with( compute_images_client, image_id, 'SAVING') tempest-17.2.0/tempest/tests/cmd/0000775000175100017510000000000013207045130016667 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/cmd/test_verify_tempest_config.py0000666000175100017510000005576013207044712024716 0ustar zuulzuul00000000000000# Copyright 2014 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 fixtures import mock from oslo_serialization import jsonutils as json from tempest import clients from tempest.cmd import verify_tempest_config from tempest.common import credentials_factory from tempest import config from tempest.lib.common import rest_client from tempest.lib.common.utils import data_utils from tempest.lib import exceptions as lib_exc from tempest.tests import base from tempest.tests import fake_config class TestGetAPIVersions(base.TestCase): def test_remove_version_project(self): f = verify_tempest_config._remove_version_project self.assertEqual('/', f('/v2.1/%s/' % data_utils.rand_uuid_hex())) self.assertEqual('', f('/v2.1/tenant_id')) self.assertEqual('', f('/v3')) self.assertEqual('/', f('/v3/')) self.assertEqual('/something/', f('/something/v2.1/tenant_id/')) self.assertEqual('/something', f('/something/v2.1/tenant_id')) self.assertEqual('/something', f('/something/v3')) self.assertEqual('/something/', f('/something/v3/')) self.assertEqual('/', f('/')) # http://localhost/ self.assertEqual('', f('')) # http://localhost def test_url_grab_versioned_nova_nossl(self): base_url = 'http://127.0.0.1:8774/v2/' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('http://127.0.0.1:8774/', endpoint) def test_url_grab_versioned_nova_ssl(self): base_url = 'https://127.0.0.1:8774/v3/' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('https://127.0.0.1:8774/', endpoint) def test_get_unversioned_endpoint_base(self): base_url = 'https://127.0.0.1:5000/' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('https://127.0.0.1:5000/', endpoint) def test_get_unversioned_endpoint_subpath(self): base_url = 'https://127.0.0.1/identity/v3' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('https://127.0.0.1/identity', endpoint) def test_get_unversioned_endpoint_subpath_trailing_solidus(self): base_url = 'https://127.0.0.1/identity/v3/' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('https://127.0.0.1/identity/', endpoint) class TestDiscovery(base.TestCase): def setUp(self): super(TestDiscovery, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) def test_get_keystone_api_versions(self): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': {'values': [{'id': 'v2.0'}, {'id': 'v3.0'}]}} fake_resp = json.dumps(fake_resp) self.useFixture(fixtures.MockPatch( 'tempest.lib.common.http.ClosingHttp.request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() versions = verify_tempest_config._get_api_versions(fake_os, 'keystone') self.assertIn('v2.0', versions) self.assertIn('v3.0', versions) def test_get_cinder_api_versions(self): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(fixtures.MockPatch( 'tempest.lib.common.http.ClosingHttp.request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() versions = verify_tempest_config._get_api_versions(fake_os, 'cinder') self.assertIn('v1.0', versions) self.assertIn('v2.0', versions) def test_get_nova_versions(self): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(fixtures.MockPatch( 'tempest.lib.common.http.ClosingHttp.request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() versions = verify_tempest_config._get_api_versions(fake_os, 'nova') self.assertIn('v2.0', versions) self.assertIn('v3.0', versions) def test_get_versions_invalid_response(self): # When the response doesn't contain a JSON response, an error is # logged. mock_log_error = self.useFixture(fixtures.MockPatchObject( verify_tempest_config.LOG, 'error')).mock self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint')) # Simulated response is not JSON. sample_body = ( 'Sample ResponseThis is the sample page ' 'for the web server. Why are you requesting it?') self.useFixture(fixtures.MockPatch( 'tempest.lib.common.http.ClosingHttp.request', return_value=(None, sample_body))) # service value doesn't matter, just needs to match what # _get_api_versions puts in its client_dict. self.assertRaises(ValueError, verify_tempest_config._get_api_versions, os=mock.MagicMock(), service='keystone') self.assertTrue(mock_log_error.called) def test_verify_api_versions(self): api_services = ['cinder', 'glance', 'keystone'] fake_os = mock.MagicMock() for svc in api_services: m = 'verify_%s_api_versions' % svc with mock.patch.object(verify_tempest_config, m) as verify_mock: verify_tempest_config.verify_api_versions(fake_os, svc, True) verify_mock.assert_called_once_with(fake_os, True) def test_verify_api_versions_not_implemented(self): api_services = ['cinder', 'glance', 'keystone'] fake_os = mock.MagicMock() for svc in api_services: m = 'verify_%s_api_versions' % svc with mock.patch.object(verify_tempest_config, m) as verify_mock: verify_tempest_config.verify_api_versions(fake_os, 'foo', True) self.assertFalse(verify_mock.called) @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_verify_keystone_api_versions_no_v3(self, mock_request): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': {'values': [{'id': 'v2.0'}]}} fake_resp = json.dumps(fake_resp) mock_request.return_value = (None, fake_resp) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_keystone_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v3', 'identity-feature-enabled', False, True) @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_verify_cinder_api_versions_no_v3(self, mock_request): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v2.0'}]} fake_resp = json.dumps(fake_resp) mock_request.return_value = (None, fake_resp) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_cinder_api_versions(fake_os, True) print_mock.assert_any_call('api_v3', 'volume-feature-enabled', False, True) self.assertEqual(1, print_mock.call_count) @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_verify_cinder_api_versions_no_v2(self, mock_request): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v3.0'}]} fake_resp = json.dumps(fake_resp) mock_request.return_value = (None, fake_resp) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_cinder_api_versions(fake_os, True) print_mock.assert_any_call('api_v2', 'volume-feature-enabled', False, True) self.assertEqual(1, print_mock.call_count) @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_verify_cinder_api_versions_no_v1(self, mock_request): self.useFixture(fixtures.MockPatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]} fake_resp = json.dumps(fake_resp) mock_request.return_value = (None, fake_resp) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_cinder_api_versions(fake_os, True) print_mock.assert_not_called() def test_verify_glance_version_no_v2_with_v1_1(self): # This test verifies that wrong config api_v2 = True is detected class FakeClient(object): def get_versions(self): return (None, ['v1.0']) fake_os = mock.MagicMock() fake_module = mock.MagicMock() fake_module.ImagesClient = FakeClient fake_os.image_v1 = fake_module with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v2', 'image-feature-enabled', False, True) def test_verify_glance_version_no_v2_with_v1_0(self): # This test verifies that wrong config api_v2 = True is detected class FakeClient(object): def get_versions(self): return (None, ['v1.0']) fake_os = mock.MagicMock() fake_module = mock.MagicMock() fake_module.ImagesClient = FakeClient fake_os.image_v1 = fake_module with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v2', 'image-feature-enabled', False, True) def test_verify_glance_version_no_v1(self): # This test verifies that wrong config api_v1 = True is detected class FakeClient(object): def get_versions(self): raise lib_exc.NotFound() def list_versions(self): return {'versions': [{'id': 'v2.0'}]} fake_os = mock.MagicMock() fake_module = mock.MagicMock() fake_module.ImagesClient = FakeClient fake_module.VersionsClient = FakeClient fake_os.image_v1 = fake_module fake_os.image_v2 = fake_module with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v1', 'image-feature-enabled', False, True) def test_verify_glance_version_no_version(self): # This test verifies that wrong config api_v1 = True is detected class FakeClient(object): def get_versions(self): raise lib_exc.NotFound() def list_versions(self): raise lib_exc.NotFound() fake_os = mock.MagicMock() fake_module = mock.MagicMock() fake_module.ImagesClient = FakeClient fake_module.VersionsClient = FakeClient fake_os.image_v1 = fake_module fake_os.image_v2 = fake_module with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('glance', 'service-available', False, True) def test_verify_extensions_neutron(self): def fake_list_extensions(): return {'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]} fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_extensions = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'neutron', {}) self.assertIn('neutron', results) self.assertIn('fake1', results['neutron']) self.assertTrue(results['neutron']['fake1']) self.assertIn('fake2', results['neutron']) self.assertTrue(results['neutron']['fake2']) self.assertIn('fake3', results['neutron']) self.assertFalse(results['neutron']['fake3']) self.assertIn('not_fake', results['neutron']) self.assertFalse(results['neutron']['not_fake']) def test_verify_extensions_neutron_all(self): def fake_list_extensions(): return {'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]} fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_extensions = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'neutron', {}) self.assertIn('neutron', results) self.assertIn('extensions', results['neutron']) self.assertEqual(sorted(['fake1', 'fake2', 'not_fake']), sorted(results['neutron']['extensions'])) def test_verify_extensions_cinder(self): def fake_list_extensions(): return {'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]} fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_extensions = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'cinder', {}) self.assertIn('cinder', results) self.assertIn('fake1', results['cinder']) self.assertTrue(results['cinder']['fake1']) self.assertIn('fake2', results['cinder']) self.assertTrue(results['cinder']['fake2']) self.assertIn('fake3', results['cinder']) self.assertFalse(results['cinder']['fake3']) self.assertIn('not_fake', results['cinder']) self.assertFalse(results['cinder']['not_fake']) def test_verify_extensions_cinder_all(self): def fake_list_extensions(): return {'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]} fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_extensions = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'cinder', {}) self.assertIn('cinder', results) self.assertIn('extensions', results['cinder']) self.assertEqual(sorted(['fake1', 'fake2', 'not_fake']), sorted(results['cinder']['extensions'])) def test_verify_extensions_nova(self): def fake_list_extensions(): return ([{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]) fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_extensions = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'nova', {}) self.assertIn('nova', results) self.assertIn('fake1', results['nova']) self.assertTrue(results['nova']['fake1']) self.assertIn('fake2', results['nova']) self.assertTrue(results['nova']['fake2']) self.assertIn('fake3', results['nova']) self.assertFalse(results['nova']['fake3']) self.assertIn('not_fake', results['nova']) self.assertFalse(results['nova']['not_fake']) def test_verify_extensions_nova_all(self): def fake_list_extensions(): return ({'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]}) fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_extensions = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'nova', {}) self.assertIn('nova', results) self.assertIn('extensions', results['nova']) self.assertEqual(sorted(['fake1', 'fake2', 'not_fake']), sorted(results['nova']['extensions'])) def test_verify_extensions_swift(self): def fake_list_extensions(): return {'fake1': 'metadata', 'fake2': 'metadata', 'not_fake': 'metadata', 'swift': 'metadata'} fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_capabilities = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'swift', {}) self.assertIn('swift', results) self.assertIn('fake1', results['swift']) self.assertTrue(results['swift']['fake1']) self.assertIn('fake2', results['swift']) self.assertTrue(results['swift']['fake2']) self.assertIn('fake3', results['swift']) self.assertFalse(results['swift']['fake3']) self.assertIn('not_fake', results['swift']) self.assertFalse(results['swift']['not_fake']) def test_verify_extensions_swift_all(self): def fake_list_extensions(): return {'fake1': 'metadata', 'fake2': 'metadata', 'not_fake': 'metadata', 'swift': 'metadata'} fake_os = mock.MagicMock() fake_client = mock.MagicMock() fake_client.list_capabilities = fake_list_extensions self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_extension_client', return_value=fake_client)) self.useFixture(fixtures.MockPatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'swift', {}) self.assertIn('swift', results) self.assertIn('extensions', results['swift']) self.assertEqual(sorted(['not_fake', 'fake1', 'fake2']), sorted(results['swift']['extensions'])) def test_get_extension_client(self): creds = credentials_factory.get_credentials( fill_in=False, username='fake_user', project_name='fake_project', password='fake_password') os = clients.Manager(creds) for service in ['nova', 'neutron', 'swift', 'cinder']: extensions_client = verify_tempest_config.get_extension_client( os, service) self.assertIsInstance(extensions_client, rest_client.RestClient) tempest-17.2.0/tempest/tests/cmd/test_workspace.py0000666000175100017510000001411613207044712022310 0ustar zuulzuul00000000000000# Copyright 2016 Rackspace # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 os import shutil import subprocess import tempfile from tempest.cmd import workspace from tempest.lib.common.utils import data_utils from tempest.tests import base class TestTempestWorkspaceBase(base.TestCase): def setUp(self): super(TestTempestWorkspaceBase, self).setUp() self.name = data_utils.rand_uuid() self.path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.path, ignore_errors=True) store_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True) self.store_file = os.path.join(store_dir, 'workspace.yaml') self.workspace_manager = workspace.WorkspaceManager( path=self.store_file) self.workspace_manager.register_new_workspace(self.name, self.path) class TestTempestWorkspace(TestTempestWorkspaceBase): def _run_cmd_gets_return_code(self, cmd, expected): process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() return_code = process.returncode msg = ("%s failed with:\nstdout: %s\nstderr: %s" % (' '.join(cmd), stdout, stderr)) self.assertEqual(return_code, expected, msg) def test_run_workspace_list(self): cmd = ['tempest', 'workspace', 'list', '--workspace-path', self.store_file] self._run_cmd_gets_return_code(cmd, 0) def test_run_workspace_register(self): name = data_utils.rand_uuid() path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, path, ignore_errors=True) cmd = ['tempest', 'workspace', 'register', '--workspace-path', self.store_file, '--name', name, '--path', path] self._run_cmd_gets_return_code(cmd, 0) self.assertIsNotNone(self.workspace_manager.get_workspace(name)) def test_run_workspace_rename(self): new_name = data_utils.rand_uuid() cmd = ['tempest', 'workspace', 'rename', '--workspace-path', self.store_file, '--old-name', self.name, '--new-name', new_name] self._run_cmd_gets_return_code(cmd, 0) self.assertIsNone(self.workspace_manager.get_workspace(self.name)) self.assertIsNotNone(self.workspace_manager.get_workspace(new_name)) def test_run_workspace_move(self): new_path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, new_path, ignore_errors=True) cmd = ['tempest', 'workspace', 'move', '--workspace-path', self.store_file, '--name', self.name, '--path', new_path] self._run_cmd_gets_return_code(cmd, 0) self.assertEqual( self.workspace_manager.get_workspace(self.name), new_path) def test_run_workspace_remove_entry(self): cmd = ['tempest', 'workspace', 'remove', '--workspace-path', self.store_file, '--name', self.name] self._run_cmd_gets_return_code(cmd, 0) self.assertIsNone(self.workspace_manager.get_workspace(self.name)) def test_run_workspace_remove_directory(self): cmd = ['tempest', 'workspace', 'remove', '--workspace-path', self.store_file, '--name', self.name, '--rmdir'] self._run_cmd_gets_return_code(cmd, 0) self.assertIsNone(self.workspace_manager.get_workspace(self.name)) class TestTempestWorkspaceManager(TestTempestWorkspaceBase): def setUp(self): super(TestTempestWorkspaceManager, self).setUp() self.name = data_utils.rand_uuid() self.path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.path, ignore_errors=True) store_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, store_dir, ignore_errors=True) self.store_file = os.path.join(store_dir, 'workspace.yaml') self.workspace_manager = workspace.WorkspaceManager( path=self.store_file) self.workspace_manager.register_new_workspace(self.name, self.path) def test_workspace_manager_get(self): self.assertIsNotNone(self.workspace_manager.get_workspace(self.name)) def test_workspace_manager_rename(self): new_name = data_utils.rand_uuid() self.workspace_manager.rename_workspace(self.name, new_name) self.assertIsNone(self.workspace_manager.get_workspace(self.name)) self.assertIsNotNone(self.workspace_manager.get_workspace(new_name)) def test_workspace_manager_move(self): new_path = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, new_path, ignore_errors=True) self.workspace_manager.move_workspace(self.name, new_path) self.assertEqual( self.workspace_manager.get_workspace(self.name), new_path) def test_workspace_manager_remove_entry(self): self.workspace_manager.remove_workspace_entry(self.name) self.assertIsNone(self.workspace_manager.get_workspace(self.name)) def test_workspace_manager_remove_directory(self): path = self.workspace_manager.remove_workspace_entry(self.name) self.workspace_manager.remove_workspace_directory(path) self.assertIsNone(self.workspace_manager.get_workspace(self.name)) def test_path_expansion(self): name = data_utils.rand_uuid() path = os.path.join("~", name) os.makedirs(os.path.expanduser(path)) self.addCleanup(shutil.rmtree, path, ignore_errors=True) self.workspace_manager.register_new_workspace(name, path) self.assertIsNotNone(self.workspace_manager.get_workspace(name)) tempest-17.2.0/tempest/tests/cmd/test_run.py0000666000175100017510000001631513207044712021121 0ustar zuulzuul00000000000000# 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 argparse import atexit import os import shutil import subprocess import tempfile import fixtures import mock from tempest.cmd import run from tempest.tests import base DEVNULL = open(os.devnull, 'wb') atexit.register(DEVNULL.close) class TestTempestRun(base.TestCase): def setUp(self): super(TestTempestRun, self).setUp() self.run_cmd = run.TempestRun(None, None) def test_build_options(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, "subunit", True) setattr(args, "parallel", False) setattr(args, "concurrency", 10) setattr(args, "load_list", '') options = self.run_cmd._build_options(args) self.assertEqual(['--subunit', '--concurrency=10'], options) def test__build_regex_default(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, 'smoke', False) setattr(args, 'regex', '') setattr(args, 'whitelist_file', None) setattr(args, 'blacklist_file', None) self.assertEqual('', self.run_cmd._build_regex(args)) def test__build_regex_smoke(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, "smoke", True) setattr(args, 'regex', '') setattr(args, 'whitelist_file', None) setattr(args, 'blacklist_file', None) self.assertEqual('smoke', self.run_cmd._build_regex(args)) def test__build_regex_regex(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, 'smoke', False) setattr(args, "regex", 'i_am_a_fun_little_regex') setattr(args, 'whitelist_file', None) setattr(args, 'blacklist_file', None) self.assertEqual('i_am_a_fun_little_regex', self.run_cmd._build_regex(args)) def test__build_whitelist_file(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, 'smoke', False) setattr(args, 'regex', None) self.tests = tempfile.NamedTemporaryFile( prefix='whitelist', delete=False) self.tests.write(b"volume \n compute") self.tests.close() setattr(args, 'whitelist_file', self.tests.name) setattr(args, 'blacklist_file', None) self.assertEqual("volume|compute", self.run_cmd._build_regex(args)) os.unlink(self.tests.name) def test__build_blacklist_file(self): args = mock.Mock(spec=argparse.Namespace) setattr(args, 'smoke', False) setattr(args, 'regex', None) self.tests = tempfile.NamedTemporaryFile( prefix='blacklist', delete=False) self.tests.write(b"volume \n compute") self.tests.close() setattr(args, 'whitelist_file', None) setattr(args, 'blacklist_file', self.tests.name) self.assertEqual("^((?!compute|volume).)*$", self.run_cmd._build_regex(args)) os.unlink(self.tests.name) class TestRunReturnCode(base.TestCase): def setUp(self): super(TestRunReturnCode, self).setUp() # Setup test dirs self.directory = tempfile.mkdtemp(prefix='tempest-unit') self.addCleanup(shutil.rmtree, self.directory) self.test_dir = os.path.join(self.directory, 'tests') os.mkdir(self.test_dir) # Setup Test files self.testr_conf_file = os.path.join(self.directory, '.testr.conf') self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg') self.passing_file = os.path.join(self.test_dir, 'test_passing.py') self.failing_file = os.path.join(self.test_dir, 'test_failing.py') self.init_file = os.path.join(self.test_dir, '__init__.py') self.setup_py = os.path.join(self.directory, 'setup.py') shutil.copy('tempest/tests/files/testr-conf', self.testr_conf_file) shutil.copy('tempest/tests/files/passing-tests', self.passing_file) shutil.copy('tempest/tests/files/failing-tests', self.failing_file) shutil.copy('setup.py', self.setup_py) shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file) shutil.copy('tempest/tests/files/__init__.py', self.init_file) # Change directory, run wrapper and check result self.addCleanup(os.chdir, os.path.abspath(os.curdir)) os.chdir(self.directory) def assertRunExit(self, cmd, expected): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() msg = ("Running %s got an unexpected returncode\n" "Stdout: %s\nStderr: %s" % (' '.join(cmd), out, err)) self.assertEqual(p.returncode, expected, msg) def test_tempest_run_passes(self): # Git init is required for the pbr testr command. pbr requires a git # version or an sdist to work. so make the test directory a git repo # too. subprocess.call(['git', 'init'], stderr=DEVNULL) self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0) def test_tempest_run_passes_with_testrepository(self): # Git init is required for the pbr testr command. pbr requires a git # version or an sdist to work. so make the test directory a git repo # too. subprocess.call(['git', 'init'], stderr=DEVNULL) subprocess.call(['testr', 'init']) self.assertRunExit(['tempest', 'run', '--regex', 'passing'], 0) def test_tempest_run_fails(self): # Git init is required for the pbr testr command. pbr requires a git # version or an sdist to work. so make the test directory a git repo # too. subprocess.call(['git', 'init'], stderr=DEVNULL) self.assertRunExit(['tempest', 'run'], 1) class TestTakeAction(base.TestCase): def test_workspace_not_registered(self): class Exception_(Exception): pass m_exit = self.useFixture(fixtures.MockPatch('sys.exit')).mock # sys.exit must not continue (or exit) m_exit.side_effect = Exception_ workspace = self.getUniqueString() tempest_run = run.TempestRun(app=mock.Mock(), app_args=mock.Mock()) parsed_args = mock.Mock() parsed_args.config_file = [] # Override $HOME so that empty workspace gets created in temp dir. self.useFixture(fixtures.TempHomeDir()) # Force use of the temporary home directory. parsed_args.workspace_path = None # Simulate --workspace argument. parsed_args.workspace = workspace self.assertRaises(Exception_, tempest_run.take_action, parsed_args) exit_msg = m_exit.call_args[0][0] self.assertIn(workspace, exit_msg) tempest-17.2.0/tempest/tests/cmd/test_tempest_init.py0000666000175100017510000001544713207044712023026 0ustar zuulzuul00000000000000# 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 os import fixtures from tempest.cmd import init from tempest.tests import base class TestTempestInit(base.TestCase): def test_generate_testr_conf(self): # Create fake conf dir conf_dir = self.useFixture(fixtures.TempDir()) init_cmd = init.TempestInit(None, None) init_cmd.generate_testr_conf(conf_dir.path) # Generate expected file contents top_level_path = os.path.dirname(os.path.dirname(init.__file__)) discover_path = os.path.join(top_level_path, 'test_discover') testr_conf_file = init.TESTR_CONF % (top_level_path, discover_path) conf_path = conf_dir.join('.testr.conf') with open(conf_path, 'r') as conf_file: self.assertEqual(conf_file.read(), testr_conf_file) def test_generate_sample_config(self): local_dir = self.useFixture(fixtures.TempDir()) etc_dir_path = os.path.join(local_dir.path, 'etc/') os.mkdir(etc_dir_path) init_cmd = init.TempestInit(None, None) local_sample_conf_file = os.path.join(etc_dir_path, 'tempest.conf.sample') # Verify no sample config file exist self.assertFalse(os.path.isfile(local_sample_conf_file)) init_cmd.generate_sample_config(local_dir.path) # Verify sample config file exist with some content self.assertTrue(os.path.isfile(local_sample_conf_file)) self.assertGreater(os.path.getsize(local_sample_conf_file), 0) def test_update_local_conf(self): local_dir = self.useFixture(fixtures.TempDir()) etc_dir_path = os.path.join(local_dir.path, 'etc/') os.mkdir(etc_dir_path) lock_dir = os.path.join(local_dir.path, 'tempest_lock') config_path = os.path.join(etc_dir_path, 'tempest.conf') log_dir = os.path.join(local_dir.path, 'logs') init_cmd = init.TempestInit(None, None) # Generate the config file init_cmd.generate_sample_config(local_dir.path) # Create a conf file with populated values config_parser_pre = init_cmd.get_configparser(config_path) with open(config_path, 'w+') as conf_file: # create the same section init will check for and add values to config_parser_pre.add_section('oslo_concurrency') config_parser_pre.set('oslo_concurrency', 'TEST', local_dir.path) # create a new section config_parser_pre.add_section('TEST') config_parser_pre.set('TEST', 'foo', "bar") config_parser_pre.write(conf_file) # Update the config file the same way tempest init does init_cmd.update_local_conf(config_path, lock_dir, log_dir) # parse the new config file to verify it config_parser_post = init_cmd.get_configparser(config_path) # check that our value in oslo_concurrency wasn't overwritten self.assertTrue(config_parser_post.has_section('oslo_concurrency')) self.assertEqual(config_parser_post.get('oslo_concurrency', 'TEST'), local_dir.path) # check that the lock directory was set correctly self.assertEqual(config_parser_post.get('oslo_concurrency', 'lock_path'), lock_dir) # check that our new section still exists and wasn't modified self.assertTrue(config_parser_post.has_section('TEST')) self.assertEqual(config_parser_post.get('TEST', 'foo'), 'bar') # check that the DEFAULT values are correct # NOTE(auggy): has_section ignores DEFAULT self.assertEqual(config_parser_post.get('DEFAULT', 'log_dir'), log_dir) def test_create_working_dir_with_existing_local_dir_non_empty(self): fake_local_dir = self.useFixture(fixtures.TempDir()) fake_local_conf_dir = self.useFixture(fixtures.TempDir()) open("%s/foo" % fake_local_dir.path, 'w').close() _init = init.TempestInit(None, None) self.assertRaises(OSError, _init.create_working_dir, fake_local_dir.path, fake_local_conf_dir.path) def test_create_working_dir(self): fake_local_dir = self.useFixture(fixtures.TempDir()) fake_local_conf_dir = self.useFixture(fixtures.TempDir()) os.rmdir(fake_local_dir.path) # Create a fake conf file fake_file = fake_local_conf_dir.join('conf_file.conf') open(fake_file, 'w').close() init_cmd = init.TempestInit(None, None) init_cmd.create_working_dir(fake_local_dir.path, fake_local_conf_dir.path) # Assert directories are created lock_path = os.path.join(fake_local_dir.path, 'tempest_lock') etc_dir = os.path.join(fake_local_dir.path, 'etc') log_dir = os.path.join(fake_local_dir.path, 'logs') testr_dir = os.path.join(fake_local_dir.path, '.testrepository') self.assertTrue(os.path.isdir(lock_path)) self.assertTrue(os.path.isdir(etc_dir)) self.assertTrue(os.path.isdir(log_dir)) self.assertTrue(os.path.isdir(testr_dir)) # Assert file creation fake_file_moved = os.path.join(etc_dir, 'conf_file.conf') local_conf_file = os.path.join(etc_dir, 'tempest.conf') local_testr_conf = os.path.join(fake_local_dir.path, '.testr.conf') self.assertTrue(os.path.isfile(fake_file_moved)) self.assertTrue(os.path.isfile(local_conf_file)) self.assertTrue(os.path.isfile(local_testr_conf)) def test_take_action_fails(self): class ParsedArgs(object): workspace_dir = self.useFixture(fixtures.TempDir()).path workspace_path = os.path.join(workspace_dir, 'workspace.yaml') name = 'test' dir_base = self.useFixture(fixtures.TempDir()).path dir = os.path.join(dir_base, 'foo', 'bar') config_dir = self.useFixture(fixtures.TempDir()).path show_global_dir = False pa = ParsedArgs() init_cmd = init.TempestInit(None, None) self.assertRaises(OSError, init_cmd.take_action, pa) # one more trying should be a same error not "workspace already exists" self.assertRaises(OSError, init_cmd.take_action, pa) tempest-17.2.0/tempest/tests/cmd/test_list_plugins.py0000666000175100017510000000156613207044712023033 0ustar zuulzuul00000000000000# 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 subprocess from tempest.tests import base class TestTempestListPlugins(base.TestCase): def test_run_list_plugins(self): return_code = subprocess.call( ['tempest', 'list-plugins'], stdout=subprocess.PIPE) self.assertEqual(return_code, 0) tempest-17.2.0/tempest/tests/cmd/sample_streams/0000775000175100017510000000000013207045130021706 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/cmd/sample_streams/calls.subunit0000666000175100017510000001614313207044712024433 0ustar zuulzuul00000000000000³+W'êfoo—µÚ ³+cM§W'êfoo text/plain pythonloggingMz2016-02-02 03:27:01,251 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:setUp): 200 POST http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents 0.257s 2016-02-02 03:27:01,251 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: {"agent": {"url": "xxx://xxxx/xxx/xxx", "hypervisor": "common", "md5hash": "add6bb58e139be103324d04d82d8f545", "version": "7.0", "architecture": "tempest-x86_64-948635295", "os": "linux"}} Response - Headers: {'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'} Body: {"agent": {"url": "xxx://xxxx/xxx/xxx", "hypervisor": "common", "md5hash": "add6bb58e139be103324d04d82d8f545", "version": "7.0", "architecture": "tempest-x86_64-948635295", "os": "linux", "agent_id": 3}} 2016-02-02 03:27:01,479 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:test_delete_agent): 200 DELETE http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents/3 0.224s 2016-02-02 03:27:01,479 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: None Response - Headers: {'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'} Body: 2016-02-02 03:27:01,753 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:test_delete_agent): 200 GET http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents 0.273s 2016-02-02 03:27:01,753 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: None Response - Headers: {'status': '200', 'content-length': '14', 'content-location': 'http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents', 'x-compute-request-id': 'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'} Body: {"agents": []} 2016-02-02 03:27:02,048 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:tearDown): 404 DELETE http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents/3 0.292s 2016-02-02 03:27:02,049 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: None Response - Headers: {'status': '404', 'content-length': '82', 'x-compute-request-id': 'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': 'application/json; charset=UTF-8'} À³+W'êbarm93«³+cNšW'êbar text/plain pythonloggingNm2016-02-02 03:27:00,350 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:setUp): 200 POST http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents 0.220s 2016-02-02 03:27:00,350 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: {"agent": {"url": "xxx://xxxx/xxx/xxx", "hypervisor": "common", "md5hash": "add6bb58e139be103324d04d82d8f545", "version": "7.0", "architecture": "tempest-x86_64-424013832", "os": "linux"}} Response - Headers: {'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'} Body: {"agent": {"url": "xxx://xxxx/xxx/xxx", "hypervisor": "common", "md5hash": "add6bb58e139be103324d04d82d8f545", "version": "7.0", "architecture": "tempest-x86_64-424013832", "os": "linux", "agent_id": 1}} 2016-02-02 03:27:00,561 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:test_create_agent): 200 POST http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents 0.208s 2016-02-02 03:27:00,561 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: {"agent": {"url": "xxx://xxxx/xxx/xxx", "hypervisor": "kvm", "md5hash": "add6bb58e139be103324d04d82d8f545", "version": "7.0", "architecture": "tempest-x86-252246646", "os": "win"}} Response - Headers: {'status': '200', 'content-length': '195', 'x-compute-request-id': 'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'} Body: {"agent": {"url": "xxx://xxxx/xxx/xxx", "hypervisor": "kvm", "md5hash": "add6bb58e139be103324d04d82d8f545", "version": "7.0", "architecture": "tempest-x86-252246646", "os": "win", "agent_id": 2}} 2016-02-02 03:27:00,775 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:tearDown): 200 DELETE http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents/1 0.211s 2016-02-02 03:27:00,775 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: None Response - Headers: {'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'} Body: 2016-02-02 03:27:00,989 3922 INFO [tempest_lib.common.rest_client] Request (AgentsAdminTestJSON:_run_cleanups): 200 DELETE http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents/2 0.212s 2016-02-02 03:27:00,989 3922 DEBUG [tempest_lib.common.rest_client] Request - Headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''} Body: None Response - Headers: {'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}à„zàtempest-17.2.0/tempest/tests/cmd/__init__.py0000666000175100017510000000000013207044712020775 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/cmd/test_subunit_describe_calls.py0000666000175100017510000002466513207044712025033 0ustar zuulzuul00000000000000# Copyright 2016 Rackspace # # 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 os import subprocess import tempfile from tempest.cmd import subunit_describe_calls from tempest.tests import base class TestSubunitDescribeCalls(base.TestCase): def test_return_code(self): subunit_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'sample_streams/calls.subunit') p = subprocess.Popen([ 'subunit-describe-calls', '-s', subunit_file, '-o', tempfile.mkstemp()[1]], stdin=subprocess.PIPE) p.communicate() self.assertEqual(0, p.returncode) def test_return_code_no_output(self): subunit_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'sample_streams/calls.subunit') p = subprocess.Popen([ 'subunit-describe-calls', '-s', subunit_file], stdin=subprocess.PIPE) p.communicate() self.assertEqual(0, p.returncode) def test_parse(self): subunit_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'sample_streams/calls.subunit') parser = subunit_describe_calls.parse( open(subunit_file), "pythonlogging", None) expected_result = { 'bar': [{ 'name': 'AgentsAdminTestJSON:setUp', 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' '"hypervisor": "common", "md5hash": ' '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' '"architecture": "tempest-x86_64-424013832", "os": "linux"}}', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' '"hypervisor": "common", "md5hash": ' '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' '"architecture": "tempest-x86_64-424013832", "os": "linux", ' '"agent_id": 1}}', 'response_headers': "{'status': '200', 'content-length': " "'203', 'x-compute-request-id': " "'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents', 'verb': 'POST'}, { 'name': 'AgentsAdminTestJSON:test_create_agent', 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' '"hypervisor": "kvm", "md5hash": ' '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' '"architecture": "tempest-x86-252246646", "os": "win"}}', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' '"hypervisor": "kvm", "md5hash": ' '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' '"architecture": "tempest-x86-252246646", "os": "win", ' '"agent_id": 2}}', 'response_headers': "{'status': '200', 'content-length': " "'195', 'x-compute-request-id': " "'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents', 'verb': 'POST'}, { 'name': 'AgentsAdminTestJSON:tearDown', 'request_body': 'None', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_body': '', 'response_headers': "{'status': '200', 'content-length': " "'0', 'x-compute-request-id': " "'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents/1', 'verb': 'DELETE'}, { 'name': 'AgentsAdminTestJSON:_run_cleanups', 'request_body': 'None', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_headers': "{'status': '200', 'content-length': " "'0', 'x-compute-request-id': " "'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents/2', 'verb': 'DELETE'}], 'foo': [{ 'name': 'AgentsAdminTestJSON:setUp', 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' '"hypervisor": "common", "md5hash": ' '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' '"architecture": "tempest-x86_64-948635295", "os": "linux"}}', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' '"hypervisor": "common", "md5hash": ' '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' '"architecture": "tempest-x86_64-948635295", "os": "linux", ' '"agent_id": 3}}', 'response_headers': "{'status': '200', 'content-length': " "'203', 'x-compute-request-id': " "'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents', 'verb': 'POST'}, { 'name': 'AgentsAdminTestJSON:test_delete_agent', 'request_body': 'None', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_body': '', 'response_headers': "{'status': '200', 'content-length': " "'0', 'x-compute-request-id': " "'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents/3', 'verb': 'DELETE'}, { 'name': 'AgentsAdminTestJSON:test_delete_agent', 'request_body': 'None', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_body': '{"agents": []}', 'response_headers': "{'status': '200', 'content-length': " "'14', 'content-location': " "'http://23.253.76.97:8774/v2.1/" "cf6b1933fe5b476fbbabb876f6d1b924/os-agents', " "'x-compute-request-id': " "'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': " "'application/json'}", 'service': 'Nova', 'status_code': '200', 'url': 'v2.1//os-agents', 'verb': 'GET'}, { 'name': 'AgentsAdminTestJSON:tearDown', 'request_body': 'None', 'request_headers': "{'Content-Type': 'application/json', " "'Accept': 'application/json', 'X-Auth-Token': ''}", 'response_headers': "{'status': '404', 'content-length': " "'82', 'x-compute-request-id': " "'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': " "'X-OpenStack-Nova-API-Version', 'connection': 'close', " "'x-openstack-nova-api-version': '2.1', 'date': " "'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': " "'application/json; charset=UTF-8'}", 'service': 'Nova', 'status_code': '404', 'url': 'v2.1//os-agents/3', 'verb': 'DELETE'}]} self.assertEqual(expected_result, parser.test_logs) tempest-17.2.0/tempest/tests/cmd/test_account_generator.py0000666000175100017510000003514713207044712024023 0ustar zuulzuul00000000000000# 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 fixtures import mock from oslo_config import cfg from tempest.cmd import account_generator from tempest import config from tempest.tests import base from tempest.tests import fake_config class FakeOpts(object): def __init__(self, version=3): self.os_username = 'fake_user' self.os_password = 'fake_password' self.os_project_name = 'fake_project_name' self.os_tenant_name = None self.os_domain_name = 'fake_domain' self.tag = 'fake' self.concurrency = 2 self.with_admin = True self.identity_version = version self.accounts = 'fake_accounts.yml' class MockHelpersMixin(object): def mock_config_and_opts(self, identity_version): self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.opts = FakeOpts(version=identity_version) self.patch('oslo_log.log.setup', autospec=True) def mock_resource_creation(self): fake_resource = dict(id='id', name='name') self.user_create_fixture = self.useFixture(fixtures.MockPatch( self.cred_client + '.create_user', return_value=fake_resource)) self.useFixture(fixtures.MockPatch( self.cred_client + '.create_project', return_value=fake_resource)) self.useFixture(fixtures.MockPatch( self.cred_client + '.assign_user_role')) self.useFixture(fixtures.MockPatch( self.cred_client + '._check_role_exists', return_value=fake_resource)) self.useFixture(fixtures.MockPatch( self.dynamic_creds + '._create_network', return_value=fake_resource)) self.useFixture(fixtures.MockPatch( self.dynamic_creds + '._create_subnet', return_value=fake_resource)) self.useFixture(fixtures.MockPatch( self.dynamic_creds + '._create_router', return_value=fake_resource)) self.useFixture(fixtures.MockPatch( self.dynamic_creds + '._add_router_interface', return_value=fake_resource)) def mock_domains(self): fake_domain_list = {'domains': [{'id': 'fake_domain', 'name': 'Fake_Domain'}]} self.useFixture(fixtures.MockPatch(''.join([ 'tempest.lib.services.identity.v3.domains_client.' 'DomainsClient.list_domains']), return_value=fake_domain_list)) self.useFixture(fixtures.MockPatch( self.cred_client + '.assign_user_role_on_domain')) class TestAccountGeneratorV2(base.TestCase, MockHelpersMixin): identity_version = 2 def setUp(self): super(TestAccountGeneratorV2, self).setUp() self.mock_config_and_opts(self.identity_version) def test_get_credential_provider(self): cp = account_generator.get_credential_provider(self.opts) admin_creds = cp.default_admin_creds self.assertEqual(self.opts.tag, cp.name) self.assertIn(str(self.opts.identity_version), cp.identity_version) self.assertEqual(self.opts.os_username, admin_creds.username) self.assertEqual(self.opts.os_project_name, admin_creds.tenant_name) self.assertEqual(self.opts.os_password, admin_creds.password) self.assertFalse(hasattr(admin_creds, 'domain_name')) def test_get_credential_provider_with_tenant(self): self.opts.os_project_name = None self.opts.os_tenant_name = 'fake_tenant' cp = account_generator.get_credential_provider(self.opts) admin_creds = cp.default_admin_creds self.assertEqual(self.opts.os_tenant_name, admin_creds.tenant_name) class TestAccountGeneratorV3(TestAccountGeneratorV2): identity_version = 3 def setUp(self): super(TestAccountGeneratorV3, self).setUp() fake_domain_list = {'domains': [{'id': 'fake_domain'}]} self.useFixture(fixtures.MockPatch(''.join([ 'tempest.lib.services.identity.v3.domains_client.' 'DomainsClient.list_domains']), return_value=fake_domain_list)) def test_get_credential_provider(self): cp = account_generator.get_credential_provider(self.opts) admin_creds = cp.default_admin_creds self.assertEqual(self.opts.tag, cp.name) self.assertIn(str(self.opts.identity_version), cp.identity_version) self.assertEqual(self.opts.os_username, admin_creds.username) self.assertEqual(self.opts.os_project_name, admin_creds.tenant_name) self.assertEqual(self.opts.os_password, admin_creds.password) self.assertEqual(self.opts.os_domain_name, admin_creds.domain_name) def test_get_credential_provider_without_domain(self): self.opts.os_domain_name = None cp = account_generator.get_credential_provider(self.opts) admin_creds = cp.default_admin_creds self.assertIsNotNone(admin_creds.domain_name) class TestGenerateResourcesV2(base.TestCase, MockHelpersMixin): identity_version = 2 cred_client = 'tempest.lib.common.cred_client.V2CredsClient' dynamic_creds = ('tempest.lib.common.dynamic_creds.' 'DynamicCredentialProvider') def setUp(self): super(TestGenerateResourcesV2, self).setUp() self.mock_config_and_opts(self.identity_version) self.cred_provider = account_generator.get_credential_provider( self.opts) self.mock_resource_creation() def test_generate_resources_no_admin(self): cfg.CONF.set_default('swift', False, group='service_available') cfg.CONF.set_default('heat', False, group='service_available') cfg.CONF.set_default('operator_role', 'fake_operator', group='object-storage') cfg.CONF.set_default('reseller_admin_role', 'fake_reseller', group='object-storage') cfg.CONF.set_default('stack_owner_role', 'fake_owner', group='orchestration') resources = account_generator.generate_resources( self.cred_provider, admin=False) resource_types = [k for k, _ in resources] # No admin, no heat, no swift, expect two credentials only self.assertEqual(2, len(resources)) # Ensure create_user was invoked twice (two distinct users) self.assertEqual(2, self.user_create_fixture.mock.call_count) self.assertIn('primary', resource_types) self.assertIn('alt', resource_types) self.assertNotIn('admin', resource_types) self.assertNotIn(['fake_operator'], resource_types) self.assertNotIn(['fake_reseller'], resource_types) self.assertNotIn(['fake_owner'], resource_types) for resource in resources: self.assertIsNotNone(resource[1].network) self.assertIsNotNone(resource[1].router) self.assertIsNotNone(resource[1].subnet) def test_generate_resources_admin(self): cfg.CONF.set_default('swift', False, group='service_available') cfg.CONF.set_default('heat', False, group='service_available') cfg.CONF.set_default('operator_role', 'fake_operator', group='object-storage') cfg.CONF.set_default('reseller_admin_role', 'fake_reseller', group='object-storage') cfg.CONF.set_default('stack_owner_role', 'fake_owner', group='orchestration') resources = account_generator.generate_resources( self.cred_provider, admin=True) resource_types = [k for k, _ in resources] # Admin, no heat, no swift, expect three credentials only self.assertEqual(3, len(resources)) # Ensure create_user was invoked 3 times (3 distinct users) self.assertEqual(3, self.user_create_fixture.mock.call_count) self.assertIn('primary', resource_types) self.assertIn('alt', resource_types) self.assertIn('admin', resource_types) self.assertNotIn(['fake_operator'], resource_types) self.assertNotIn(['fake_reseller'], resource_types) self.assertNotIn(['fake_owner'], resource_types) for resource in resources: self.assertIsNotNone(resource[1].network) self.assertIsNotNone(resource[1].router) self.assertIsNotNone(resource[1].subnet) def test_generate_resources_swift_heat_admin(self): cfg.CONF.set_default('swift', True, group='service_available') cfg.CONF.set_default('heat', True, group='service_available') cfg.CONF.set_default('operator_role', 'fake_operator', group='object-storage') cfg.CONF.set_default('reseller_admin_role', 'fake_reseller', group='object-storage') cfg.CONF.set_default('stack_owner_role', 'fake_owner', group='orchestration') resources = account_generator.generate_resources( self.cred_provider, admin=True) resource_types = [k for k, _ in resources] # all options on, expect six credentials self.assertEqual(6, len(resources)) # Ensure create_user was invoked 6 times (6 distinct users) self.assertEqual(6, self.user_create_fixture.mock.call_count) self.assertIn('primary', resource_types) self.assertIn('alt', resource_types) self.assertIn('admin', resource_types) self.assertIn(['fake_operator'], resource_types) self.assertIn(['fake_reseller'], resource_types) self.assertIn(['fake_owner', 'fake_operator'], resource_types) for resource in resources: self.assertIsNotNone(resource[1].network) self.assertIsNotNone(resource[1].router) self.assertIsNotNone(resource[1].subnet) class TestGenerateResourcesV3(TestGenerateResourcesV2): identity_version = 3 cred_client = 'tempest.lib.common.cred_client.V3CredsClient' def setUp(self): self.mock_domains() super(TestGenerateResourcesV3, self).setUp() class TestDumpAccountsV2(base.TestCase, MockHelpersMixin): identity_version = 2 cred_client = 'tempest.lib.common.cred_client.V2CredsClient' dynamic_creds = ('tempest.lib.common.dynamic_creds.' 'DynamicCredentialProvider') domain_is_in = False def setUp(self): super(TestDumpAccountsV2, self).setUp() self.mock_config_and_opts(self.identity_version) self.cred_provider = account_generator.get_credential_provider( self.opts) self.mock_resource_creation() cfg.CONF.set_default('swift', True, group='service_available') cfg.CONF.set_default('heat', True, group='service_available') self.resources = account_generator.generate_resources( self.cred_provider, admin=True) def test_dump_accounts(self): self.useFixture(fixtures.MockPatch('os.path.exists', return_value=False)) mocked_open = mock.mock_open() with mock.patch('{}.open'.format(account_generator.__name__), mocked_open, create=True): with mock.patch('yaml.safe_dump') as yaml_dump_mock: account_generator.setup_logging() account_generator.dump_accounts(self.resources, self.opts.identity_version, self.opts.accounts) mocked_open.assert_called_once_with(self.opts.accounts, 'w') handle = mocked_open() # Ordered args in [0], keyword args in [1] accounts, f = yaml_dump_mock.call_args[0] self.assertEqual(handle, f) self.assertEqual(6, len(accounts)) if self.domain_is_in: self.assertIn('domain_name', accounts[0].keys()) else: self.assertNotIn('domain_name', accounts[0].keys()) self.assertEqual(1, len([x for x in accounts if x.get('types') == ['admin']])) self.assertEqual(3, len([x for x in accounts if 'roles' in x])) for account in accounts: self.assertIn('resources', account) self.assertIn('network', account.get('resources')) def test_dump_accounts_existing_file(self): self.useFixture(fixtures.MockPatch('os.path.exists', return_value=True)) rename_mock = self.useFixture(fixtures.MockPatch('os.rename')).mock backup_file = '.'.join((self.opts.accounts, 'bak')) mocked_open = mock.mock_open() with mock.patch('{}.open'.format(account_generator.__name__), mocked_open, create=True): with mock.patch('yaml.safe_dump') as yaml_dump_mock: account_generator.setup_logging() account_generator.dump_accounts(self.resources, self.opts.identity_version, self.opts.accounts) rename_mock.assert_called_once_with(self.opts.accounts, backup_file) mocked_open.assert_called_once_with(self.opts.accounts, 'w') handle = mocked_open() # Ordered args in [0], keyword args in [1] accounts, f = yaml_dump_mock.call_args[0] self.assertEqual(handle, f) self.assertEqual(6, len(accounts)) if self.domain_is_in: self.assertIn('domain_name', accounts[0].keys()) else: self.assertNotIn('domain_name', accounts[0].keys()) self.assertEqual(1, len([x for x in accounts if x.get('types') == ['admin']])) self.assertEqual(3, len([x for x in accounts if 'roles' in x])) for account in accounts: self.assertIn('resources', account) self.assertIn('network', account.get('resources')) class TestDumpAccountsV3(TestDumpAccountsV2): identity_version = 3 cred_client = 'tempest.lib.common.cred_client.V3CredsClient' domain_is_in = True def setUp(self): self.mock_domains() super(TestDumpAccountsV3, self).setUp() tempest-17.2.0/tempest/tests/test_tempest_plugin.py0000666000175100017510000000753613207044712022616 0ustar zuulzuul00000000000000# Copyright (c) 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. from tempest.lib.services import clients from tempest.test_discover import plugins from tempest.tests import base from tempest.tests import fake_tempest_plugin as fake_plugin from tempest.tests.lib.services import registry_fixture class TestPluginDiscovery(base.TestCase): def setUp(self): super(TestPluginDiscovery, self).setUp() # Make sure we leave the registry clean self.useFixture(registry_fixture.RegistryFixture()) def test_load_tests_with_one_plugin(self): # we can't mock stevedore since it's a singleton and already executed # during test discovery. So basically this test covers the plugin loop # and the abstract plugin interface. manager = plugins.TempestTestPluginManager() fake_obj = fake_plugin.FakeStevedoreObj() manager.ext_plugins = [fake_obj] result = manager.get_plugin_load_tests_tuple() self.assertEqual(fake_plugin.FakePlugin.expected_load_test, result[fake_obj.name]) def test_load_tests_with_two_plugins(self): manager = plugins.TempestTestPluginManager() obj1 = fake_plugin.FakeStevedoreObj('fake01') obj2 = fake_plugin.FakeStevedoreObj('fake02') manager.ext_plugins = [obj1, obj2] result = manager.get_plugin_load_tests_tuple() self.assertEqual(fake_plugin.FakePlugin.expected_load_test, result['fake01']) self.assertEqual(fake_plugin.FakePlugin.expected_load_test, result['fake02']) def test__register_service_clients_with_one_plugin(self): registry = clients.ClientsRegistry() manager = plugins.TempestTestPluginManager() fake_obj = fake_plugin.FakeStevedoreObj() manager.ext_plugins = [fake_obj] manager._register_service_clients() expected_result = fake_plugin.FakePlugin.expected_service_clients registered_clients = registry.get_service_clients() self.assertIn(fake_obj.name, registered_clients) self.assertEqual(expected_result, registered_clients[fake_obj.name]) def test__get_service_clients_with_two_plugins(self): registry = clients.ClientsRegistry() manager = plugins.TempestTestPluginManager() obj1 = fake_plugin.FakeStevedoreObj('fake01') obj2 = fake_plugin.FakeStevedoreObj('fake02') manager.ext_plugins = [obj1, obj2] manager._register_service_clients() expected_result = fake_plugin.FakePlugin.expected_service_clients registered_clients = registry.get_service_clients() self.assertIn('fake01', registered_clients) self.assertIn('fake02', registered_clients) self.assertEqual(expected_result, registered_clients['fake01']) self.assertEqual(expected_result, registered_clients['fake02']) def test__register_service_clients_one_plugin_no_service_clients(self): registry = clients.ClientsRegistry() manager = plugins.TempestTestPluginManager() fake_obj = fake_plugin.FakeStevedoreObjNoServiceClients() manager.ext_plugins = [fake_obj] manager._register_service_clients() registered_clients = registry.get_service_clients() self.assertNotIn(fake_obj.name, registered_clients) tempest-17.2.0/tempest/tests/test_hacking.py0000666000175100017510000002433713207044712021161 0ustar zuulzuul00000000000000# Copyright 2014 Matthew Treinish # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.hacking import checks from tempest.tests import base class HackingTestCase(base.TestCase): """Test class for hacking rule This class tests the hacking checks in tempest.hacking.checks by passing strings to the check methods like the pep8/flake8 parser would. The parser loops over each line in the file and then passes the parameters to the check method. The parameter names in the check method dictate what type of object is passed to the check method. The parameter types are:: logical_line: A processed line with the following modifications: - Multi-line statements converted to a single line. - Stripped left and right. - Contents of strings replaced with "xxx" of same length. - Comments removed. physical_line: Raw line of text from the input file. lines: a list of the raw lines from the input file tokens: the tokens that contribute to this logical line line_number: line number in the input file total_lines: number of lines in the input file blank_lines: blank lines before this one indent_char: indentation character in this file (" " or "\t") indent_level: indentation (with tabs expanded to multiples of 8) previous_indent_level: indentation on previous line previous_logical: previous logical line filename: Path of the file being run through pep8 When running a test on a check method the return will be False/None if there is no violation in the sample input. If there is an error a tuple is returned with a position in the line, and a message. So to check the result just assertTrue if the check is expected to fail and assertFalse if it should pass. """ def test_no_setup_teardown_class_for_tests(self): self.assertTrue(checks.no_setup_teardown_class_for_tests( " def setUpClass(cls):", './tempest/tests/fake_test.py')) self.assertIsNone(checks.no_setup_teardown_class_for_tests( " def setUpClass(cls): # noqa", './tempest/tests/fake_test.py')) self.assertTrue(checks.no_setup_teardown_class_for_tests( " def setUpClass(cls):", './tempest/api/fake_test.py')) self.assertTrue(checks.no_setup_teardown_class_for_tests( " def setUpClass(cls):", './tempest/scenario/fake_test.py')) self.assertFalse(checks.no_setup_teardown_class_for_tests( " def setUpClass(cls):", './tempest/test.py')) self.assertTrue(checks.no_setup_teardown_class_for_tests( " def tearDownClass(cls):", './tempest/tests/fake_test.py')) self.assertIsNone(checks.no_setup_teardown_class_for_tests( " def tearDownClass(cls): # noqa", './tempest/tests/fake_test.py')) self.assertTrue(checks.no_setup_teardown_class_for_tests( " def tearDownClass(cls):", './tempest/api/fake_test.py')) self.assertTrue(checks.no_setup_teardown_class_for_tests( " def tearDownClass(cls):", './tempest/scenario/fake_test.py')) self.assertFalse(checks.no_setup_teardown_class_for_tests( " def tearDownClass(cls):", './tempest/test.py')) def test_import_no_clients_in_api_and_scenario_tests(self): for client in checks.PYTHON_CLIENTS: string = "import " + client + "client" self.assertTrue( checks.import_no_clients_in_api_and_scenario_tests( string, './tempest/api/fake_test.py')) self.assertTrue( checks.import_no_clients_in_api_and_scenario_tests( string, './tempest/scenario/fake_test.py')) self.assertFalse( checks.import_no_clients_in_api_and_scenario_tests( string, './tempest/test.py')) def test_scenario_tests_need_service_tags(self): self.assertFalse(checks.scenario_tests_need_service_tags( 'def test_fake:', './tempest/scenario/test_fake.py', "@utils.services('compute')")) self.assertFalse(checks.scenario_tests_need_service_tags( 'def test_fake_test:', './tempest/api/compute/test_fake.py', "@utils.services('image')")) self.assertFalse(checks.scenario_tests_need_service_tags( 'def test_fake:', './tempest/scenario/orchestration/test_fake.py', "@utils.services('compute')")) self.assertTrue(checks.scenario_tests_need_service_tags( 'def test_fake_test:', './tempest/scenario/test_fake.py', '\n')) self.assertTrue(checks.scenario_tests_need_service_tags( 'def test_fake:', './tempest/scenario/orchestration/test_fake.py', "\n")) def test_no_vi_headers(self): # NOTE(mtreinish) The lines parameter is used only for finding the # line location in the file. So these tests just pass a list of an # arbitrary length to use for verifying the check function. self.assertTrue(checks.no_vi_headers( '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 1, range(250))) self.assertTrue(checks.no_vi_headers( '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 249, range(250))) self.assertFalse(checks.no_vi_headers( '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 149, range(250))) def test_service_tags_not_in_module_path(self): self.assertTrue(checks.service_tags_not_in_module_path( "@utils.services('compute')", './tempest/api/compute/fake_test.py')) self.assertFalse(checks.service_tags_not_in_module_path( "@utils.services('compute')", './tempest/scenario/compute/fake_test.py')) self.assertFalse(checks.service_tags_not_in_module_path( "@utils.services('compute')", './tempest/api/image/fake_test.py')) def test_no_hyphen_at_end_of_rand_name(self): self.assertIsNone(checks.no_hyphen_at_end_of_rand_name( 'data_utils.rand_name("fake-resource")', './tempest/test_foo.py')) self.assertEqual(2, len(list(checks.no_hyphen_at_end_of_rand_name( 'data_utils.rand_name("fake-resource-")', './tempest/test_foo.py') ))) def test_no_mutable_default_args(self): self.assertEqual(1, len(list(checks.no_mutable_default_args( " def function1(para={}):")))) self.assertEqual(1, len(list(checks.no_mutable_default_args( "def function2(para1, para2, para3=[])")))) self.assertEqual(0, len(list(checks.no_mutable_default_args( "defined = []")))) self.assertEqual(0, len(list(checks.no_mutable_default_args( "defined, undefined = [], {}")))) def test_no_testtools_skip_decorator(self): self.assertEqual(1, len(list(checks.no_testtools_skip_decorator( " @testtools.skip('Bug xxx')")))) self.assertEqual(0, len(list(checks.no_testtools_skip_decorator( " @testtools.skipUnless(CONF.something, 'msg')")))) self.assertEqual(0, len(list(checks.no_testtools_skip_decorator( " @testtools.skipIf(CONF.something, 'msg')")))) def test_dont_import_local_tempest_code_into_lib(self): self.assertEqual(0, len(list(checks.dont_import_local_tempest_into_lib( "from tempest.common import waiters", './tempest/common/compute.py')))) self.assertEqual(0, len(list(checks.dont_import_local_tempest_into_lib( "from tempest import config", './tempest/common/compute.py')))) self.assertEqual(0, len(list(checks.dont_import_local_tempest_into_lib( "import tempest.exception", './tempest/common/compute.py')))) self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib( "from tempest.common import waiters", './tempest/lib/common/compute.py')))) self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib( "from tempest import config", './tempest/lib/common/compute.py')))) self.assertEqual(1, len(list(checks.dont_import_local_tempest_into_lib( "import tempest.exception", './tempest/lib/common/compute.py')))) def test_dont_use_config_in_tempest_lib(self): self.assertFalse(list(checks.dont_use_config_in_tempest_lib( 'from tempest import config', './tempest/common/compute.py'))) self.assertFalse(list(checks.dont_use_config_in_tempest_lib( 'from oslo_concurrency import lockutils', './tempest/lib/auth.py'))) self.assertTrue(list(checks.dont_use_config_in_tempest_lib( 'from tempest import config', './tempest/lib/auth.py'))) self.assertTrue(list(checks.dont_use_config_in_tempest_lib( 'from oslo_config import cfg', './tempest/lib/decorators.py'))) self.assertTrue(list(checks.dont_use_config_in_tempest_lib( 'import tempest.config', './tempest/lib/common/rest_client.py'))) def test_unsupported_exception_attribute_PY3(self): self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3( "raise TestCase.failureException(e.message)"))), 1) self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3( "raise TestCase.failureException(ex.message)"))), 1) self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3( "raise TestCase.failureException(exc.message)"))), 1) self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3( "raise TestCase.failureException(exception.message)"))), 1) self.assertEqual(len(list(checks.unsupported_exception_attribute_PY3( "raise TestCase.failureException(ee.message)"))), 0) tempest-17.2.0/tempest/tests/lib/0000775000175100017510000000000013207045130016672 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/test_decorators.py0000666000175100017510000001444413207044712022466 0ustar zuulzuul00000000000000# 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 mock import testtools from tempest.lib import base as test from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.tests import base class TestAttrDecorator(base.TestCase): def _test_attr_helper(self, expected_attrs, **decorator_args): @decorators.attr(**decorator_args) def foo(): pass # By our decorators.attr decorator the attribute __testtools_attrs # will be set only for 'type' argument, so we test it first. if 'type' in decorator_args: # this is what testtools sets self.assertEqual(getattr(foo, '__testtools_attrs'), set(expected_attrs)) def test_attr_without_type(self): self._test_attr_helper(expected_attrs='baz', bar='baz') def test_attr_decorator_with_list_type(self): # if type is 'smoke' we'll get the original list of types self._test_attr_helper(expected_attrs=['smoke', 'foo'], type=['smoke', 'foo']) def test_attr_decorator_with_unknown_type(self): self._test_attr_helper(expected_attrs=['foo'], type='foo') def test_attr_decorator_with_duplicated_type(self): self._test_attr_helper(expected_attrs=['foo'], type=['foo', 'foo']) 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 = data_utils.rand_uuid() 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 = data_utils.rand_uuid() 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) class TestRelatedBugDecorator(base.TestCase): def test_relatedbug_when_no_exception(self): f = mock.Mock() sentinel = object() @decorators.related_bug(bug="1234", status_code=500) def test_foo(self): f(self) test_foo(sentinel) f.assert_called_once_with(sentinel) def test_relatedbug_when_exception(self): class MyException(Exception): def __init__(self, status_code): self.status_code = status_code def f(self): raise MyException(status_code=500) @decorators.related_bug(bug="1234", status_code=500) def test_foo(self): f(self) with mock.patch.object(decorators.LOG, 'error') as m_error: self.assertRaises(MyException, test_foo, object()) m_error.assert_called_once_with(mock.ANY, '1234', '1234') tempest-17.2.0/tempest/tests/lib/cli/0000775000175100017510000000000013207045130017441 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/cli/__init__.py0000666000175100017510000000000013207044712021547 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/cli/test_command_failed.py0000666000175100017510000000215113207044712024002 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 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)) tempest-17.2.0/tempest/tests/lib/cli/test_execute.py0000666000175100017510000001231413207044712022524 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 subprocess import mock from tempest.lib.cli import base as cli_base from tempest.lib import exceptions from tempest.tests import base class TestExecute(base.TestCase): @mock.patch('subprocess.Popen', autospec=True) def test_execute_success(self, mock_popen): mock_popen.return_value.returncode = 0 mock_popen.return_value.communicate.return_value = ( "__init__.py", "") result = cli_base.execute("/bin/ls", action="tempest", flags="-l -a") args, kwargs = mock_popen.call_args # Check merge_stderr == False self.assertEqual(subprocess.PIPE, kwargs['stderr']) # Check action and flags are passed args = args[0] # We just tests that all pieces are passed through, we cannot make # assumptions about the order self.assertIn("/bin/ls", args) self.assertIn("-l", args) self.assertIn("-a", args) self.assertIn("tempest", args) # The result is mocked - checking that the mock was invoked correctly self.assertIsInstance(result, str) self.assertIn("__init__.py", result) @mock.patch('subprocess.Popen', autospec=True) def test_execute_failure(self, mock_popen): mock_popen.return_value.returncode = 1 mock_popen.return_value.communicate.return_value = ( "No such option --foobar", "") result = cli_base.execute("/bin/ls", action="tempest.lib", flags="--foobar", merge_stderr=True, fail_ok=True) args, kwargs = mock_popen.call_args # Check the merge_stderr self.assertEqual(subprocess.STDOUT, kwargs['stderr']) # Check action and flags are passed args = args[0] # We just tests that all pieces are passed through, we cannot make # assumptions about the order self.assertIn("/bin/ls", args) self.assertIn("--foobar", args) self.assertIn("tempest.lib", args) # The result is mocked - checking that the mock was invoked correctly self.assertIsInstance(result, str) self.assertIn("--foobar", result) @mock.patch('subprocess.Popen', autospec=True) def test_execute_failure_raise_exception(self, mock_popen): mock_popen.return_value.returncode = 1 mock_popen.return_value.communicate.return_value = ( "No such option --foobar", "") self.assertRaises(exceptions.CommandFailed, cli_base.execute, "/bin/ls", action="tempest", flags="--foobar", merge_stderr=True) def test_execute_with_prefix(self): result = cli_base.execute("env", action="", prefix="env NEW_VAR=1") self.assertIsInstance(result, str) self.assertIn("NEW_VAR=1", result) class TestCLIClient(base.TestCase): @mock.patch.object(cli_base, 'execute') def test_execute_with_prefix(self, mock_execute): cli = cli_base.CLIClient(prefix='env LAC_ALL=C') cli.glance('action') self.assertEqual(mock_execute.call_count, 1) self.assertEqual(mock_execute.call_args[1], {'prefix': 'env LAC_ALL=C'}) @mock.patch.object(cli_base, 'execute') def test_execute_with_domain_name(self, mock_execute): cli = cli_base.CLIClient( user_domain_name='default', project_domain_name='default' ) cli.glance('action') self.assertEqual(mock_execute.call_count, 1) self.assertIn('--os-user-domain-name default', mock_execute.call_args[0][2]) self.assertIn('--os-project-domain-name default', mock_execute.call_args[0][2]) self.assertNotIn('--os-user-domain-id', mock_execute.call_args[0][2]) self.assertNotIn('--os-project-domain-id', mock_execute.call_args[0][2]) @mock.patch.object(cli_base, 'execute') def test_execute_with_domain_id(self, mock_execute): cli = cli_base.CLIClient( user_domain_id='default', project_domain_id='default' ) cli.glance('action') self.assertEqual(mock_execute.call_count, 1) self.assertIn('--os-user-domain-id default', mock_execute.call_args[0][2]) self.assertIn('--os-project-domain-id default', mock_execute.call_args[0][2]) self.assertNotIn('--os-user-domain-name', mock_execute.call_args[0][2]) self.assertNotIn('--os-project-domain-name', mock_execute.call_args[0][2]) tempest-17.2.0/tempest/tests/lib/cli/test_output_parser.py0000666000175100017510000001511413207044712023777 0ustar zuulzuul00000000000000# 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 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) tempest-17.2.0/tempest/tests/lib/test_tempest_lib.py0000666000175100017510000000136313207044712022624 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import base class TestTempest_lib(base.TestCase): def test_something(self): pass tempest-17.2.0/tempest/tests/lib/test_ssh.py0000666000175100017510000002724013207044712021114 0ustar zuulzuul00000000000000# 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. import socket import mock import six from six import StringIO import testtools from tempest.lib.common import ssh from tempest.lib import exceptions from tempest.tests 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', port=22, username='root', pkey=None, key_filename=None, look_for_keys=False, timeout=10.0, password=None, sock=None )] self.assertEqual(expected_connect, client_mock.connect.mock_calls) self.assertEqual(0, s_mock.call_count) def test_get_ssh_connection_over_ssh(self): c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks() proxy_client_mock = mock.MagicMock() proxy_client_mock.connect.return_value = True s_mock = self.patch('time.sleep') c_mock.side_effect = [client_mock, proxy_client_mock] aa_mock.return_value = mock.sentinel.aa proxy_client = ssh.Client('proxy-host', 'proxy-user', timeout=2) client = ssh.Client('localhost', 'root', timeout=2, proxy_client=proxy_client) client._get_ssh_connection(sleep=1) aa_mock.assert_has_calls([mock.call(), mock.call()]) proxy_client_mock.set_missing_host_key_policy.assert_called_once_with( mock.sentinel.aa) proxy_expected_connect = [mock.call( 'proxy-host', port=22, username='proxy-user', pkey=None, key_filename=None, look_for_keys=False, timeout=10.0, password=None, sock=None )] self.assertEqual(proxy_expected_connect, proxy_client_mock.connect.mock_calls) client_mock.set_missing_host_key_policy.assert_called_once_with( mock.sentinel.aa) expected_connect = [mock.call( 'localhost', port=22, username='root', pkey=None, key_filename=None, look_for_keys=False, timeout=10.0, password=None, sock=proxy_client_mock.get_transport().open_session() )] 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)) 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() 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().__enter__.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().__enter__.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) tempest-17.2.0/tempest/tests/lib/fake_http.py0000666000175100017510000000511113207044712021216 0ustar zuulzuul00000000000000# 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 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, chunked=False): if not self.return_type: fake_headers = fake_http_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', 'content-length': len(body) } resp_header = fake_http_response(header_info, status=self.return_type) return (resp_header, body) else: msg = "unsupported return type %s" % self.return_type raise TypeError(msg) class fake_http_response(dict): def __init__(self, headers, body=None, version=1.0, status=200, reason="Ok"): """Initialization of fake HTTP Response :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['status'] = str(self.status) self.reason = reason self.version = version self.headers = headers if headers: for key, value in headers.items(): self[key.lower()] = value 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) tempest-17.2.0/tempest/tests/lib/test_credentials.py0000666000175100017510000001647413207044712022623 0ustar zuulzuul00000000000000# 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 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 # NOTE(andreaf) project_* attributes are accepted as input but # never set on the credentials object attributes = [x for x in credentials.ATTRIBUTES if ( '_id' in x and x != 'domain_id' and x != 'project_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.patchobject(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: 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) tempest-17.2.0/tempest/tests/lib/fake_identity.py0000666000175100017510000001556013207044712022101 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from tempest.tests.lib import fake_http 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", "password_expires_at": None, }, "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", "password_expires_at": None, }, "serviceCatalog": CATALOG_V2, }, } # Fake Identity V3 constants COMPUTE_ENDPOINTS_V3 = { "endpoints": [ { "id": "first_compute_fake_service", "interface": "public", "region": "NoMatchRegion", "region_id": "NoMatchRegion", "url": "http://fake_url/v3/first_endpoint/api" }, { "id": "second_fake_service", "interface": "public", "region": "FakeRegion", "region_id": "FakeRegion", "url": "http://fake_url/v3/second_endpoint/api" }, { "id": "third_fake_service", "interface": "admin", "region": "MiddleEarthRegion", "region_id": "MiddleEarthRegion", "url": "http://fake_url/v3/third_endpoint/api" } ], "type": "compute", "id": "fake_compute_endpoint", "name": "nova" } CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ] IDENTITY_V3_RESPONSE = { "token": { "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"], "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", "password_expires_at": None, }, "issued_at": "2013-05-29T16:55:21.468960Z", "catalog": CATALOG_V3 } } IDENTITY_V3_RESPONSE_DOMAIN_SCOPE = { "token": { "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"], "methods": [ "token", "password" ], "expires_at": "2020-01-01T00:00:10.000123Z", "domain": { "id": "fake_domain_id", "name": "domain_name" }, "user": { "domain": { "id": "fake_domain_id", "name": "domain_name" }, "id": "fake_user_id", "name": "username", "password_expires_at": None, }, "issued_at": "2013-05-29T16:55:21.468960Z", "catalog": CATALOG_V3 } } IDENTITY_V3_RESPONSE_NO_SCOPE = { "token": { "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"], "methods": [ "token", "password" ], "expires_at": "2020-01-01T00:00:10.000123Z", "user": { "domain": { "id": "fake_domain_id", "name": "domain_name" }, "id": "fake_user_id", "name": "username", "password_expires_at": None, }, "issued_at": "2013-05-29T16:55:21.468960Z", } } 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 = { "x-subject-token": TOKEN } return (fake_http.fake_http_response(fake_headers, status=201), json.dumps(IDENTITY_V3_RESPONSE)) def _fake_v3_response_domain_scope(self, uri, method="GET", body=None, headers=None, redirections=5, connection_type=None): fake_headers = { "status": "201", "x-subject-token": TOKEN } return (fake_http.fake_http_response(fake_headers, status=201), json.dumps(IDENTITY_V3_RESPONSE_DOMAIN_SCOPE)) def _fake_v3_response_no_scope(self, uri, method="GET", body=None, headers=None, redirections=5, connection_type=None): fake_headers = { "status": "201", "x-subject-token": TOKEN } return (fake_http.fake_http_response(fake_headers, status=201), json.dumps(IDENTITY_V3_RESPONSE_NO_SCOPE)) def _fake_v2_response(self, uri, method="GET", body=None, headers=None, redirections=5, connection_type=None): return (fake_http.fake_http_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 fake_http.fake_http_response({}, status=401), json.dumps(body) tempest-17.2.0/tempest/tests/lib/common/0000775000175100017510000000000013207045130020162 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/common/test_validation_resources.py0000666000175100017510000004411513207044712026033 0ustar zuulzuul00000000000000# Copyright (c) 2017 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 fixtures import mock import testtools from tempest.lib.common import validation_resources as vr from tempest.lib import exceptions as lib_exc from tempest.lib.services import clients from tempest.tests import base from tempest.tests.lib import fake_credentials from tempest.tests.lib.services import registry_fixture FAKE_SECURITY_GROUP = {'security_group': {'id': 'sg_id'}} FAKE_KEYPAIR = {'keypair': {'name': 'keypair_name'}} FAKE_FIP_NOVA_NET = {'floating_ip': {'ip': '1.2.3.4', 'id': '1234'}} FAKE_FIP_NEUTRON = {'floatingip': {'floating_ip_address': '1.2.3.4', 'id': '1234'}} SERVICES = 'tempest.lib.services' SG_CLIENT = (SERVICES + '.%s.security_groups_client.SecurityGroupsClient.%s') SGR_CLIENT = (SERVICES + '.%s.security_group_rules_client.' 'SecurityGroupRulesClient.create_security_group_rule') KP_CLIENT = (SERVICES + '.compute.keypairs_client.KeyPairsClient.%s') FIP_CLIENT = (SERVICES + '.%s.floating_ips_client.FloatingIPsClient.%s') class TestValidationResources(base.TestCase): def setUp(self): super(TestValidationResources, self).setUp() self.useFixture(registry_fixture.RegistryFixture()) self.mock_sg_compute = self.useFixture(fixtures.MockPatch( SG_CLIENT % ('compute', 'create_security_group'), autospec=True, return_value=FAKE_SECURITY_GROUP)) self.mock_sg_network = self.useFixture(fixtures.MockPatch( SG_CLIENT % ('network', 'create_security_group'), autospec=True, return_value=FAKE_SECURITY_GROUP)) self.mock_sgr_compute = self.useFixture(fixtures.MockPatch( SGR_CLIENT % 'compute', autospec=True)) self.mock_sgr_network = self.useFixture(fixtures.MockPatch( SGR_CLIENT % 'network', autospec=True)) self.mock_kp = self.useFixture(fixtures.MockPatch( KP_CLIENT % 'create_keypair', autospec=True, return_value=FAKE_KEYPAIR)) self.mock_fip_compute = self.useFixture(fixtures.MockPatch( FIP_CLIENT % ('compute', 'create_floating_ip'), autospec=True, return_value=FAKE_FIP_NOVA_NET)) self.mock_fip_network = self.useFixture(fixtures.MockPatch( FIP_CLIENT % ('network', 'create_floatingip'), autospec=True, return_value=FAKE_FIP_NEUTRON)) self.os = clients.ServiceClients( fake_credentials.FakeKeystoneV3Credentials(), 'fake_uri') def test_create_ssh_security_group_nova_net(self): expected_sg_id = FAKE_SECURITY_GROUP['security_group']['id'] sg = vr.create_ssh_security_group(self.os, add_rule=True, use_neutron=False) self.assertEqual(FAKE_SECURITY_GROUP['security_group'], sg) # Neutron clients have not been used self.assertEqual(self.mock_sg_network.mock.call_count, 0) self.assertEqual(self.mock_sgr_network.mock.call_count, 0) # Nova-net clients assertions self.assertGreater(self.mock_sg_compute.mock.call_count, 0) self.assertGreater(self.mock_sgr_compute.mock.call_count, 0) for call in self.mock_sgr_compute.mock.call_args_list[1:]: self.assertIn(expected_sg_id, call[1].values()) def test_create_ssh_security_group_neutron(self): expected_sg_id = FAKE_SECURITY_GROUP['security_group']['id'] expected_ethertype = 'fake_ethertype' sg = vr.create_ssh_security_group(self.os, add_rule=True, use_neutron=True, ethertype=expected_ethertype) self.assertEqual(FAKE_SECURITY_GROUP['security_group'], sg) # Nova-net clients have not been used self.assertEqual(self.mock_sg_compute.mock.call_count, 0) self.assertEqual(self.mock_sgr_compute.mock.call_count, 0) # Nova-net clients assertions self.assertGreater(self.mock_sg_network.mock.call_count, 0) self.assertGreater(self.mock_sgr_network.mock.call_count, 0) # Check SG ID and ethertype are passed down to rules for call in self.mock_sgr_network.mock.call_args_list[1:]: self.assertIn(expected_sg_id, call[1].values()) self.assertIn(expected_ethertype, call[1].values()) def test_create_ssh_security_no_rules(self): sg = vr.create_ssh_security_group(self.os, add_rule=False) self.assertEqual(FAKE_SECURITY_GROUP['security_group'], sg) # SG Rules clients have not been used self.assertEqual(self.mock_sgr_compute.mock.call_count, 0) self.assertEqual(self.mock_sgr_network.mock.call_count, 0) @mock.patch.object(vr, 'create_ssh_security_group', return_value=FAKE_SECURITY_GROUP['security_group']) def test_create_validation_resources_nova_net(self, mock_create_sg): expected_floating_network_id = 'my_fni' expected_floating_network_name = 'my_fnn' resources = vr.create_validation_resources( self.os, keypair=True, floating_ip=True, security_group=True, security_group_rules=True, ethertype='IPv6', use_neutron=False, floating_network_id=expected_floating_network_id, floating_network_name=expected_floating_network_name) # Keypair calls self.assertGreater(self.mock_kp.mock.call_count, 0) # Floating IP calls self.assertGreater(self.mock_fip_compute.mock.call_count, 0) for call in self.mock_fip_compute.mock.call_args_list[1:]: self.assertIn(expected_floating_network_name, call[1].values()) self.assertNotIn(expected_floating_network_id, call[1].values()) self.assertEqual(self.mock_fip_network.mock.call_count, 0) # SG calls mock_create_sg.assert_called_once() # Resources for resource in ['keypair', 'floating_ip', 'security_group']: self.assertIn(resource, resources) self.assertEqual(FAKE_KEYPAIR['keypair'], resources['keypair']) self.assertEqual(FAKE_SECURITY_GROUP['security_group'], resources['security_group']) self.assertEqual(FAKE_FIP_NOVA_NET['floating_ip'], resources['floating_ip']) @mock.patch.object(vr, 'create_ssh_security_group', return_value=FAKE_SECURITY_GROUP['security_group']) def test_create_validation_resources_neutron(self, mock_create_sg): expected_floating_network_id = 'my_fni' expected_floating_network_name = 'my_fnn' resources = vr.create_validation_resources( self.os, keypair=True, floating_ip=True, security_group=True, security_group_rules=True, ethertype='IPv6', use_neutron=True, floating_network_id=expected_floating_network_id, floating_network_name=expected_floating_network_name) # Keypair calls self.assertGreater(self.mock_kp.mock.call_count, 0) # Floating IP calls self.assertEqual(self.mock_fip_compute.mock.call_count, 0) self.assertGreater(self.mock_fip_network.mock.call_count, 0) for call in self.mock_fip_compute.mock.call_args_list[1:]: self.assertIn(expected_floating_network_id, call[1].values()) self.assertNotIn(expected_floating_network_name, call[1].values()) # SG calls mock_create_sg.assert_called_once() # Resources for resource in ['keypair', 'floating_ip', 'security_group']: self.assertIn(resource, resources) self.assertEqual(FAKE_KEYPAIR['keypair'], resources['keypair']) self.assertEqual(FAKE_SECURITY_GROUP['security_group'], resources['security_group']) self.assertIn('ip', resources['floating_ip']) self.assertEqual(resources['floating_ip']['ip'], FAKE_FIP_NEUTRON['floatingip']['floating_ip_address']) self.assertEqual(resources['floating_ip']['id'], FAKE_FIP_NEUTRON['floatingip']['id']) class TestClearValidationResourcesFixture(base.TestCase): def setUp(self): super(TestClearValidationResourcesFixture, self).setUp() self.useFixture(registry_fixture.RegistryFixture()) self.mock_sg_compute = self.useFixture(fixtures.MockPatch( SG_CLIENT % ('compute', 'delete_security_group'), autospec=True)) self.mock_sg_network = self.useFixture(fixtures.MockPatch( SG_CLIENT % ('network', 'delete_security_group'), autospec=True)) self.mock_sg_wait_compute = self.useFixture(fixtures.MockPatch( SG_CLIENT % ('compute', 'wait_for_resource_deletion'), autospec=True)) self.mock_sg_wait_network = self.useFixture(fixtures.MockPatch( SG_CLIENT % ('network', 'wait_for_resource_deletion'), autospec=True)) self.mock_kp = self.useFixture(fixtures.MockPatch( KP_CLIENT % 'delete_keypair', autospec=True)) self.mock_fip_compute = self.useFixture(fixtures.MockPatch( FIP_CLIENT % ('compute', 'delete_floating_ip'), autospec=True)) self.mock_fip_network = self.useFixture(fixtures.MockPatch( FIP_CLIENT % ('network', 'delete_floatingip'), autospec=True)) self.os = clients.ServiceClients( fake_credentials.FakeKeystoneV3Credentials(), 'fake_uri') def test_clear_validation_resources_nova_net(self): vr.clear_validation_resources( self.os, floating_ip=FAKE_FIP_NOVA_NET['floating_ip'], security_group=FAKE_SECURITY_GROUP['security_group'], keypair=FAKE_KEYPAIR['keypair'], use_neutron=False) self.assertGreater(self.mock_kp.mock.call_count, 0) for call in self.mock_kp.mock.call_args_list[1:]: self.assertIn(FAKE_KEYPAIR['keypair']['name'], call[1].values()) self.assertGreater(self.mock_sg_compute.mock.call_count, 0) for call in self.mock_sg_compute.mock.call_args_list[1:]: self.assertIn(FAKE_SECURITY_GROUP['security_group']['id'], call[1].values()) self.assertGreater(self.mock_sg_wait_compute.mock.call_count, 0) for call in self.mock_sg_wait_compute.mock.call_args_list[1:]: self.assertIn(FAKE_SECURITY_GROUP['security_group']['id'], call[1].values()) self.assertEqual(self.mock_sg_network.mock.call_count, 0) self.assertEqual(self.mock_sg_wait_network.mock.call_count, 0) self.assertGreater(self.mock_fip_compute.mock.call_count, 0) for call in self.mock_fip_compute.mock.call_args_list[1:]: self.assertIn(FAKE_FIP_NOVA_NET['floating_ip']['id'], call[1].values()) self.assertEqual(self.mock_fip_network.mock.call_count, 0) def test_clear_validation_resources_neutron(self): vr.clear_validation_resources( self.os, floating_ip=FAKE_FIP_NEUTRON['floatingip'], security_group=FAKE_SECURITY_GROUP['security_group'], keypair=FAKE_KEYPAIR['keypair'], use_neutron=True) self.assertGreater(self.mock_kp.mock.call_count, 0) for call in self.mock_kp.mock.call_args_list[1:]: self.assertIn(FAKE_KEYPAIR['keypair']['name'], call[1].values()) self.assertGreater(self.mock_sg_network.mock.call_count, 0) for call in self.mock_sg_network.mock.call_args_list[1:]: self.assertIn(FAKE_SECURITY_GROUP['security_group']['id'], call[1].values()) self.assertGreater(self.mock_sg_wait_network.mock.call_count, 0) for call in self.mock_sg_wait_network.mock.call_args_list[1:]: self.assertIn(FAKE_SECURITY_GROUP['security_group']['id'], call[1].values()) self.assertEqual(self.mock_sg_compute.mock.call_count, 0) self.assertEqual(self.mock_sg_wait_compute.mock.call_count, 0) self.assertGreater(self.mock_fip_network.mock.call_count, 0) for call in self.mock_fip_network.mock.call_args_list[1:]: self.assertIn(FAKE_FIP_NEUTRON['floatingip']['id'], call[1].values()) self.assertEqual(self.mock_fip_compute.mock.call_count, 0) def test_clear_validation_resources_exceptions(self): # Test that even with exceptions all cleanups are invoked and that only # the first exception is reported. # NOTE(andreaf) There's not way of knowing which exception is going to # be raised first unless we enforce which resource is cleared first, # which is not really interesting, but also not harmful. keypair first. self.mock_kp.mock.side_effect = Exception('keypair exception') self.mock_sg_network.mock.side_effect = Exception('sg exception') self.mock_fip_network.mock.side_effect = Exception('fip exception') with testtools.ExpectedException(Exception, value_re='keypair'): vr.clear_validation_resources( self.os, floating_ip=FAKE_FIP_NEUTRON['floatingip'], security_group=FAKE_SECURITY_GROUP['security_group'], keypair=FAKE_KEYPAIR['keypair'], use_neutron=True) # Clients calls are still made, but not the wait call self.assertGreater(self.mock_kp.mock.call_count, 0) self.assertGreater(self.mock_sg_network.mock.call_count, 0) self.assertGreater(self.mock_fip_network.mock.call_count, 0) def test_clear_validation_resources_wait_not_found_wait(self): # Test that a not found on wait is not an exception self.mock_sg_wait_network.mock.side_effect = lib_exc.NotFound('yay') vr.clear_validation_resources( self.os, floating_ip=FAKE_FIP_NEUTRON['floatingip'], security_group=FAKE_SECURITY_GROUP['security_group'], keypair=FAKE_KEYPAIR['keypair'], use_neutron=True) # Clients calls are still made, but not the wait call self.assertGreater(self.mock_kp.mock.call_count, 0) self.assertGreater(self.mock_sg_network.mock.call_count, 0) self.assertGreater(self.mock_sg_wait_network.mock.call_count, 0) self.assertGreater(self.mock_fip_network.mock.call_count, 0) def test_clear_validation_resources_wait_not_found_delete(self): # Test that a not found on delete is not an exception self.mock_kp.mock.side_effect = lib_exc.NotFound('yay') self.mock_sg_network.mock.side_effect = lib_exc.NotFound('yay') self.mock_fip_network.mock.side_effect = lib_exc.NotFound('yay') vr.clear_validation_resources( self.os, floating_ip=FAKE_FIP_NEUTRON['floatingip'], security_group=FAKE_SECURITY_GROUP['security_group'], keypair=FAKE_KEYPAIR['keypair'], use_neutron=True) # Clients calls are still made, but not the wait call self.assertGreater(self.mock_kp.mock.call_count, 0) self.assertGreater(self.mock_sg_network.mock.call_count, 0) self.assertEqual(self.mock_sg_wait_network.mock.call_count, 0) self.assertGreater(self.mock_fip_network.mock.call_count, 0) class TestValidationResourcesFixture(base.TestCase): @mock.patch.object(vr, 'create_validation_resources', autospec=True) def test_use_fixture(self, mock_vr): exp_vr = dict(keypair='keypair', floating_ip='floating_ip', security_group='security_group') mock_vr.return_value = exp_vr exp_clients = 'clients' exp_parameters = dict(keypair=True, floating_ip=True, security_group=True, security_group_rules=True, ethertype='v6', use_neutron=True, floating_network_id='fnid', floating_network_name='fnname') # First mock cleanup self.useFixture(fixtures.MockPatchObject( vr, 'clear_validation_resources', autospec=True)) # And then use vr fixture, so when the fixture is cleaned-up, the mock # is still there vr_fixture = self.useFixture(vr.ValidationResourcesFixture( exp_clients, **exp_parameters)) # Assert vr have been provisioned mock_vr.assert_called_once_with(exp_clients, **exp_parameters) # Assert vr have been setup in the fixture self.assertEqual(exp_vr, vr_fixture.resources) @mock.patch.object(vr, 'clear_validation_resources', autospec=True) @mock.patch.object(vr, 'create_validation_resources', autospec=True) def test_use_fixture_context(self, mock_vr, mock_clear): exp_vr = dict(keypair='keypair', floating_ip='floating_ip', security_group='security_group') mock_vr.return_value = exp_vr exp_clients = 'clients' exp_parameters = dict(keypair=True, floating_ip=True, security_group=True, security_group_rules=True, ethertype='v6', use_neutron=True, floating_network_id='fnid', floating_network_name='fnname') with vr.ValidationResourcesFixture(exp_clients, **exp_parameters) as vr_fixture: # Assert vr have been provisioned mock_vr.assert_called_once_with(exp_clients, **exp_parameters) # Assert vr have been setup in the fixture self.assertEqual(exp_vr, vr_fixture.resources) # After context manager is closed, clear is invoked exp_vr['use_neutron'] = exp_parameters['use_neutron'] mock_clear.assert_called_once_with(exp_clients, **exp_vr) tempest-17.2.0/tempest/tests/lib/common/test_preprov_creds.py0000666000175100017510000005631313207044712024467 0ustar zuulzuul00000000000000# Copyright 2014 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 hashlib import os import shutil import mock import six import testtools import fixtures from oslo_concurrency.fixture import lockutils as lockutils_fixtures from oslo_config import cfg from tempest import config from tempest.lib import auth from tempest.lib.common import cred_provider from tempest.lib.common import preprov_creds from tempest.lib import exceptions as lib_exc from tempest.tests import base from tempest.tests import fake_config from tempest.tests.lib import fake_identity from tempest.tests.lib.services import registry_fixture class TestPreProvisionedCredentials(base.TestCase): fixed_params = {'name': 'test class', 'identity_version': 'v2', 'identity_uri': 'fake_uri', 'test_accounts_file': 'fake_accounts_file', 'accounts_lock_dir': 'fake_locks_dir', 'admin_role': 'admin', 'object_storage_operator_role': 'operator', 'object_storage_reseller_admin_role': 'reseller'} identity_response = fake_identity._fake_v2_response token_client = ('tempest.lib.services.identity.v2.token_client' '.TokenClient.raw_request') @classmethod def _fake_accounts(cls, admin_role): return [ {'username': 'test_user1', 'tenant_name': 'test_tenant1', 'password': 'p'}, {'username': 'test_user2', 'project_name': 'test_tenant2', 'password': 'p'}, {'username': 'test_user3', 'tenant_name': 'test_tenant3', 'password': 'p'}, {'username': 'test_user4', 'project_name': 'test_tenant4', 'password': 'p'}, {'username': 'test_user5', 'tenant_name': 'test_tenant5', 'password': 'p'}, {'username': 'test_user6', 'project_name': 'test_tenant6', 'password': 'p', 'roles': ['role1', 'role2']}, {'username': 'test_user7', 'tenant_name': 'test_tenant7', 'password': 'p', 'roles': ['role2', 'role3']}, {'username': 'test_user8', 'project_name': 'test_tenant8', 'password': 'p', 'roles': ['role4', 'role1']}, {'username': 'test_user9', 'tenant_name': 'test_tenant9', 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']}, {'username': 'test_user10', 'project_name': 'test_tenant10', 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']}, {'username': 'test_admin1', 'tenant_name': 'test_tenant11', 'password': 'p', 'roles': [admin_role]}, {'username': 'test_admin2', 'project_name': 'test_tenant12', 'password': 'p', 'roles': [admin_role]}, {'username': 'test_admin3', 'project_name': 'test_tenant13', 'password': 'p', 'types': ['admin']}] def setUp(self): super(TestPreProvisionedCredentials, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.patch(self.token_client, side_effect=self.identity_response) self.useFixture(lockutils_fixtures.ExternalLockFixture()) self.test_accounts = self._fake_accounts(cfg.CONF.identity.admin_role) self.accounts_mock = self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=self.test_accounts)) self.useFixture(fixtures.MockPatch( 'os.path.isfile', return_value=True)) # Make sure we leave the registry clean self.useFixture(registry_fixture.RegistryFixture()) def tearDown(self): super(TestPreProvisionedCredentials, self).tearDown() shutil.rmtree(self.fixed_params['accounts_lock_dir'], ignore_errors=True) def _get_hash_list(self, accounts_list): hash_list = [] hash_fields = ( preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS) for account in accounts_list: hash = hashlib.md5() account_for_hash = dict((k, v) for (k, v) in account.items() if k in hash_fields) hash.update(six.text_type(account_for_hash).encode('utf-8')) temp_hash = hash.hexdigest() hash_list.append(temp_hash) return hash_list def test_get_hash(self): # Test with all accounts to make sure we try all combinations # and hide no race conditions hash_index = 0 for test_cred_dict in self.test_accounts: test_account_class = ( preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params)) hash_list = self._get_hash_list(self.test_accounts) test_creds = auth.get_credentials( fake_identity.FAKE_AUTH_URL, identity_version=self.fixed_params['identity_version'], **test_cred_dict) results = test_account_class.get_hash(test_creds) self.assertEqual(hash_list[hash_index], results) hash_index += 1 def test_get_hash_dict(self): test_account_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) hash_dict = test_account_class.get_hash_dict( self.test_accounts, self.fixed_params['admin_role']) hash_list = self._get_hash_list(self.test_accounts) for hash in hash_list: self.assertIn(hash, hash_dict['creds'].keys()) self.assertIn(hash_dict['creds'][hash], self.test_accounts) def test_create_hash_file_previous_file(self): # Emulate the lock existing on the filesystem self.useFixture(fixtures.MockPatch( 'os.path.isfile', return_value=True)) with mock.patch('six.moves.builtins.open', mock.mock_open(), create=True): test_account_class = ( preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params)) res = test_account_class._create_hash_file('12345') self.assertFalse(res, "_create_hash_file should return False if the " "pseudo-lock file already exists") def test_create_hash_file_no_previous_file(self): # Emulate the lock not existing on the filesystem self.useFixture(fixtures.MockPatch( 'os.path.isfile', return_value=False)) with mock.patch('six.moves.builtins.open', mock.mock_open(), create=True): test_account_class = ( preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params)) res = test_account_class._create_hash_file('12345') self.assertTrue(res, "_create_hash_file should return True if the " "pseudo-lock doesn't already exist") @mock.patch('oslo_concurrency.lockutils.lock') def test_get_free_hash_no_previous_accounts(self, lock_mock): # Emulate no pre-existing lock self.useFixture(fixtures.MockPatch( 'os.path.isdir', return_value=False)) hash_list = self._get_hash_list(self.test_accounts) mkdir_mock = self.useFixture(fixtures.MockPatch('os.mkdir')) self.useFixture(fixtures.MockPatch( 'os.path.isfile', return_value=False)) test_account_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) with mock.patch('six.moves.builtins.open', mock.mock_open(), create=True) as open_mock: test_account_class._get_free_hash(hash_list) lock_path = os.path.join(self.fixed_params['accounts_lock_dir'], hash_list[0]) open_mock.assert_called_once_with(lock_path, 'w') mkdir_path = os.path.join(self.fixed_params['accounts_lock_dir']) mkdir_mock.mock.assert_called_once_with(mkdir_path) @mock.patch('oslo_concurrency.lockutils.lock') def test_get_free_hash_no_free_accounts(self, lock_mock): hash_list = self._get_hash_list(self.test_accounts) # Emulate pre-existing lock dir self.useFixture(fixtures.MockPatch('os.path.isdir', return_value=True)) # Emulate all locks in list are in use self.useFixture(fixtures.MockPatch( 'os.path.isfile', return_value=True)) test_account_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) with mock.patch('six.moves.builtins.open', mock.mock_open(), create=True): self.assertRaises(lib_exc.InvalidCredentials, test_account_class._get_free_hash, hash_list) @mock.patch('oslo_concurrency.lockutils.lock') def test_get_free_hash_some_in_use_accounts(self, lock_mock): # Emulate no pre-existing lock self.useFixture(fixtures.MockPatch('os.path.isdir', return_value=True)) hash_list = self._get_hash_list(self.test_accounts) test_account_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) def _fake_is_file(path): # Fake isfile() to return that the path exists unless a specific # hash is in the path if hash_list[3] in path: return False return True self.patchobject(os.path, 'isfile', _fake_is_file) with mock.patch('six.moves.builtins.open', mock.mock_open(), create=True) as open_mock: test_account_class._get_free_hash(hash_list) lock_path = os.path.join(self.fixed_params['accounts_lock_dir'], hash_list[3]) open_mock.assert_has_calls([mock.call(lock_path, 'w')]) @mock.patch('oslo_concurrency.lockutils.lock') def test_remove_hash_last_account(self, lock_mock): hash_list = self._get_hash_list(self.test_accounts) # Pretend the pseudo-lock is there self.useFixture( fixtures.MockPatch('os.path.isfile', return_value=True)) # Pretend the lock dir is empty self.useFixture(fixtures.MockPatch('os.listdir', return_value=[])) test_account_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) remove_mock = self.useFixture(fixtures.MockPatch('os.remove')) rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir')) test_account_class.remove_hash(hash_list[2]) hash_path = os.path.join(self.fixed_params['accounts_lock_dir'], hash_list[2]) lock_path = self.fixed_params['accounts_lock_dir'] remove_mock.mock.assert_called_once_with(hash_path) rmdir_mock.mock.assert_called_once_with(lock_path) @mock.patch('oslo_concurrency.lockutils.lock') def test_remove_hash_not_last_account(self, lock_mock): hash_list = self._get_hash_list(self.test_accounts) # Pretend the pseudo-lock is there self.useFixture(fixtures.MockPatch( 'os.path.isfile', return_value=True)) # Pretend the lock dir is empty self.useFixture(fixtures.MockPatch('os.listdir', return_value=[ hash_list[1], hash_list[4]])) test_account_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) remove_mock = self.useFixture(fixtures.MockPatch('os.remove')) rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir')) test_account_class.remove_hash(hash_list[2]) hash_path = os.path.join(self.fixed_params['accounts_lock_dir'], hash_list[2]) remove_mock.mock.assert_called_once_with(hash_path) rmdir_mock.mock.assert_not_called() def test_is_multi_user(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) self.assertTrue(test_accounts_class.is_multi_user()) def test_is_not_multi_user(self): self.test_accounts = [self.test_accounts[0]] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=self.test_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) self.assertFalse(test_accounts_class.is_multi_user()) def test__get_creds_by_roles_one_role(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) hashes = test_accounts_class.hash_dict['roles']['role4'] temp_hash = hashes[0] get_free_hash_mock = self.useFixture(fixtures.MockPatchObject( test_accounts_class, '_get_free_hash', return_value=temp_hash)) # Test a single role returns all matching roles test_accounts_class._get_creds(roles=['role4']) calls = get_free_hash_mock.mock.mock_calls self.assertEqual(len(calls), 1) args = calls[0][1][0] for i in hashes: self.assertIn(i, args) def test__get_creds_by_roles_list_role(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) hashes = test_accounts_class.hash_dict['roles']['role4'] hashes2 = test_accounts_class.hash_dict['roles']['role2'] hashes = list(set(hashes) & set(hashes2)) temp_hash = hashes[0] get_free_hash_mock = self.useFixture(fixtures.MockPatchObject( test_accounts_class, '_get_free_hash', return_value=temp_hash)) # Test an intersection of multiple roles test_accounts_class._get_creds(roles=['role2', 'role4']) calls = get_free_hash_mock.mock.mock_calls self.assertEqual(len(calls), 1) args = calls[0][1][0] for i in hashes: self.assertIn(i, args) def test__get_creds_by_roles_no_admin(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) hashes = list(test_accounts_class.hash_dict['creds'].keys()) admin_hashes = test_accounts_class.hash_dict['roles'][ cfg.CONF.identity.admin_role] temp_hash = hashes[0] get_free_hash_mock = self.useFixture(fixtures.MockPatchObject( test_accounts_class, '_get_free_hash', return_value=temp_hash)) # Test an intersection of multiple roles test_accounts_class._get_creds() calls = get_free_hash_mock.mock.mock_calls self.assertEqual(len(calls), 1) args = calls[0][1][0] self.assertEqual(len(args), 10) for i in admin_hashes: self.assertNotIn(i, args) def test_networks_returned_with_creds(self): test_accounts = [ {'username': 'test_user13', 'tenant_name': 'test_tenant13', 'password': 'p', 'resources': {'network': 'network-1'}}, {'username': 'test_user14', 'tenant_name': 'test_tenant14', 'password': 'p', 'roles': ['role-7', 'role-11'], 'resources': {'network': 'network-2'}}] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=test_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) with mock.patch('tempest.lib.services.compute.networks_client.' 'NetworksClient.list_networks', return_value={'networks': [{'name': 'network-2', 'id': 'fake-id', 'label': 'network-2'}]}): creds = test_accounts_class.get_creds_by_roles(['role-7']) self.assertIsInstance(creds, cred_provider.TestResources) network = creds.network self.assertIsNotNone(network) self.assertIn('name', network) self.assertIn('id', network) self.assertEqual('fake-id', network['id']) self.assertEqual('network-2', network['name']) def test_get_primary_creds(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) primary_creds = test_accounts_class.get_primary_creds() self.assertNotIn('test_admin', primary_creds.username) def test_get_primary_creds_none_available(self): admin_accounts = [x for x in self.test_accounts if 'test_admin' in x['username']] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=admin_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) with testtools.ExpectedException(lib_exc.InvalidCredentials): # Get one more test_accounts_class.get_primary_creds() def test_get_alt_creds(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) alt_creds = test_accounts_class.get_alt_creds() self.assertNotIn('test_admin', alt_creds.username) def test_get_alt_creds_none_available(self): admin_accounts = [x for x in self.test_accounts if 'test_admin' in x['username']] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=admin_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) with testtools.ExpectedException(lib_exc.InvalidCredentials): # Get one more test_accounts_class.get_alt_creds() def test_get_admin_creds(self): test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) admin_creds = test_accounts_class.get_admin_creds() self.assertIn('test_admin', admin_creds.username) def test_get_admin_creds_by_type(self): test_accounts = [ {'username': 'test_user10', 'project_name': 'test_tenant10', 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']}, {'username': 'test_admin1', 'tenant_name': 'test_tenant11', 'password': 'p', 'types': ['admin']}] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=test_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) admin_creds = test_accounts_class.get_admin_creds() self.assertIn('test_admin', admin_creds.username) def test_get_admin_creds_by_role(self): test_accounts = [ {'username': 'test_user10', 'project_name': 'test_tenant10', 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']}, {'username': 'test_admin1', 'tenant_name': 'test_tenant11', 'password': 'p', 'roles': [cfg.CONF.identity.admin_role]}] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=test_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) admin_creds = test_accounts_class.get_admin_creds() self.assertIn('test_admin', admin_creds.username) def test_get_admin_creds_none_available(self): non_admin_accounts = [x for x in self.test_accounts if 'test_admin' not in x['username']] self.useFixture(fixtures.MockPatch( 'tempest.lib.common.preprov_creds.read_accounts_yaml', return_value=non_admin_accounts)) test_accounts_class = preprov_creds.PreProvisionedCredentialProvider( **self.fixed_params) with testtools.ExpectedException(lib_exc.InvalidCredentials): # Get one more test_accounts_class.get_admin_creds() class TestPreProvisionedCredentialsV3(TestPreProvisionedCredentials): fixed_params = {'name': 'test class', 'identity_version': 'v3', 'identity_uri': 'fake_uri', 'test_accounts_file': 'fake_accounts_file', 'accounts_lock_dir': 'fake_locks_dir_v3', 'admin_role': 'admin', 'object_storage_operator_role': 'operator', 'object_storage_reseller_admin_role': 'reseller'} identity_response = fake_identity._fake_v3_response token_client = ('tempest.lib.services.identity.v3.token_client' '.V3TokenClient.raw_request') @classmethod def _fake_accounts(cls, admin_role): return [ {'username': 'test_user1', 'project_name': 'test_project1', 'domain_name': 'domain', 'password': 'p'}, {'username': 'test_user2', 'project_name': 'test_project2', 'domain_name': 'domain', 'password': 'p'}, {'username': 'test_user3', 'project_name': 'test_project3', 'domain_name': 'domain', 'password': 'p'}, {'username': 'test_user4', 'project_name': 'test_project4', 'domain_name': 'domain', 'password': 'p'}, {'username': 'test_user5', 'project_name': 'test_project5', 'domain_name': 'domain', 'password': 'p'}, {'username': 'test_user6', 'project_name': 'test_project6', 'domain_name': 'domain', 'password': 'p', 'roles': ['role1', 'role2']}, {'username': 'test_user7', 'project_name': 'test_project7', 'domain_name': 'domain', 'password': 'p', 'roles': ['role2', 'role3']}, {'username': 'test_user8', 'project_name': 'test_project8', 'domain_name': 'domain', 'password': 'p', 'roles': ['role4', 'role1']}, {'username': 'test_user9', 'project_name': 'test_project9', 'domain_name': 'domain', 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']}, {'username': 'test_user10', 'project_name': 'test_project10', 'domain_name': 'domain', 'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']}, {'username': 'test_admin1', 'project_name': 'test_project11', 'domain_name': 'domain', 'password': 'p', 'roles': [admin_role]}, {'username': 'test_admin2', 'project_name': 'test_project12', 'domain_name': 'domain', 'password': 'p', 'roles': [admin_role]}, {'username': 'test_admin3', 'project_name': 'test_tenant13', 'domain_name': 'domain', 'password': 'p', 'types': ['admin']}] tempest-17.2.0/tempest/tests/lib/common/test_cred_client.py0000666000175100017510000000635513207044712024066 0ustar zuulzuul00000000000000# Copyright 2016 Hewlett Packard Enterprise Development LP # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 tempest.lib.common import cred_client from tempest.tests import base class TestCredClientV2(base.TestCase): def setUp(self): super(TestCredClientV2, self).setUp() self.identity_client = mock.MagicMock() self.projects_client = mock.MagicMock() self.users_client = mock.MagicMock() self.roles_client = mock.MagicMock() self.creds_client = cred_client.V2CredsClient(self.identity_client, self.projects_client, self.users_client, self.roles_client) def test_create_project(self): self.projects_client.create_tenant.return_value = { 'tenant': 'a_tenant' } res = self.creds_client.create_project('fake_name', 'desc') self.assertEqual('a_tenant', res) self.projects_client.create_tenant.assert_called_once_with( name='fake_name', description='desc') def test_delete_project(self): self.creds_client.delete_project('fake_id') self.projects_client.delete_tenant.assert_called_once_with( 'fake_id') class TestCredClientV3(base.TestCase): def setUp(self): super(TestCredClientV3, self).setUp() self.identity_client = mock.MagicMock() self.projects_client = mock.MagicMock() self.users_client = mock.MagicMock() self.roles_client = mock.MagicMock() self.domains_client = mock.MagicMock() self.domains_client.list_domains.return_value = { 'domains': [{'id': 'fake_domain_id'}] } self.creds_client = cred_client.V3CredsClient(self.identity_client, self.projects_client, self.users_client, self.roles_client, self.domains_client, 'fake_domain') def test_create_project(self): self.projects_client.create_project.return_value = { 'project': 'a_tenant' } res = self.creds_client.create_project('fake_name', 'desc') self.assertEqual('a_tenant', res) self.projects_client.create_project.assert_called_once_with( name='fake_name', description='desc', domain_id='fake_domain_id') def test_delete_project(self): self.creds_client.delete_project('fake_id') self.projects_client.delete_project.assert_called_once_with( 'fake_id') tempest-17.2.0/tempest/tests/lib/common/test_api_version_request.py0000666000175100017510000001474613207044712025704 0ustar zuulzuul00000000000000# Copyright 2014 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.common import api_version_request from tempest.lib import exceptions from tempest.tests import base class APIVersionRequestTests(base.TestCase): def test_valid_version_strings(self): def _test_string(version, exp_major, exp_minor): v = api_version_request.APIVersionRequest(version) self.assertEqual(v.ver_major, exp_major) self.assertEqual(v.ver_minor, exp_minor) _test_string("1.1", 1, 1) _test_string("2.10", 2, 10) _test_string("5.234", 5, 234) _test_string("12.5", 12, 5) _test_string("2.0", 2, 0) _test_string("2.200", 2, 200) def test_null_version(self): v = api_version_request.APIVersionRequest() self.assertTrue(v.is_null()) def test_invalid_version_strings(self): self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "2") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "200") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "2.1.4") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "200.23.66.3") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "5 .3") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "5. 3") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "5.03") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "02.1") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "2.001") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, " 2.1") self.assertRaises(exceptions.InvalidAPIVersionString, api_version_request.APIVersionRequest, "2.1 ") def test_version_comparisons(self): vers2_0 = api_version_request.APIVersionRequest("2.0") vers2_5 = api_version_request.APIVersionRequest("2.5") vers5_23 = api_version_request.APIVersionRequest("5.23") v_null = api_version_request.APIVersionRequest() v_latest = api_version_request.APIVersionRequest('latest') self.assertTrue(v_null < vers2_5) self.assertTrue(vers2_0 < vers2_5) self.assertTrue(vers2_0 <= vers2_5) self.assertTrue(vers2_0 <= vers2_0) self.assertTrue(vers2_5 > v_null) self.assertTrue(vers5_23 > vers2_5) self.assertTrue(vers2_0 >= vers2_0) self.assertTrue(vers5_23 >= vers2_5) self.assertTrue(vers2_0 != vers2_5) self.assertTrue(vers2_0 == vers2_0) self.assertTrue(vers2_0 != v_null) self.assertTrue(v_null == v_null) self.assertTrue(vers2_0 <= v_latest) self.assertTrue(vers2_0 != v_latest) self.assertTrue(v_latest == v_latest) self.assertRaises(TypeError, vers2_0.__lt__, "2.1") def test_version_matches(self): vers2_0 = api_version_request.APIVersionRequest("2.0") vers2_5 = api_version_request.APIVersionRequest("2.5") vers2_45 = api_version_request.APIVersionRequest("2.45") vers3_3 = api_version_request.APIVersionRequest("3.3") vers3_23 = api_version_request.APIVersionRequest("3.23") vers4_0 = api_version_request.APIVersionRequest("4.0") v_null = api_version_request.APIVersionRequest() v_latest = api_version_request.APIVersionRequest('latest') def _check_version_matches(version, version1, version2, check=True): if check: msg = "Version %s does not matches with [%s - %s] range" self.assertTrue(version.matches(version1, version2), msg % (version.get_string(), version1.get_string(), version2.get_string())) else: msg = "Version %s matches with [%s - %s] range" self.assertFalse(version.matches(version1, version2), msg % (version.get_string(), version1.get_string(), version2.get_string())) _check_version_matches(vers2_5, vers2_0, vers2_45) _check_version_matches(vers2_5, vers2_0, v_null) _check_version_matches(vers2_0, vers2_0, vers2_5) _check_version_matches(vers3_3, vers2_5, vers3_3) _check_version_matches(vers3_3, v_null, vers3_3) _check_version_matches(vers3_3, v_null, vers4_0) _check_version_matches(vers2_0, vers2_5, vers2_45, False) _check_version_matches(vers3_23, vers2_5, vers3_3, False) _check_version_matches(vers2_5, vers2_45, vers2_0, False) _check_version_matches(vers2_5, vers2_0, v_latest) _check_version_matches(v_latest, v_latest, v_latest) _check_version_matches(vers2_5, v_latest, v_latest, False) _check_version_matches(v_latest, vers2_0, vers4_0, False) self.assertRaises(ValueError, v_null.matches, vers2_0, vers2_45) def test_get_string(self): vers_string = ["3.23", "latest"] for ver in vers_string: ver_obj = api_version_request.APIVersionRequest(ver) self.assertEqual(ver, ver_obj.get_string()) self.assertIsNotNone( api_version_request.APIVersionRequest().get_string) tempest-17.2.0/tempest/tests/lib/common/test_jsonschema_validator.py0000666000175100017510000000661013207044712026004 0ustar zuulzuul00000000000000# 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.api_schema.response.compute.v2_1 import parameter_types from tempest.lib.common import rest_client from tempest.lib import exceptions from tempest.tests import base from tempest.tests.lib import fake_http class TestJSONSchemaDateTimeFormat(base.TestCase): date_time_schema = [ { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'date-time': parameter_types.date_time } } }, { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'date-time': parameter_types.date_time_or_null } } } ] def test_valid_date_time_format(self): valid_instances = ['2016-10-02T10:00:00-05:00', '2016-10-02T10:00:00+09:00', '2016-10-02T15:00:00Z', '2016-10-02T15:00:00.05Z'] resp = fake_http.fake_http_response('', status=200) for instance in valid_instances: body = {'date-time': instance} for schema in self.date_time_schema: rest_client.RestClient.validate_response(schema, resp, body) def test_invalid_date_time_format(self): invalid_instances = ['2016-10-02 T10:00:00-05:00', '2016-10-02T 15:00:00', '2016-10-02T15:00:00.05 Z', '2016-10-02:15:00:00.05Z', 'T15:00:00.05Z', '2016:10:02T15:00:00', '2016-10-02T15-00-00', '2016-10-02T15.05Z', '09MAR2015 11:15', '13 Oct 2015 05:55:36 GMT', ''] resp = fake_http.fake_http_response('', status=200) for instance in invalid_instances: body = {'date-time': instance} for schema in self.date_time_schema: self.assertRaises(exceptions.InvalidHTTPResponseBody, rest_client.RestClient.validate_response, schema, resp, body) def test_date_time_or_null_format(self): instance = None resp = fake_http.fake_http_response('', status=200) body = {'date-time': instance} rest_client.RestClient.validate_response(self.date_time_schema[1], resp, body) self.assertRaises(exceptions.InvalidHTTPResponseBody, rest_client.RestClient.validate_response, self.date_time_schema[0], resp, body) tempest-17.2.0/tempest/tests/lib/common/utils/0000775000175100017510000000000013207045130021322 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/common/utils/__init__.py0000666000175100017510000000000013207044712023430 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/common/utils/linux/0000775000175100017510000000000013207045130022461 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/common/utils/linux/test_remote_client.py0000666000175100017510000000560513207044712026740 0ustar zuulzuul00000000000000# Copyright 2017 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 from tempest.lib.common import ssh from tempest.lib.common.utils.linux import remote_client from tempest.lib import exceptions as lib_exc from tempest.tests import base class FakeServersClient(object): def get_console_output(self, server_id): return {"output": "fake_output"} class TestRemoteClient(base.TestCase): @mock.patch.object(ssh.Client, 'exec_command', return_value='success') def test_exec_command(self, mock_ssh_exec_command): client = remote_client.RemoteClient('192.168.1.10', 'username') client.exec_command('ls') mock_ssh_exec_command.assert_called_once_with( 'set -eu -o pipefail; PATH=$PATH:/sbin; ls') @mock.patch.object(ssh.Client, 'test_connection_auth') def test_validate_authentication(self, mock_test_connection_auth): client = remote_client.RemoteClient('192.168.1.10', 'username') client.validate_authentication() mock_test_connection_auth.assert_called_once_with() @mock.patch.object(remote_client.LOG, 'debug') @mock.patch.object(ssh.Client, 'exec_command') def test_debug_ssh_without_console(self, mock_exec_command, mock_debug): mock_exec_command.side_effect = lib_exc.SSHTimeout server = {'id': 'fake_id'} client = remote_client.RemoteClient('192.168.1.10', 'username', server=server) self.assertRaises(lib_exc.SSHTimeout, client.exec_command, 'ls') mock_debug.assert_called_with( 'Caller: %s. Timeout trying to ssh to server %s', 'TestRemoteClient:test_debug_ssh_without_console', server) @mock.patch.object(remote_client.LOG, 'debug') @mock.patch.object(ssh.Client, 'exec_command') def test_debug_ssh_with_console(self, mock_exec_command, mock_debug): mock_exec_command.side_effect = lib_exc.SSHTimeout server = {'id': 'fake_id'} client = remote_client.RemoteClient('192.168.1.10', 'username', server=server, servers_client=FakeServersClient()) self.assertRaises(lib_exc.SSHTimeout, client.exec_command, 'ls') mock_debug.assert_called_with( 'Console log for server %s: %s', server['id'], 'fake_output') tempest-17.2.0/tempest/tests/lib/common/utils/linux/__init__.py0000666000175100017510000000000013207044712024567 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/common/utils/test_misc.py0000666000175100017510000000251513207044712023700 0ustar zuulzuul00000000000000# 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 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) tempest-17.2.0/tempest/tests/lib/common/utils/test_data_utils.py0000666000175100017510000001325613207044712025102 0ustar zuulzuul00000000000000# 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 data_utils from tempest.tests import base class TestDataUtils(base.TestCase): def test_rand_uuid(self): actual = data_utils.rand_uuid() self.assertIsInstance(actual, str) self.assertRegex(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.assertRegex(actual, "^[0-9a-f]{32}$") actual2 = data_utils.rand_uuid_hex() self.assertNotEqual(actual, actual2) def test_rand_name_with_default_prefix(self): actual = data_utils.rand_name('foo') self.assertIsInstance(actual, str) self.assertTrue(actual.startswith('tempest-foo')) actual2 = data_utils.rand_name('foo') self.assertTrue(actual2.startswith('tempest-foo')) self.assertNotEqual(actual, actual2) def test_rand_name_with_none_prefix(self): actual = data_utils.rand_name('foo', prefix=None) self.assertIsInstance(actual, str) self.assertTrue(actual.startswith('foo')) actual2 = data_utils.rand_name('foo', prefix=None) self.assertTrue(actual2.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.assertRegex(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.assertRegex(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.assertRegex(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.assertRegex(actual, "[A-Za-z0-9~!@#%^&*_=+]{3}") actual2 = data_utils.rand_password(2) # NOTE(masayukig): Originally, we checked that the acutal and actual2 # are different each other. But only 3 letters can be the same value # in a very rare case. So, we just check the length here, too, # just in case. self.assertEqual(len(actual2), 3) def test_rand_url(self): actual = data_utils.rand_url() self.assertIsInstance(actual, str) self.assertRegex(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_infiniband_guid_address(self): actual = data_utils.rand_infiniband_guid_address() self.assertIsInstance(actual, str) self.assertRegex(actual, "^([0-9a-f][0-9a-f]:){7}" "[0-9a-f][0-9a-f]$") actual2 = data_utils.rand_infiniband_guid_address() self.assertNotEqual(actual, actual2) def test_rand_mac_address(self): actual = data_utils.rand_mac_address() self.assertIsInstance(actual, str) self.assertRegex(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, bytes) self.assertEqual(1024, len(actual)) actual2 = data_utils.random_bytes() self.assertNotEqual(actual, actual2) actual = data_utils.random_bytes(size=2048) self.assertEqual(2048, len(actual)) def test_chunkify(self): data = "aaa" chunks = data_utils.chunkify(data, 2) self.assertEqual("aa", next(chunks)) self.assertEqual("a", next(chunks)) self.assertRaises(StopIteration, next, chunks) tempest-17.2.0/tempest/tests/lib/common/utils/test_test_utils.py0000666000175100017510000001173413207044712025147 0ustar zuulzuul00000000000000# Copyright 2016 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 mock from tempest.lib.common.utils import test_utils from tempest.lib import exceptions from tempest.tests import base from tempest.tests import utils class TestTestUtils(base.TestCase): def test_find_test_caller_test_case(self): # Calling it from here should give us the method we're in. self.assertEqual('TestTestUtils:test_find_test_caller_test_case', test_utils.find_test_caller()) def test_find_test_caller_setup_self(self): def setUp(self): return test_utils.find_test_caller() self.assertEqual('TestTestUtils:setUp', setUp(self)) def test_find_test_caller_setup_no_self(self): def setUp(): return test_utils.find_test_caller() self.assertEqual(':setUp', setUp()) def test_find_test_caller_setupclass_cls(self): def setUpClass(cls): # noqa return test_utils.find_test_caller() self.assertEqual('TestTestUtils:setUpClass', setUpClass(self.__class__)) def test_find_test_caller_teardown_self(self): def tearDown(self): return test_utils.find_test_caller() self.assertEqual('TestTestUtils:tearDown', tearDown(self)) def test_find_test_caller_teardown_no_self(self): def tearDown(): return test_utils.find_test_caller() self.assertEqual(':tearDown', tearDown()) def test_find_test_caller_teardown_class(self): def tearDownClass(cls): # noqa return test_utils.find_test_caller() self.assertEqual('TestTestUtils:tearDownClass', tearDownClass(self.__class__)) def test_call_and_ignore_notfound_exc_when_notfound_raised(self): def raise_not_found(): raise exceptions.NotFound() self.assertIsNone( test_utils.call_and_ignore_notfound_exc(raise_not_found)) def test_call_and_ignore_notfound_exc_when_value_error_raised(self): def raise_value_error(): raise ValueError() self.assertRaises(ValueError, test_utils.call_and_ignore_notfound_exc, raise_value_error) def test_call_and_ignore_notfound_exc(self): m = mock.Mock(return_value=42) args, kwargs = (1,), {'1': None} self.assertEqual( 42, test_utils.call_and_ignore_notfound_exc(m, *args, **kwargs)) m.assert_called_once_with(*args, **kwargs) @mock.patch('time.sleep') @mock.patch('time.time') def test_call_until_true_when_f_never_returns_true(self, m_time, m_sleep): def set_value(bool_value): return bool_value timeout = 42 # The value doesn't matter as we mock time.time() sleep = 60 # The value doesn't matter as we mock time.sleep() m_time.side_effect = utils.generate_timeout_series(timeout) self.assertEqual( False, test_utils.call_until_true(set_value, timeout, sleep, False) ) m_sleep.call_args_list = [mock.call(sleep)] * 2 m_time.call_args_list = [mock.call()] * 2 @mock.patch('time.sleep') @mock.patch('time.time') def test_call_until_true_when_f_returns_true(self, m_time, m_sleep): def set_value(bool_value=False): return bool_value timeout = 42 # The value doesn't matter as we mock time.time() sleep = 60 # The value doesn't matter as we mock time.sleep() m_time.return_value = 0 self.assertEqual( True, test_utils.call_until_true(set_value, timeout, sleep, bool_value=True) ) self.assertEqual(0, m_sleep.call_count) # when logging cost time we need to acquire current time. self.assertEqual(2, m_time.call_count) @mock.patch('time.sleep') @mock.patch('time.time') def test_call_until_true_when_f_returns_true_no_param( self, m_time, m_sleep): def set_value(bool_value=False): return bool_value timeout = 42 # The value doesn't matter as we mock time.time() sleep = 60 # The value doesn't matter as we mock time.sleep() m_time.side_effect = utils.generate_timeout_series(timeout) self.assertEqual( False, test_utils.call_until_true(set_value, timeout, sleep) ) m_sleep.call_args_list = [mock.call(sleep)] * 2 m_time.call_args_list = [mock.call()] * 2 tempest-17.2.0/tempest/tests/lib/common/test_dynamic_creds.py0000666000175100017510000007340313207044712024415 0ustar zuulzuul00000000000000# Copyright 2014 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 fixtures import mock from oslo_config import cfg from tempest.common import credentials_factory as credentials from tempest import config from tempest.lib.common import dynamic_creds from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc from tempest.lib.services.identity.v2 import identity_client as v2_iden_client from tempest.lib.services.identity.v2 import roles_client as v2_roles_client from tempest.lib.services.identity.v2 import tenants_client as \ v2_tenants_client from tempest.lib.services.identity.v2 import token_client as v2_token_client from tempest.lib.services.identity.v2 import users_client as v2_users_client from tempest.lib.services.identity.v3 import domains_client from tempest.lib.services.identity.v3 import identity_client as v3_iden_client from tempest.lib.services.identity.v3 import projects_client as \ v3_projects_client from tempest.lib.services.identity.v3 import roles_client as v3_roles_client from tempest.lib.services.identity.v3 import token_client as v3_token_client from tempest.lib.services.identity.v3 import users_client as \ v3_users_client from tempest.lib.services.network import routers_client from tempest.tests import base from tempest.tests import fake_config from tempest.tests.lib import fake_http from tempest.tests.lib import fake_identity from tempest.tests.lib.services import registry_fixture class TestDynamicCredentialProvider(base.TestCase): fixed_params = {'name': 'test class', 'identity_version': 'v2', 'admin_role': 'admin', 'identity_uri': 'fake_uri'} token_client = v2_token_client iden_client = v2_iden_client roles_client = v2_roles_client tenants_client = v2_tenants_client users_client = v2_users_client token_client_class = token_client.TokenClient fake_response = fake_identity._fake_v2_response tenants_client_class = tenants_client.TenantsClient delete_tenant = 'delete_tenant' def setUp(self): super(TestDynamicCredentialProvider, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.useFixture(registry_fixture.RegistryFixture()) self.patchobject(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.patchobject(self.token_client_class, 'raw_request', self.fake_response) cfg.CONF.set_default('operator_role', 'FakeRole', group='object-storage') self._mock_list_ec2_credentials('fake_user_id', 'fake_tenant_id') self.fixed_params.update( admin_creds=self._get_fake_admin_creds()) def test_tempest_client(self): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self.assertIsInstance(creds.identity_admin_client, self.iden_client.IdentityClient) def _get_fake_admin_creds(self): return credentials.get_credentials( fill_in=False, identity_version=self.fixed_params['identity_version'], username='fake_username', password='fake_password', tenant_name='fake_tenant') def _mock_user_create(self, id, name): user_fix = self.useFixture(fixtures.MockPatchObject( self.users_client.UsersClient, 'create_user', return_value=(rest_client.ResponseBody (200, {'user': {'id': id, 'name': name}})))) return user_fix def _mock_tenant_create(self, id, name): tenant_fix = self.useFixture(fixtures.MockPatchObject( self.tenants_client.TenantsClient, 'create_tenant', return_value=(rest_client.ResponseBody (200, {'tenant': {'id': id, 'name': name}})))) return tenant_fix def _mock_list_roles(self, id, name): roles_fix = self.useFixture(fixtures.MockPatchObject( self.roles_client.RolesClient, 'list_roles', return_value=(rest_client.ResponseBody (200, {'roles': [{'id': id, 'name': name}, {'id': '1', 'name': 'FakeRole'}, {'id': '2', 'name': 'Member'}]})))) return roles_fix def _mock_list_2_roles(self): roles_fix = self.useFixture(fixtures.MockPatchObject( self.roles_client.RolesClient, 'list_roles', return_value=(rest_client.ResponseBody (200, {'roles': [{'id': '1234', 'name': 'role1'}, {'id': '1', 'name': 'FakeRole'}, {'id': '12345', 'name': 'role2'}]})))) return roles_fix def _mock_assign_user_role(self): tenant_fix = self.useFixture(fixtures.MockPatchObject( self.roles_client.RolesClient, 'create_user_role_on_project', return_value=(rest_client.ResponseBody (200, {})))) return tenant_fix def _mock_list_role(self): roles_fix = self.useFixture(fixtures.MockPatchObject( self.roles_client.RolesClient, 'list_roles', return_value=(rest_client.ResponseBody (200, {'roles': [ {'id': '1', 'name': 'FakeRole'}, {'id': '2', 'name': 'Member'}]})))) return roles_fix def _mock_list_ec2_credentials(self, user_id, tenant_id): ec2_creds_fix = self.useFixture(fixtures.MockPatchObject( self.users_client.UsersClient, 'list_user_ec2_credentials', return_value=(rest_client.ResponseBody (200, {'credentials': [{ 'access': 'fake_access', 'secret': 'fake_secret', 'tenant_id': tenant_id, 'user_id': user_id, 'trust_id': None}]})))) return ec2_creds_fix def _mock_network_create(self, iso_creds, id, name): net_fix = self.useFixture(fixtures.MockPatchObject( iso_creds.networks_admin_client, 'create_network', return_value={'network': {'id': id, 'name': name}})) return net_fix def _mock_subnet_create(self, iso_creds, id, name): subnet_fix = self.useFixture(fixtures.MockPatchObject( iso_creds.subnets_admin_client, 'create_subnet', return_value={'subnet': {'id': id, 'name': name}})) return subnet_fix def _mock_router_create(self, id, name): router_fix = self.useFixture(fixtures.MockPatchObject( routers_client.RoutersClient, 'create_router', return_value={'router': {'id': id, 'name': name}})) return router_fix @mock.patch('tempest.lib.common.rest_client.RestClient') def test_primary_creds(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_tenant_create('1234', 'fake_prim_tenant') self._mock_user_create('1234', 'fake_prim_user') primary_creds = creds.get_primary_creds() self.assertEqual(primary_creds.username, 'fake_prim_user') self.assertEqual(primary_creds.tenant_name, 'fake_prim_tenant') # Verify IDs self.assertEqual(primary_creds.tenant_id, '1234') self.assertEqual(primary_creds.user_id, '1234') @mock.patch('tempest.lib.common.rest_client.RestClient') def test_admin_creds(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self._mock_list_roles('1234', 'admin') self._mock_user_create('1234', 'fake_admin_user') self._mock_tenant_create('1234', 'fake_admin_tenant') user_mock = mock.patch.object(self.roles_client.RolesClient, 'create_user_role_on_project') user_mock.start() self.addCleanup(user_mock.stop) with mock.patch.object(self.roles_client.RolesClient, 'create_user_role_on_project') as user_mock: admin_creds = creds.get_admin_creds() user_mock.assert_has_calls([ mock.call('1234', '1234', '1234')]) self.assertEqual(admin_creds.username, 'fake_admin_user') self.assertEqual(admin_creds.tenant_name, 'fake_admin_tenant') # Verify IDs self.assertEqual(admin_creds.tenant_id, '1234') self.assertEqual(admin_creds.user_id, '1234') @mock.patch('tempest.lib.common.rest_client.RestClient') def test_role_creds(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self._mock_list_2_roles() self._mock_user_create('1234', 'fake_role_user') self._mock_tenant_create('1234', 'fake_role_tenant') user_mock = mock.patch.object(self.roles_client.RolesClient, 'create_user_role_on_project') user_mock.start() self.addCleanup(user_mock.stop) with mock.patch.object(self.roles_client.RolesClient, 'create_user_role_on_project') as user_mock: role_creds = creds.get_creds_by_roles( roles=['role1', 'role2']) calls = user_mock.mock_calls # Assert that the role creation is called with the 2 specified roles self.assertEqual(len(calls), 2) args = map(lambda x: x[1], calls) args = list(args) self.assertIn(('1234', '1234', '1234'), args) self.assertIn(('1234', '1234', '12345'), args) self.assertEqual(role_creds.username, 'fake_role_user') self.assertEqual(role_creds.tenant_name, 'fake_role_tenant') # Verify IDs self.assertEqual(role_creds.tenant_id, '1234') self.assertEqual(role_creds.user_id, '1234') @mock.patch('tempest.lib.common.rest_client.RestClient') def test_all_cred_cleanup(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_tenant_create('1234', 'fake_prim_tenant') self._mock_user_create('1234', 'fake_prim_user') creds.get_primary_creds() self._mock_tenant_create('12345', 'fake_alt_tenant') self._mock_user_create('12345', 'fake_alt_user') creds.get_alt_creds() self._mock_tenant_create('123456', 'fake_admin_tenant') self._mock_user_create('123456', 'fake_admin_user') self._mock_list_roles('123456', 'admin') creds.get_admin_creds() user_mock = self.patchobject(self.users_client.UsersClient, 'delete_user') tenant_mock = self.patchobject(self.tenants_client_class, self.delete_tenant) creds.clear_creds() # Verify user delete calls calls = user_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify tenant delete calls calls = tenant_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) @mock.patch('tempest.lib.common.rest_client.RestClient') def test_alt_creds(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_alt_user') self._mock_tenant_create('1234', 'fake_alt_tenant') alt_creds = creds.get_alt_creds() self.assertEqual(alt_creds.username, 'fake_alt_user') self.assertEqual(alt_creds.tenant_name, 'fake_alt_tenant') # Verify IDs self.assertEqual(alt_creds.tenant_id, '1234') self.assertEqual(alt_creds.user_id, '1234') @mock.patch('tempest.lib.common.rest_client.RestClient') def test_no_network_creation_with_config_set(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, create_networks=False, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') net = mock.patch.object(creds.networks_admin_client, 'delete_network') net_mock = net.start() subnet = mock.patch.object(creds.subnets_admin_client, 'delete_subnet') subnet_mock = subnet.start() router = mock.patch.object(creds.routers_admin_client, 'delete_router') router_mock = router.start() primary_creds = creds.get_primary_creds() self.assertEqual(net_mock.mock_calls, []) self.assertEqual(subnet_mock.mock_calls, []) self.assertEqual(router_mock.mock_calls, []) network = primary_creds.network subnet = primary_creds.subnet router = primary_creds.router self.assertIsNone(network) self.assertIsNone(subnet) self.assertIsNone(router) @mock.patch('tempest.lib.common.rest_client.RestClient') def test_network_creation(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self._mock_network_create(creds, '1234', 'fake_net') self._mock_subnet_create(creds, '1234', 'fake_subnet') self._mock_router_create('1234', 'fake_router') router_interface_mock = self.patch( 'tempest.lib.services.network.routers_client.RoutersClient.' 'add_router_interface') primary_creds = creds.get_primary_creds() router_interface_mock.assert_called_once_with('1234', subnet_id='1234') network = primary_creds.network subnet = primary_creds.subnet router = primary_creds.router self.assertEqual(network['id'], '1234') self.assertEqual(network['name'], 'fake_net') self.assertEqual(subnet['id'], '1234') self.assertEqual(subnet['name'], 'fake_subnet') self.assertEqual(router['id'], '1234') self.assertEqual(router['name'], 'fake_router') @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'], "name": args['name'], "description": args['name'], "security_group_rules": [], "id": "sg-%s" % args['tenant_id']}]} creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, **self.fixed_params) # Create primary tenant and network self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self._mock_network_create(creds, '1234', 'fake_net') self._mock_subnet_create(creds, '1234', 'fake_subnet') self._mock_router_create('1234', 'fake_router') router_interface_mock = self.patch( 'tempest.lib.services.network.routers_client.RoutersClient.' 'add_router_interface') creds.get_primary_creds() router_interface_mock.assert_called_once_with('1234', subnet_id='1234') router_interface_mock.reset_mock() # Create alternate tenant and network self._mock_user_create('12345', 'fake_alt_user') self._mock_tenant_create('12345', 'fake_alt_tenant') self._mock_network_create(creds, '12345', 'fake_alt_net') self._mock_subnet_create(creds, '12345', 'fake_alt_subnet') self._mock_router_create('12345', 'fake_alt_router') creds.get_alt_creds() router_interface_mock.assert_called_once_with('12345', subnet_id='12345') router_interface_mock.reset_mock() # Create admin tenant and networks self._mock_user_create('123456', 'fake_admin_user') self._mock_tenant_create('123456', 'fake_admin_tenant') self._mock_network_create(creds, '123456', 'fake_admin_net') self._mock_subnet_create(creds, '123456', 'fake_admin_subnet') self._mock_router_create('123456', 'fake_admin_router') self._mock_list_roles('123456', 'admin') creds.get_admin_creds() self.patchobject(self.users_client.UsersClient, 'delete_user') self.patchobject(self.tenants_client_class, self.delete_tenant) net = mock.patch.object(creds.networks_admin_client, 'delete_network') net_mock = net.start() subnet = mock.patch.object(creds.subnets_admin_client, 'delete_subnet') subnet_mock = subnet.start() router = mock.patch.object(creds.routers_admin_client, 'delete_router') router_mock = router.start() remove_router_interface_mock = self.patch( 'tempest.lib.services.network.routers_client.RoutersClient.' 'remove_router_interface') return_values = ({'status': 200}, {'ports': []}) port_list_mock = mock.patch.object(creds.ports_admin_client, 'list_ports', return_value=return_values) port_list_mock.start() secgroup_list_mock = mock.patch.object( creds.security_groups_admin_client, 'list_security_groups', side_effect=side_effect) secgroup_list_mock.start() return_values = fake_http.fake_http_response({}, status=204), '' remove_secgroup_mock = self.patch( 'tempest.lib.services.network.security_groups_client.' 'SecurityGroupsClient.delete', return_value=return_values) creds.clear_creds() # Verify default security group delete calls = remove_secgroup_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('v2.0/security-groups/sg-1234', args) self.assertIn('v2.0/security-groups/sg-12345', args) self.assertIn('v2.0/security-groups/sg-123456', args) # Verify remove router interface calls calls = remove_router_interface_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: (x[1][0], x[2]), calls) args = list(args) self.assertIn(('1234', {'subnet_id': '1234'}), args) self.assertIn(('12345', {'subnet_id': '12345'}), args) self.assertIn(('123456', {'subnet_id': '123456'}), args) # Verify network delete calls calls = net_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify subnet delete calls calls = subnet_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify router delete calls calls = router_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) @mock.patch('tempest.lib.common.rest_client.RestClient') def test_network_alt_creation(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_alt_user') self._mock_tenant_create('1234', 'fake_alt_tenant') self._mock_network_create(creds, '1234', 'fake_alt_net') self._mock_subnet_create(creds, '1234', 'fake_alt_subnet') self._mock_router_create('1234', 'fake_alt_router') router_interface_mock = self.patch( 'tempest.lib.services.network.routers_client.RoutersClient.' 'add_router_interface') alt_creds = creds.get_alt_creds() router_interface_mock.assert_called_once_with('1234', subnet_id='1234') network = alt_creds.network subnet = alt_creds.subnet router = alt_creds.router self.assertEqual(network['id'], '1234') self.assertEqual(network['name'], 'fake_alt_net') self.assertEqual(subnet['id'], '1234') self.assertEqual(subnet['name'], 'fake_alt_subnet') self.assertEqual(router['id'], '1234') self.assertEqual(router['name'], 'fake_alt_router') @mock.patch('tempest.lib.common.rest_client.RestClient') def test_network_admin_creation(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, **self.fixed_params) self._mock_assign_user_role() self._mock_user_create('1234', 'fake_admin_user') self._mock_tenant_create('1234', 'fake_admin_tenant') self._mock_network_create(creds, '1234', 'fake_admin_net') self._mock_subnet_create(creds, '1234', 'fake_admin_subnet') self._mock_router_create('1234', 'fake_admin_router') router_interface_mock = self.patch( 'tempest.lib.services.network.routers_client.RoutersClient.' 'add_router_interface') self._mock_list_roles('123456', 'admin') admin_creds = creds.get_admin_creds() router_interface_mock.assert_called_once_with('1234', subnet_id='1234') network = admin_creds.network subnet = admin_creds.subnet router = admin_creds.router self.assertEqual(network['id'], '1234') self.assertEqual(network['name'], 'fake_admin_net') self.assertEqual(subnet['id'], '1234') self.assertEqual(subnet['name'], 'fake_admin_subnet') self.assertEqual(router['id'], '1234') self.assertEqual(router['name'], 'fake_admin_router') @mock.patch('tempest.lib.common.rest_client.RestClient') def test_no_network_resources(self, MockRestClient): net_dict = { 'network': False, 'router': False, 'subnet': False, 'dhcp': False, } creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, network_resources=net_dict, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') net = mock.patch.object(creds.networks_admin_client, 'delete_network') net_mock = net.start() subnet = mock.patch.object(creds.subnets_admin_client, 'delete_subnet') subnet_mock = subnet.start() router = mock.patch.object(creds.routers_admin_client, 'delete_router') router_mock = router.start() primary_creds = creds.get_primary_creds() self.assertEqual(net_mock.mock_calls, []) self.assertEqual(subnet_mock.mock_calls, []) self.assertEqual(router_mock.mock_calls, []) network = primary_creds.network subnet = primary_creds.subnet router = primary_creds.router self.assertIsNone(network) self.assertIsNone(subnet) self.assertIsNone(router) @mock.patch('tempest.lib.common.rest_client.RestClient') def test_router_without_network(self, MockRestClient): net_dict = { 'network': False, 'router': True, 'subnet': False, 'dhcp': False, } creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, network_resources=net_dict, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self.assertRaises(lib_exc.InvalidConfiguration, creds.get_primary_creds) @mock.patch('tempest.lib.common.rest_client.RestClient') def test_subnet_without_network(self, MockRestClient): net_dict = { 'network': False, 'router': False, 'subnet': True, 'dhcp': False, } creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, network_resources=net_dict, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self.assertRaises(lib_exc.InvalidConfiguration, creds.get_primary_creds) @mock.patch('tempest.lib.common.rest_client.RestClient') def test_dhcp_without_subnet(self, MockRestClient): net_dict = { 'network': False, 'router': False, 'subnet': False, 'dhcp': True, } creds = dynamic_creds.DynamicCredentialProvider( neutron_available=True, project_network_cidr='10.100.0.0/16', project_network_mask_bits=28, network_resources=net_dict, **self.fixed_params) self._mock_assign_user_role() self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self.assertRaises(lib_exc.InvalidConfiguration, creds.get_primary_creds) class TestDynamicCredentialProviderV3(TestDynamicCredentialProvider): fixed_params = {'name': 'test class', 'identity_version': 'v3', 'admin_role': 'admin', 'identity_uri': 'fake_uri'} token_client = v3_token_client iden_client = v3_iden_client roles_client = v3_roles_client tenants_client = v3_projects_client users_client = v3_users_client token_client_class = token_client.V3TokenClient fake_response = fake_identity._fake_v3_response tenants_client_class = tenants_client.ProjectsClient delete_tenant = 'delete_project' def setUp(self): super(TestDynamicCredentialProviderV3, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.useFixture(fixtures.MockPatchObject( domains_client.DomainsClient, 'list_domains', return_value=dict(domains=[dict(id='default', name='Default')]))) self.patchobject(self.roles_client.RolesClient, 'create_user_role_on_domain') def _mock_list_ec2_credentials(self, user_id, tenant_id): pass def _mock_tenant_create(self, id, name): project_fix = self.useFixture(fixtures.MockPatchObject( self.tenants_client.ProjectsClient, 'create_project', return_value=(rest_client.ResponseBody (200, {'project': {'id': id, 'name': name}})))) return project_fix @mock.patch('tempest.lib.common.rest_client.RestClient') def test_member_role_creation_with_duplicate(self, rest_client_mock): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) creds.creds_client = mock.MagicMock() creds.creds_client.create_user_role.side_effect = lib_exc.Conflict with mock.patch('tempest.lib.common.dynamic_creds.LOG') as log_mock: creds._create_creds() log_mock.warning.assert_called_once_with( "Member role already exists, ignoring conflict.") creds.creds_client.assign_user_role.assert_called_once_with( mock.ANY, mock.ANY, 'Member') tempest-17.2.0/tempest/tests/lib/common/__init__.py0000666000175100017510000000000013207044712022270 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/common/test_http.py0000666000175100017510000000536113207044712022566 0ustar zuulzuul00000000000000# 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 http from tempest.tests import base class TestClosingHttp(base.TestCase): def setUp(self): super(TestClosingHttp, self).setUp() self.cert_none = "CERT_NONE" self.cert_location = "/etc/ssl/certs/ca-certificates.crt" def test_constructor_invalid_ca_certs_and_timeout(self): connection = http.ClosingHttp( disable_ssl_certificate_validation=False, ca_certs=None, timeout=None) for attr in ('cert_reqs', 'ca_certs', 'timeout'): self.assertNotIn(attr, connection.connection_pool_kw) def test_constructor_valid_ca_certs(self): cert_required = 'CERT_REQUIRED' connection = http.ClosingHttp( disable_ssl_certificate_validation=False, ca_certs=self.cert_location, timeout=None) self.assertEqual(cert_required, connection.connection_pool_kw['cert_reqs']) self.assertEqual(self.cert_location, connection.connection_pool_kw['ca_certs']) self.assertNotIn('timeout', connection.connection_pool_kw) def test_constructor_ssl_cert_validation_disabled(self): connection = http.ClosingHttp( disable_ssl_certificate_validation=True, ca_certs=None, timeout=30) self.assertEqual(self.cert_none, connection.connection_pool_kw['cert_reqs']) self.assertEqual(30, connection.connection_pool_kw['timeout']) self.assertNotIn('ca_certs', connection.connection_pool_kw) def test_constructor_ssl_cert_validation_disabled_and_ca_certs(self): connection = http.ClosingHttp( disable_ssl_certificate_validation=True, ca_certs=self.cert_location, timeout=None) self.assertNotIn('timeout', connection.connection_pool_kw) self.assertEqual(self.cert_none, connection.connection_pool_kw['cert_reqs']) self.assertNotIn('ca_certs', connection.connection_pool_kw) tempest-17.2.0/tempest/tests/lib/common/test_rest_client.py0000666000175100017510000012000613207044712024114 0ustar zuulzuul00000000000000# 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 fixtures import jsonschema import six from tempest.lib.common import http from tempest.lib.common import rest_client from tempest.lib import exceptions from tempest.tests 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.patchobject(http.ClosingHttp, 'request', self.fake_http.request) self.useFixture(fixtures.MockPatchObject(self.rest_client, '_log_request')) class TestRestClientHTTPMethods(BaseRestClientTestClass): def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestRestClientHTTPMethods, self).setUp() self.useFixture(fixtures.MockPatchObject(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(fixtures.MockPatchObject(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): def _verify_headers(self, resp): 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.header_value = 'application/json' 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(fixtures.MockPatchObject(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(fixtures.MockPatchObject(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(fixtures.MockPatchObject(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) def test_parse_empty_list(self): empty_list = [] body = self.rest_client._parse_resp(json.dumps(empty_list)) self.assertEqual(empty_list, 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 = fake_http.fake_http_response(headers=resp_dict, status=int(r_code), body=json.dumps(resp_body)) data = { "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_412(self): self._test_error_checker(exceptions.PreconditionFailed, self.set_data("412")) 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, 412, 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 TestRateLimiting(BaseRestClientTestClass): def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestRateLimiting, self).setUp() def test__get_retry_after_delay_with_integer(self): resp = {'retry-after': '123'} self.assertEqual(123, self.rest_client._get_retry_after_delay(resp)) def test__get_retry_after_delay_with_http_date(self): resp = { 'date': 'Mon, 4 Apr 2016 21:56:23 GMT', 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT', } self.assertEqual(123, self.rest_client._get_retry_after_delay(resp)) def test__get_retry_after_delay_of_zero_with_integer(self): resp = {'retry-after': '0'} self.assertEqual(1, self.rest_client._get_retry_after_delay(resp)) def test__get_retry_after_delay_of_zero_with_http_date(self): resp = { 'date': 'Mon, 4 Apr 2016 21:56:23 GMT', 'retry-after': 'Mon, 4 Apr 2016 21:56:23 GMT', } self.assertEqual(1, self.rest_client._get_retry_after_delay(resp)) def test__get_retry_after_delay_with_missing_date_header(self): resp = { 'retry-after': 'Mon, 4 Apr 2016 21:58:26 GMT', } self.assertRaises(ValueError, self.rest_client._get_retry_after_delay, resp) def test__get_retry_after_delay_with_invalid_http_date(self): resp = { 'retry-after': 'Mon, 4 AAA 2016 21:58:26 GMT', 'date': 'Mon, 4 Apr 2016 21:56:23 GMT', } self.assertRaises(ValueError, self.rest_client._get_retry_after_delay, resp) def test__get_retry_after_delay_with_missing_retry_after_header(self): self.assertRaises(ValueError, self.rest_client._get_retry_after_delay, {}) def test_is_absolute_limit_gives_false_with_retry_after(self): resp = {'retry-after': 123} # is_absolute_limit() requires the overLimit body to be unwrapped resp_body = self.rest_client._parse_resp("""{ "overLimit": { "message": "" } }""") self.assertFalse(self.rest_client.is_absolute_limit(resp, resp_body)) 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, 'name': 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, 'name': 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) def test_non_success_read_code_as_string(self): expected_code = 202 read_code = '202' self.assertRaises(TypeError, self.rest_client.expected_success, expected_code, read_code) def test_non_success_read_code_as_list(self): expected_code = 202 read_code = [202] self.assertRaises(TypeError, self.rest_client.expected_success, expected_code, read_code) def test_non_success_expected_code_as_non_int(self): expected_code = ['201', 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 fixtures.MockPatchObject(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']) tempest-17.2.0/tempest/tests/lib/common/test_api_version_utils.py0000666000175100017510000002030613207044712025341 0ustar zuulzuul00000000000000# 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 six import testtools from tempest.lib.common import api_version_utils from tempest.lib import exceptions from tempest.tests import base class TestVersionSkipLogic(base.TestCase): def _test_version(self, test_min_version, test_max_version, cfg_min_version, cfg_max_version, expected_skip=False): try: api_version_utils.check_skip_with_microversion(test_min_version, test_max_version, cfg_min_version, cfg_max_version) except testtools.TestCase.skipException as e: if not expected_skip: raise testtools.TestCase.failureException(six.text_type(e)) def test_version_min_in_range(self): self._test_version('2.2', '2.10', '2.1', '2.7') def test_version_max_in_range(self): self._test_version('2.1', '2.3', '2.2', '2.7') def test_version_cfg_in_range(self): self._test_version('2.2', '2.9', '2.3', '2.7') def test_version_equal(self): self._test_version('2.2', '2.2', '2.2', '2.2') def test_version_below_cfg_min(self): self._test_version('2.2', '2.4', '2.5', '2.7', expected_skip=True) def test_version_above_cfg_max(self): self._test_version('2.8', '2.9', '2.3', '2.7', expected_skip=True) def test_version_min_greater_than_max(self): self.assertRaises(exceptions.InvalidAPIVersionRange, self._test_version, '2.8', '2.7', '2.3', '2.7') def test_cfg_version_min_greater_than_max(self): self.assertRaises(exceptions.InvalidAPIVersionRange, self._test_version, '2.2', '2.7', '2.9', '2.7') class TestSelectRequestMicroversion(base.TestCase): def _test_request_version(self, test_min_version, cfg_min_version, expected_version): selected_version = api_version_utils.select_request_microversion( test_min_version, cfg_min_version) self.assertEqual(expected_version, selected_version) def test_cfg_min_version_greater(self): self._test_request_version('2.1', '2.3', expected_version='2.3') def test_class_min_version_greater(self): self._test_request_version('2.5', '2.3', expected_version='2.5') def test_cfg_min_version_none(self): self._test_request_version('2.5', None, expected_version='2.5') def test_class_min_version_none(self): self._test_request_version(None, '2.3', expected_version='2.3') def test_both_min_version_none(self): self._test_request_version(None, None, expected_version=None) def test_both_min_version_equal(self): self._test_request_version('2.3', '2.3', expected_version='2.3') class TestMicroversionHeaderMatches(base.TestCase): def test_header_matches(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {microversion_header_name: request_microversion} api_version_utils.assert_version_header_matches_request( microversion_header_name, request_microversion, test_response) def test_header_does_not_match(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {microversion_header_name: '2.2'} self.assertRaises( exceptions.InvalidHTTPResponseHeader, api_version_utils.assert_version_header_matches_request, microversion_header_name, request_microversion, test_response) def test_header_not_present(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {} self.assertRaises( exceptions.InvalidHTTPResponseHeader, api_version_utils.assert_version_header_matches_request, microversion_header_name, request_microversion, test_response) def test_compare_versions_less_than(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.2' test_response = {microversion_header_name: '2.1'} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "lt")) def test_compare_versions_less_than_equal(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.2' test_response = {microversion_header_name: '2.1'} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "le")) def test_compare_versions_greater_than_equal(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {microversion_header_name: '2.2'} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "ge")) def test_compare_versions_greater_than(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {microversion_header_name: '2.2'} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "gt")) def test_compare_versions_equal(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.11' test_response = {microversion_header_name: '2.1'} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "eq")) def test_compare_versions_not_equal(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {microversion_header_name: '2.1'} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "ne")) def test_compare_versions_with_name_in_microversion(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = 'volume 3.1' test_response = {microversion_header_name: 'volume 3.1'} self.assertTrue( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "eq")) def test_compare_versions_invalid_operation(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {microversion_header_name: '2.1'} self.assertRaises( exceptions.InvalidParam, api_version_utils.compare_version_header_to_response, microversion_header_name, request_microversion, test_response, "foo") def test_compare_versions_header_not_present(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' test_response = {} self.assertFalse( api_version_utils.compare_version_header_to_response( microversion_header_name, request_microversion, test_response, "eq")) tempest-17.2.0/tempest/tests/lib/__init__.py0000666000175100017510000000000013207044712021000 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/test_base.py0000666000175100017510000000367113207044712021233 0ustar zuulzuul00000000000000# 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.""" tempest-17.2.0/tempest/tests/lib/services/0000775000175100017510000000000013207045130020515 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/0000775000175100017510000000000013207045130021577 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/v1/0000775000175100017510000000000013207045130022125 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/v1/__init__.py0000666000175100017510000000000013207044712024233 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/v1/test_image_members_client.py0000666000175100017510000000637213207044712027707 0ustar zuulzuul00000000000000# 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.services.image.v1 import image_members_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestImageMembersClient(base.BaseServiceTest): FAKE_LIST_IMAGE_MEMBERS = { "members": [ { "created_at": "2013-10-07T17:58:03Z", "image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe", "member_id": "123456789", "status": "pending", "updated_at": "2013-10-07T17:58:03Z" }, { "created_at": "2013-10-07T17:58:55Z", "image_id": "dbc999e3-c52f-4200-bedd-3b18fe7f87fe", "member_id": "987654321", "status": "accepted", "updated_at": "2013-10-08T12:08:55Z" } ] } def setUp(self): super(TestImageMembersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = image_members_client.ImageMembersClient(fake_auth, 'image', 'regionOne') def _test_list_image_members(self, bytes_body=False): self.check_service_client_function( self.client.list_image_members, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_IMAGE_MEMBERS, bytes_body, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e") def _test_create_image_member(self, bytes_body=False): self.check_service_client_function( self.client.create_image_member, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e", member_id="8989447062e04a818baf9e073fd04fa7", status=204) def test_list_image_members_with_str_body(self): self._test_list_image_members() def test_list_image_members_with_bytes_body(self): self._test_list_image_members(bytes_body=True) def test_create_image_member_with_str_body(self): self._test_create_image_member() def test_create_image_member_with_bytes_body(self): self._test_create_image_member(bytes_body=True) def test_delete_image_member(self): self.check_service_client_function( self.client.delete_image_member, 'tempest.lib.common.rest_client.RestClient.delete', {}, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e", member_id="8989447062e04a818baf9e073fd04fa7", status=204) tempest-17.2.0/tempest/tests/lib/services/image/__init__.py0000666000175100017510000000000013207044712023705 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/v2/0000775000175100017510000000000013207045130022126 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/v2/test_namespaces_client.py0000666000175100017510000000702313207044712027225 0ustar zuulzuul00000000000000# 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.services.image.v2 import namespaces_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestNamespacesClient(base.BaseServiceTest): FAKE_CREATE_SHOW_NAMESPACE = { "namespace": "OS::Compute::Hypervisor", "visibility": "public", "description": "Tempest", "display_name": u"\u2740(*\xb4\u25e1`*)\u2740", "protected": True } FAKE_UPDATE_NAMESPACE = { "namespace": "OS::Compute::Hypervisor", "visibility": "public", "description": "Tempest", "display_name": u"\u2740(*\xb4\u25e2`*)\u2740", "protected": True } def setUp(self): super(TestNamespacesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = namespaces_client.NamespacesClient(fake_auth, 'image', 'regionOne') def _test_show_namespace(self, bytes_body=False): self.check_service_client_function( self.client.show_namespace, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CREATE_SHOW_NAMESPACE, bytes_body, namespace="OS::Compute::Hypervisor") def _test_create_namespace(self, bytes_body=False): self.check_service_client_function( self.client.create_namespace, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SHOW_NAMESPACE, bytes_body, namespace="OS::Compute::Hypervisor", visibility="public", description="Tempest", display_name=u"\u2740(*\xb4\u25e1`*)\u2740", protected=True, status=201) def _test_update_namespace(self, bytes_body=False): self.check_service_client_function( self.client.update_namespace, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_NAMESPACE, bytes_body, namespace="OS::Compute::Hypervisor", display_name=u"\u2740(*\xb4\u25e2`*)\u2740", protected=True) def test_show_namespace_with_str_body(self): self._test_show_namespace() def test_show_namespace_with_bytes_body(self): self._test_show_namespace(bytes_body=True) def test_create_namespace_with_str_body(self): self._test_create_namespace() def test_create_namespace_with_bytes_body(self): self._test_create_namespace(bytes_body=True) def test_delete_namespace(self): self.check_service_client_function( self.client.delete_namespace, 'tempest.lib.common.rest_client.RestClient.delete', {}, namespace="OS::Compute::Hypervisor", status=204) def test_update_namespace_with_str_body(self): self._test_update_namespace() def test_update_namespace_with_bytes_body(self): self._test_update_namespace(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/image/v2/test_versions_client.py0000666000175100017510000000632213207044712026757 0ustar zuulzuul00000000000000# Copyright 2017 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.image.v2 import versions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestVersionsClient(base.BaseServiceTest): FAKE_VERSIONS_INFO = { "versions": [ { "status": "CURRENT", "id": "v2.5", "links": [ {"href": "https://10.220.1.21:9292/v2/", "rel": "self"} ] }, { "status": "SUPPORTED", "id": "v2.4", "links": [ {"href": "https://10.220.1.21:9292/v2/", "rel": "self"} ] }, { "status": "SUPPORTED", "id": "v2.3", "links": [ {"href": "https://10.220.1.21:9292/v2/", "rel": "self"} ] }, { "status": "SUPPORTED", "id": "v2.2", "links": [ {"href": "https://10.220.1.21:9292/v2/", "rel": "self"} ] }, { "status": "SUPPORTED", "id": "v2.1", "links": [ {"href": "https://10.220.1.21:9292/v2/", "rel": "self"} ] }, { "status": "SUPPORTED", "id": "v2.0", "links": [ {"href": "https://10.220.1.21:9292/v2/", "rel": "self"} ] }, { "status": "DEPRECATED", "id": "v1.1", "links": [ {"href": "https://10.220.1.21:9292/v1/", "rel": "self"} ] }, { "status": "DEPRECATED", "id": "v1.0", "links": [ {"href": "https://10.220.1.21:9292/v1/", "rel": "self"} ] } ] } def setUp(self): super(TestVersionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = versions_client.VersionsClient(fake_auth, 'image', 'regionOne') def _test_list_versions(self, bytes_body=False): self.check_service_client_function( self.client.list_versions, 'tempest.lib.common.rest_client.RestClient.raw_request', self.FAKE_VERSIONS_INFO, bytes_body, 300) def test_list_versions_with_str_body(self): self._test_list_versions() def test_list_versions_with_bytes_body(self): self._test_list_versions(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/image/v2/test_namespace_tags_client.py0000666000175100017510000001111513207044712030055 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. 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.image.v2 import namespace_tags_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestNamespaceTagsClient(base.BaseServiceTest): FAKE_CREATE_SHOW_TAGS = { "created_at": "2015-05-09T01:12:31Z", "name": "added-sample-tag", "updated_at": "2015-05-09T01:12:31Z" } FAKE_LIST_TAGS = { "tags": [ { "name": "sample-tag1" }, { "name": "sample-tag2" }, { "name": "sample-tag3" } ] } FAKE_UPDATE_TAGS = {"name": "new-tag-name"} def setUp(self): super(TestNamespaceTagsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = namespace_tags_client.NamespaceTagsClient( fake_auth, 'image', 'regionOne') def _test_create_namespace_tags(self, bytes_body=False): self.check_service_client_function( self.client.create_namespace_tags, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SHOW_TAGS, bytes_body, status=201, namespace="OS::Compute::Hypervisor", tags=[{"name": "sample-tag1"}, {"name": "sample-tag2"}, {"name": "sample-tag3"}]) def _test_list_namespace_tags(self, bytes_body=False): self.check_service_client_function( self.client.list_namespace_tags, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_TAGS, bytes_body, namespace="OS::Compute::Hypervisor") def _test_create_namespace_tag_definition(self, bytes_body=False): self.check_service_client_function( self.client.create_namespace_tag, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SHOW_TAGS, bytes_body, status=201, namespace="OS::Compute::Hypervisor", tag_name="added-sample-tag") def _test_show_namespace_tag_definition(self, bytes_body=False): self.check_service_client_function( self.client.show_namespace_tag, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CREATE_SHOW_TAGS, bytes_body, namespace="OS::Compute::Hypervisor", tag_name="added-sample-tag") def _test_update_namespace_tag_definition(self, bytes_body=False): self.check_service_client_function( self.client.update_namespace_tag, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_OBJECTS, bytes_body, namespace="OS::Compute::Hypervisor", tag_name="added-sample-tag", name="new-tag-name") def test_create_namespace_tags_with_str_body(self): self._test_create_namespace_tags() def test_create_namespace_tags_with_bytes_body(self): self._test_create_namespace_tags(bytes_body=True) def test_list_namespace_tags_with_str_body(self): self._test_list_namespace_tags() def test_list_namespace_tags_with_bytes_body(self): self._test_list_namespace_tags(bytes_body=True) def test_create_namespace_tag_with_str_body(self): self._test_create_namespace_tag_definition() def test_create_namespace_tag_with_bytes_body(self): self._test_create_namespace_tag_definition(bytes_body=True) def test_show_namespace_tag_with_str_body(self): self._test_show_namespace_tag_definition() def test_show_namespace_tag_with_bytes_body(self): self._test_show_namespace_tag_definition(bytes_body=True) def test_delete_all_namespace_tags(self): self.check_service_client_function( self.client.delete_namespace_tags, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=200, namespace="OS::Compute::Hypervisor") tempest-17.2.0/tempest/tests/lib/services/image/v2/test_namespace_properties_client.py0000666000175100017510000001613713207044712031324 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. 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.image.v2 import namespace_properties_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestNamespacePropertiesClient(base.BaseServiceTest): FAKE_CREATE_SHOW_NAMESPACE_PROPERTY = { "description": "property", "enum": ["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"], "name": "OS::Glance::Image", "title": "Hypervisor Type", "type": "string" } FAKE_LIST_NAMESPACE_PROPERTY = { "properties": { "hw_disk_bus": { "description": "property.", "enum": ["scsi", "virtio", "uml", "xen", "ide", "usb"], "title": "Disk Bus", "type": "string" }, "hw_machine_type": { "description": "desc.", "title": "Machine Type", "type": "string" }, "hw_qemu_guest_agent": { "description": "desc.", "enum": [ "yes", "no" ], "title": "QEMU Guest Agent", "type": "string" }, "hw_rng_model": { "default": "virtio", "description": "desc", "title": "Random Number Generator Device", "type": "string" }, "hw_scsi_model": { "default": "virtio-scsi", "description": "desc.", "title": "SCSI Model", "type": "string" }, "hw_video_model": { "description": "The video image driver used.", "enum": [ "vga", "cirrus", "vmvga", "xen", "qxl" ], "title": "Video Model", "type": "string" }, "hw_video_ram": { "description": "desc.", "title": "Max Video Ram", "type": "integer" }, "hw_vif_model": { "description": "desc.", "enum": ["e1000", "ne2k_pci", "pcnet", "rtl8139", "virtio", "e1000", "e1000e", "VirtualE1000", "VirtualE1000e", "VirtualPCNet32", "VirtualSriovEthernetCard", "VirtualVmxnet", "netfront", "ne2k_pci" ], "title": "Virtual Network Interface", "type": "string" }, "os_command_line": { "description": "desc.", "title": "Kernel Command Line", "type": "string" } } } FAKE_UPDATE_NAMESPACE_PROPERTY = { "description": "property", "enum": ["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"], "name": "OS::Glance::Image", "title": "update Hypervisor Type", "type": "string" } def setUp(self): super(TestNamespacePropertiesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = namespace_properties_client.NamespacePropertiesClient( fake_auth, 'image', 'regionOne') def _test_create_namespace_property(self, bytes_body=False): self.check_service_client_function( self.client.create_namespace_property, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SHOW_NAMESPACE_PROPERTY, bytes_body, status=201, namespace="OS::Compute::Hypervisor", title="Hypervisor Type", name="OS::Glance::Image", type="string", enum=["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"]) def _test_list_namespace_property(self, bytes_body=False): self.check_service_client_function( self.client.list_namespace_properties, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_NAMESPACE_PROPERTY, bytes_body, namespace="OS::Compute::Hypervisor") def _test_show_namespace_property(self, bytes_body=False): self.check_service_client_function( self.client.show_namespace_properties, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CREATE_SHOW_NAMESPACE_PROPERTY, bytes_body, namespace="OS::Compute::Hypervisor", property_name="OS::Glance::Image") def _test_update_namespace_property(self, bytes_body=False): self.check_service_client_function( self.client.update_namespace_properties, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_NAMESPACE_PROPERTY, bytes_body, namespace="OS::Compute::Hypervisor", property_name="OS::Glance::Image", title="update Hypervisor Type", type="string", enum=["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"], name="OS::Glance::Image") def test_create_namespace_property_with_str_body(self): self._test_create_namespace_property() def test_create_namespace_property_with_bytes_body(self): self._test_create_namespace_property(bytes_body=True) def test_list_namespace_property_with_str_body(self): self._test_list_namespace_property() def test_list_namespace_property_with_bytes_body(self): self._test_list_namespace_property(bytes_body=True) def test_show_namespace_property_with_str_body(self): self._test_show_namespace_property() def test_show_namespace_property_with_bytes_body(self): self._test_show_namespace_property(bytes_body=True) def test_update_namespace_property_with_str_body(self): self._test_update_namespace_property() def test_update_namespace_property_with_bytes_body(self): self._test_update_namespace_property(bytes_body=True) def test_delete_namespace(self): self.check_service_client_function( self.client.delete_namespace_property, 'tempest.lib.common.rest_client.RestClient.delete', {}, namespace="OS::Compute::Hypervisor", property_name="OS::Glance::Image", status=204) tempest-17.2.0/tempest/tests/lib/services/image/v2/test_images_client.py0000666000175100017510000002035413207044712026355 0ustar zuulzuul00000000000000# 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 six from tempest.lib.common.utils import data_utils from tempest.lib.services.image.v2 import images_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestImagesClient(base.BaseServiceTest): FAKE_CREATE_UPDATE_SHOW_IMAGE = { "id": "e485aab9-0907-4973-921c-bb6da8a8fcf8", "name": u"\u2740(*\xb4\u25e2`*)\u2740", "status": "active", "visibility": "public", "size": 2254249, "checksum": "2cec138d7dae2aa59038ef8c9aec2390", "tags": [ "fedora", "beefy" ], "created_at": "2012-08-10T19:23:50Z", "updated_at": "2012-08-12T11:11:33Z", "self": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea", "file": "/v2/images/da3b75d9-3f4a-40e7-8a2c-bfab23927dea/file", "schema": "/v2/schemas/image", "owner": None, "min_ram": None, "min_disk": None, "disk_format": None, "virtual_size": None, "container_format": None } FAKE_LIST_IMAGES = { "images": [ { "status": "active", "name": "cirros-0.3.2-x86_64-disk", "tags": [], "container_format": "bare", "created_at": "2014-11-07T17:07:06Z", "disk_format": "qcow2", "updated_at": "2014-11-07T17:19:09Z", "visibility": "public", "self": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27", "min_disk": 0, "protected": False, "id": "1bea47ed-f6a9-463b-b423-14b9cca9ad27", "file": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27/file", "checksum": "64d7c1cd2b6f60c92c14662941cb7913", "owner": "5ef70662f8b34079a6eddb8da9d75fe8", "size": 13167616, "min_ram": 0, "schema": "/v2/schemas/image", "virtual_size": None }, { "status": "active", "name": "F17-x86_64-cfntools", "tags": [], "container_format": "bare", "created_at": "2014-10-30T08:23:39Z", "disk_format": "qcow2", "updated_at": "2014-11-03T16:40:10Z", "visibility": "public", "self": "/v2/images/781b3762-9469-4cec-b58d-3349e5de4e9c", "min_disk": 0, "protected": False, "id": "781b3762-9469-4cec-b58d-3349e5de4e9c", "file": "/v2/images/781b3762-9469-4cec-b58d-3349e5de4e9c/file", "checksum": "afab0f79bac770d61d24b4d0560b5f70", "owner": "5ef70662f8b34079a6eddb8da9d75fe8", "size": 476704768, "min_ram": 0, "schema": "/v2/schemas/image", "virtual_size": None } ], "schema": "/v2/schemas/images", "first": "/v2/images" } FAKE_TAG_NAME = "fake tag" def setUp(self): super(TestImagesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = images_client.ImagesClient(fake_auth, 'image', 'regionOne') def _test_update_image(self, bytes_body=False): self.check_service_client_function( self.client.update_image, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_CREATE_UPDATE_SHOW_IMAGE, bytes_body, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", patch=[{"op": "add", "path": "/a/b/c", "value": ["foo", "bar"]}]) def _test_create_image(self, bytes_body=False): self.check_service_client_function( self.client.create_image, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_UPDATE_SHOW_IMAGE, bytes_body, name="virtual machine image", status=201) def _test_show_image(self, bytes_body=False): self.check_service_client_function( self.client.show_image, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CREATE_UPDATE_SHOW_IMAGE, bytes_body, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8") def _test_list_images(self, bytes_body=False): self.check_service_client_function( self.client.list_images, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_IMAGES, bytes_body, mock_args=['images']) def test_create_image_with_str_body(self): self._test_create_image() def test_create_image_with_bytes_body(self): self._test_create_image(bytes_body=True) def test_update_image_with_str_body(self): self._test_update_image() def test_update_image_with_bytes_body(self): self._test_update_image(bytes_body=True) def test_deactivate_image(self): self.check_service_client_function( self.client.deactivate_image, 'tempest.lib.common.rest_client.RestClient.post', {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204) def test_reactivate_image(self): self.check_service_client_function( self.client.reactivate_image, 'tempest.lib.common.rest_client.RestClient.post', {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204) def test_delete_image(self): self.check_service_client_function( self.client.delete_image, 'tempest.lib.common.rest_client.RestClient.delete', {}, image_id="e485aab9-0907-4973-921c-bb6da8a8fcf8", status=204) def test_store_image_file(self): data = six.BytesIO(data_utils.random_bytes()) self.check_service_client_function( self.client.store_image_file, 'tempest.lib.common.rest_client.RestClient.raw_request', {}, image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"], status=204, data=data) def test_show_image_file(self): # NOTE: The response for this API returns raw binary data, but an error # is thrown if random bytes are used for the resp body since # ``create_response`` then calls ``json.dumps``. self.check_service_client_function( self.client.show_image_file, 'tempest.lib.common.rest_client.RestClient.get', {}, resp_as_string=True, image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"], headers={'Content-Type': 'application/octet-stream'}, status=200) def test_add_image_tag(self): self.check_service_client_function( self.client.add_image_tag, 'tempest.lib.common.rest_client.RestClient.put', {}, image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"], status=204, tag=self.FAKE_TAG_NAME) def test_delete_image_tag(self): self.check_service_client_function( self.client.delete_image_tag, 'tempest.lib.common.rest_client.RestClient.delete', {}, image_id=self.FAKE_CREATE_UPDATE_SHOW_IMAGE["id"], status=204, tag=self.FAKE_TAG_NAME) def test_show_image_with_str_body(self): self._test_show_image() def test_show_image_with_bytes_body(self): self._test_show_image(bytes_body=True) def test_list_images_with_str_body(self): self._test_list_images() def test_list_images_with_bytes_body(self): self._test_list_images(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/image/v2/__init__.py0000666000175100017510000000000013207044712024234 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/image/v2/test_image_members_client.py0000666000175100017510000000725313207044712027707 0ustar zuulzuul00000000000000# 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.services.image.v2 import image_members_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestImageMembersClient(base.BaseServiceTest): FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER = { "status": "pending", "created_at": "2013-11-26T07:21:21Z", "updated_at": "2013-11-26T07:21:21Z", "image_id": "0ae74cc5-5147-4239-9ce2-b0c580f7067e", "member_id": "8989447062e04a818baf9e073fd04fa7", "schema": "/v2/schemas/member" } def setUp(self): super(TestImageMembersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = image_members_client.ImageMembersClient(fake_auth, 'image', 'regionOne') def _test_show_image_member(self, bytes_body=False): self.check_service_client_function( self.client.show_image_member, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER, bytes_body, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e", member_id="8989447062e04a818baf9e073fd04fa7") def _test_create_image_member(self, bytes_body=False): self.check_service_client_function( self.client.create_image_member, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER, bytes_body, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e", member_id="8989447062e04a818baf9e073fd04fa7") def _test_update_image_member(self, bytes_body=False): self.check_service_client_function( self.client.update_image_member, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_CREATE_SHOW_UPDATE_IMAGE_MEMBER, bytes_body, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e", member_id="8989447062e04a818baf9e073fd04fa7", schema="/v2/schemas/member2") def test_show_image_member_with_str_body(self): self._test_show_image_member() def test_show_image_member_with_bytes_body(self): self._test_show_image_member(bytes_body=True) def test_create_image_member_with_str_body(self): self._test_create_image_member() def test_create_image_member_with_bytes_body(self): self._test_create_image_member(bytes_body=True) def test_delete_image_member(self): self.check_service_client_function( self.client.delete_image_member, 'tempest.lib.common.rest_client.RestClient.delete', {}, image_id="0ae74cc5-5147-4239-9ce2-b0c580f7067e", member_id="8989447062e04a818baf9e073fd04fa7", status=204) def test_update_image_member_with_str_body(self): self._test_update_image_member() def test_update_image_member_with_bytes_body(self): self._test_update_image_member(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/image/v2/test_schemas_client.py0000666000175100017510000000717513207044712026541 0ustar zuulzuul00000000000000# 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.services.image.v2 import schemas_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSchemasClient(base.BaseServiceTest): FAKE_SHOW_SCHEMA = { "links": [ { "href": "{schema}", "rel": "describedby" } ], "name": "members", "properties": { "members": { "items": { "name": "member", "properties": { "created_at": { "description": ("Date and time of image member" " creation"), "type": "string" }, "image_id": { "description": "An identifier for the image", "pattern": ("^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}" "-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}" "-([0-9a-fA-F]){12}$"), "type": "string" }, "member_id": { "description": ("An identifier for the image" " member (tenantId)"), "type": "string" }, "schema": { "type": "string" }, "status": { "description": "The status of this image member", "enum": [ "pending", "accepted", "rejected" ], "type": "string" }, "updated_at": { "description": ("Date and time of last" " modification of image member"), "type": "string" } } }, "type": "array" }, "schema": { "type": "string" } } } def setUp(self): super(TestSchemasClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = schemas_client.SchemasClient(fake_auth, 'image', 'regionOne') def _test_show_schema(self, bytes_body=False): self.check_service_client_function( self.client.show_schema, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SHOW_SCHEMA, bytes_body, schema="member") def test_show_schema_with_str_body(self): self._test_show_schema() def test_show_schema_with_bytes_body(self): self._test_show_schema(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/image/v2/test_resource_types_client.py0000666000175100017510000000516013207044712030161 0ustar zuulzuul00000000000000# 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.services.image.v2 import resource_types_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestResouceTypesClient(base.BaseServiceTest): FAKE_LIST_RESOURCETYPES = { "resource_types": [ { "created_at": "2014-08-28T18:13:04Z", "name": "OS::Glance::Image", "updated_at": "2014-08-28T18:13:04Z" }, { "created_at": "2014-08-28T18:13:04Z", "name": "OS::Cinder::Volume", "updated_at": "2014-08-28T18:13:04Z" }, { "created_at": "2014-08-28T18:13:04Z", "name": "OS::Nova::Flavor", "updated_at": "2014-08-28T18:13:04Z" }, { "created_at": "2014-08-28T18:13:04Z", "name": "OS::Nova::Aggregate", "updated_at": "2014-08-28T18:13:04Z" }, { "created_at": "2014-08-28T18:13:04Z", "name": u"\u2740(*\xb4\u25e1`*)\u2740", "updated_at": "2014-08-28T18:13:04Z" } ] } def setUp(self): super(TestResouceTypesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = resource_types_client.ResourceTypesClient(fake_auth, 'image', 'regionOne') def _test_list_resouce_types(self, bytes_body=False): self.check_service_client_function( self.client.list_resource_types, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_RESOURCETYPES, bytes_body) def test_list_resouce_types_with_str_body(self): self._test_list_resouce_types() def test_list_resouce_types_with_bytes_body(self): self._test_list_resouce_types(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/image/v2/test_namespace_object_client.py0000666000175100017510000002032713207044712030372 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. 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.image.v2 import namespace_objects_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestNamespaceObjectClient(base.BaseServiceTest): FAKE_CREATE_SHOW_OBJECTS = { "created_at": "2016-09-19T18:20:56Z", "description": "You can configure the CPU limits.", "name": "CPU Limits", "properties": { "quota:cpu_period": { "description": "Specifies the enforcement interval", "maximum": 1000000, "minimum": 1000, "title": "Quota: CPU Period", "type": "integer" }, "quota:cpu_quota": { "description": "Specifies the maximum allowed bandwidth ", "title": "Quota: CPU Quota", "type": "integer" }, "quota:cpu_shares": { "description": "Specifies the proportional weighted share.", "title": "Quota: CPU Shares", "type": "integer" } }, "required": [], "schema": "/v2/schemas/metadefs/object", "self": "/v2/metadefs/namespaces/OS::Compute::Quota/objects/CPU", "updated_at": "2016-09-19T18:20:56Z" } FAKE_LIST_OBJECTS = { "objects": [ { "created_at": "2016-09-18T18:16:35Z", "description": "You can configure the CPU limits.", "name": "CPU Limits", "properties": { "quota:cpu_period": { "description": "Specifies the enforcement interval ", "maximum": 1000000, "minimum": 1000, "title": "Quota: CPU Period", "type": "integer" }, "quota:cpu_quota": { "description": "Specifies the maximum.", "title": "Quota: CPU Quota", "type": "integer" }, "quota:cpu_shares": { "description": " Desc.", "title": "Quota: CPU Shares", "type": "integer" } }, "required": [], "schema": "/v2/schemas/metadefs/object", "self": "/v2/metadefs/namespaces/OS::Compute::Quota/objects/CPU" }, { "created_at": "2016-09-18T18:16:35Z", "description": "Using disk I/O quotas.", "name": "Disk QoS", "properties": { "quota:disk_read_bytes_sec": { "description": "Sets disk I/O quota.", "title": "Quota: Disk read bytes / sec", "type": "integer" }, "quota:disk_read_iops_sec": { "description": "Sets disk I/O quota", "title": "Quota: Disk read IOPS / sec", "type": "integer" }, "quota:disk_total_bytes_sec": { "description": "Sets disk I/O quota.", "title": "Quota: Disk Total Bytes / sec", "type": "integer" }, "quota:disk_total_iops_sec": { "description": "Sets disk I/O quota.", "title": "Quota: Disk Total IOPS / sec", "type": "integer" }, "quota:disk_write_bytes_sec": { "description": "Sets disk I/O quota.", "title": "Quota: Disk Write Bytes / sec", "type": "integer" }, "quota:disk_write_iops_sec": { "description": "Sets disk I/O quota.", "title": "Quota: Disk Write IOPS / sec", "type": "integer" } }, "required": [], "schema": "/v2/schemas/metadefs/object", "self": "/v2/metadefs/namespaces/OS::Compute::Quota/objects/Disk QoS" }, ], "schema": "v2/schemas/metadefs/objects" } FAKE_UPDATE_OBJECTS = { "description": "You can configure the CPU limits.", "name": "CPU", "properties": { "quota:cpu_shares": { "description": "Specify.", "title": "Quota: CPU Shares", "type": "integer" } }, "required": [] } def setUp(self): super(TestNamespaceObjectClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = namespace_objects_client.NamespaceObjectsClient( fake_auth, 'image', 'regionOne') def _test_create_namespace_objects(self, bytes_body=False): self.check_service_client_function( self.client.create_namespace_object, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SHOW_OBJECTS, bytes_body, status=201, namespace="OS::Compute::Hypervisor", object_name="OS::Glance::Image") def _test_list_namespace_objects(self, bytes_body=False): self.check_service_client_function( self.client.list_namespace_objects, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_OBJECTS, bytes_body, namespace="OS::Compute::Hypervisor") def _test_show_namespace_objects(self, bytes_body=False): self.check_service_client_function( self.client.show_namespace_object, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CREATE_SHOW_OBJECTS, bytes_body, namespace="OS::Compute::Hypervisor", object_name="OS::Glance::Image") def _test_update_namespace_objects(self, bytes_body=False): self.check_service_client_function( self.client.update_namespace_object, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_OBJECTS, bytes_body, namespace="OS::Compute::Hypervisor", object_name="OS::Glance::Image", name="CPU") def test_create_namespace_object_with_str_body(self): self._test_create_namespace_objects() def test_create_namespace_object_with_bytes_body(self): self._test_create_namespace_objects(bytes_body=True) def test_list_namespace_object_with_str_body(self): self._test_list_namespace_objects() def test_list_namespace_object_with_bytes_body(self): self._test_list_namespace_objects(bytes_body=True) def test_show_namespace_object_with_str_body(self): self._test_show_namespace_objects() def test_show_namespace_object_with_bytes_body(self): self._test_show_namespace_objects(bytes_body=True) def test_update_namespace_object_with_str_body(self): self._test_update_namespace_objects() def test_update_namespace_object_with_bytes_body(self): self._test_update_namespace_objects(bytes_body=True) def test_delete_namespace_objects(self): self.check_service_client_function( self.client.delete_namespace_object, 'tempest.lib.common.rest_client.RestClient.delete', {}, namespace="OS::Compute::Hypervisor", object_name="OS::Glance::Image", status=204) tempest-17.2.0/tempest/tests/lib/services/network/0000775000175100017510000000000013207045130022206 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/network/test_service_providers_client.py0000666000175100017510000000301013207044712030713 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 service_providers_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestServiceProvidersClient(base.BaseServiceTest): def setUp(self): super(TestServiceProvidersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = service_providers_client.ServiceProvidersClient( fake_auth, 'network', 'regionOne') def _test_list_service_providers(self, bytes_body=False): self.check_service_client_function( self.client.list_service_providers, 'tempest.lib.common.rest_client.RestClient.get', {"service_providers": []}, bytes_body) def test_list_service_providers_with_str_body(self): self._test_list_service_providers() def test_list_service_providers_with_bytes_body(self): self._test_list_service_providers(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_quotas_client.py0000666000175100017510000000653613207044712026512 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.network import quotas_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQuotasClient(base.BaseServiceTest): FAKE_QUOTAS = { "quotas": [ { "floatingip": 50, "network": 15, "port": 50, "project_id": "bab7d5c60cd041a0a36f7c4b6e1dd978", "rbac_policy": -1, "router": 10, "security_group": 10, "security_group_rule": 100, "subnet": 10, "subnetpool": -1, "tenant_id": "bab7d5c60cd041a0a36f7c4b6e1dd978" } ] } FAKE_QUOTA_TENANT_ID = "bab7d5c60cd041a0a36f7c4b6e1dd978" def setUp(self): super(TestQuotasClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.quotas_client = quotas_client.QuotasClient( fake_auth, "network", "regionOne") def _test_list_quotas(self, bytes_body=False): self.check_service_client_function( self.quotas_client.list_quotas, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_QUOTAS, bytes_body, 200) def _test_show_quotas(self, bytes_body=False): self.check_service_client_function( self.quotas_client.show_quotas, "tempest.lib.common.rest_client.RestClient.get", {"quota": self.FAKE_QUOTAS["quotas"][0]}, bytes_body, 200, tenant_id=self.FAKE_QUOTA_TENANT_ID) def _test_update_quotas(self, bytes_body=False): self.check_service_client_function( self.quotas_client.update_quotas, "tempest.lib.common.rest_client.RestClient.put", {"quota": self.FAKE_QUOTAS["quotas"][0]}, bytes_body, 200, tenant_id=self.FAKE_QUOTA_TENANT_ID) def test_reset_quotas(self): self.check_service_client_function( self.quotas_client.reset_quotas, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, tenant_id=self.FAKE_QUOTA_TENANT_ID) def test_list_quotas_with_str_body(self): self._test_list_quotas() def test_list_quotas_with_bytes_body(self): self._test_list_quotas(bytes_body=True) def test_show_quotas_with_str_body(self): self._test_show_quotas() def test_show_quotas_with_bytes_body(self): self._test_show_quotas(bytes_body=True) def test_update_quotas_with_str_body(self): self._test_update_quotas() def test_update_quotas_with_bytes_body(self): self._test_update_quotas(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_base_network_client.py0000666000175100017510000000772613207044712027663 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from tempest.lib.services.network import base as base_network_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_http from tempest.tests.lib.services import base class TestBaseNetworkClient(base.BaseServiceTest): def setUp(self): super(TestBaseNetworkClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = base_network_client.BaseNetworkClient( fake_auth, 'compute', 'regionOne') self.mock_expected_success = mock.patch.object( self.client, 'expected_success').start() def _assert_empty(self, resp): self.assertEqual([], list(resp.keys())) @mock.patch('tempest.lib.common.rest_client.RestClient.post') def test_create_resource(self, mock_post): response = fake_http.fake_http_response(headers=None, status=201) mock_post.return_value = response, '{"baz": "qux"}' post_data = {'foo': 'bar'} resp = self.client.create_resource('/fake_url', post_data) self.assertEqual({'status': '201'}, resp.response) self.assertEqual("qux", resp["baz"]) mock_post.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}') self.mock_expected_success.assert_called_once_with( 201, 201) @mock.patch('tempest.lib.common.rest_client.RestClient.post') def test_create_resource_expect_different_values(self, mock_post): response = fake_http.fake_http_response(headers=None, status=200) mock_post.return_value = response, '{}' post_data = {'foo': 'bar'} resp = self.client.create_resource('/fake_url', post_data, expect_response_code=200, expect_empty_body=True) self.assertEqual({'status': '200'}, resp.response) self._assert_empty(resp) mock_post.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}') self.mock_expected_success.assert_called_once_with( 200, 200) @mock.patch('tempest.lib.common.rest_client.RestClient.put') def test_update_resource(self, mock_put): response = fake_http.fake_http_response(headers=None, status=200) mock_put.return_value = response, '{"baz": "qux"}' put_data = {'foo': 'bar'} resp = self.client.update_resource('/fake_url', put_data) self.assertEqual({'status': '200'}, resp.response) self.assertEqual("qux", resp["baz"]) mock_put.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}') self.mock_expected_success.assert_called_once_with( 200, 200) @mock.patch('tempest.lib.common.rest_client.RestClient.put') def test_update_resource_expect_different_values(self, mock_put): response = fake_http.fake_http_response(headers=None, status=201) mock_put.return_value = response, '{}' put_data = {'foo': 'bar'} resp = self.client.update_resource('/fake_url', put_data, expect_response_code=201, expect_empty_body=True) self.assertEqual({'status': '201'}, resp.response) self._assert_empty(resp) mock_put.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}') self.mock_expected_success.assert_called_once_with( 201, 201) tempest-17.2.0/tempest/tests/lib/services/network/test_metering_label_rules_client.py0000666000175100017510000001051013207044712031344 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.network import metering_label_rules_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestMeteringLabelRulesClient(base.BaseServiceTest): FAKE_METERING_LABEL_RULES = { "metering_label_rules": [ { "remote_ip_prefix": "20.0.0.0/24", "direction": "ingress", "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812", "id": "9536641a-7d14-4dc5-afaf-93a973ce0eb8", "excluded": False }, { "remote_ip_prefix": "10.0.0.0/24", "direction": "ingress", "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812", "id": "ffc6fd15-40de-4e7d-b617-34d3f7a93aec", "excluded": False } ] } FAKE_METERING_LABEL_RULE = { "remote_ip_prefix": "20.0.0.0/24", "direction": "ingress", "metering_label_id": "e131d186-b02d-4c0b-83d5-0c0725c4f812" } FAKE_METERING_LABEL_ID = "e131d186-b02d-4c0b-83d5-0c0725c4f812" FAKE_METERING_LABEL_RULE_ID = "9536641a-7d14-4dc5-afaf-93a973ce0eb8" def setUp(self): super(TestMeteringLabelRulesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.metering_label_rules_client = \ metering_label_rules_client.MeteringLabelRulesClient( fake_auth, "network", "regionOne") def _test_list_metering_label_rules(self, bytes_body=False): self.check_service_client_function( self.metering_label_rules_client.list_metering_label_rules, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_METERING_LABEL_RULES, bytes_body, 200) def _test_create_metering_label_rule(self, bytes_body=False): self.check_service_client_function( self.metering_label_rules_client.create_metering_label_rule, "tempest.lib.common.rest_client.RestClient.post", {"metering_label_rule": self.FAKE_METERING_LABEL_RULES[ "metering_label_rules"][0]}, bytes_body, 201, **self.FAKE_METERING_LABEL_RULE) def _test_show_metering_label_rule(self, bytes_body=False): self.check_service_client_function( self.metering_label_rules_client.show_metering_label_rule, "tempest.lib.common.rest_client.RestClient.get", {"metering_label_rule": self.FAKE_METERING_LABEL_RULES[ "metering_label_rules"][0]}, bytes_body, 200, metering_label_rule_id=self.FAKE_METERING_LABEL_RULE_ID) def test_delete_metering_label_rule(self): self.check_service_client_function( self.metering_label_rules_client.delete_metering_label_rule, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, metering_label_rule_id=self.FAKE_METERING_LABEL_RULE_ID) def test_list_metering_label_rules_with_str_body(self): self._test_list_metering_label_rules() def test_list_metering_label_rules_with_bytes_body(self): self._test_list_metering_label_rules(bytes_body=True) def test_create_metering_label_rule_with_str_body(self): self._test_create_metering_label_rule() def test_create_metering_label_rule_with_bytes_body(self): self._test_create_metering_label_rule(bytes_body=True) def test_show_metering_label_rule_with_str_body(self): self._test_show_metering_label_rule() def test_show_metering_label_rule_with_bytes_body(self): self._test_show_metering_label_rule(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_versions_client.py0000666000175100017510000000460413207044712027040 0ustar zuulzuul00000000000000# Copyright 2016 VMware, 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 copy from tempest.lib.services.network import versions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestNetworkVersionsClient(base.BaseServiceTest): FAKE_INIT_VERSION = { "version": { "id": "v2.0", "links": [ { "href": "http://openstack.example.com/v2.0/", "rel": "self" }, { "href": "http://docs.openstack.org/", "rel": "describedby", "type": "text/html" } ], "status": "CURRENT" } } 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.network+json;version=2.0" } ] def setUp(self): super(TestNetworkVersionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.versions_client = ( versions_client.NetworkVersionsClient (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_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) tempest-17.2.0/tempest/tests/lib/services/network/test_security_group_rules_client.py0000666000175100017510000001301413207044712031460 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import mock from oslo_serialization import jsonutils as json from tempest.lib.services.network import base as network_base from tempest.lib.services.network import security_group_rules_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSecurityGroupsClient(base.BaseServiceTest): FAKE_SEC_GROUP_RULE_ID = "3c0e45ff-adaf-4124-b083-bf390e5482ff" FAKE_SECURITY_GROUP_RULES = { "security_group_rules": [ { "direction": "egress", "ethertype": "IPv6", "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", "port_range_max": None, "port_range_min": None, "protocol": None, "remote_group_id": None, "remote_ip_prefix": None, "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", "project_id": "e4f50856753b4dc6afee5fa6b9b6c550", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550", "description": "" }, { "direction": "egress", "ethertype": "IPv4", "id": "93aa42e5-80db-4581-9391-3a608bd0e448", "port_range_max": None, "port_range_min": None, "protocol": None, "remote_group_id": None, "remote_ip_prefix": None, "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", "project_id": "e4f50856753b4dc6afee5fa6b9b6c550", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550", "description": "" } ] } FAKE_SECURITY_GROUP_RULE = copy.copy( FAKE_SECURITY_GROUP_RULES['security_group_rules'][0]) def setUp(self): super(TestSecurityGroupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = security_group_rules_client.SecurityGroupRulesClient( fake_auth, 'network', 'regionOne') def _test_list_security_group_rules(self, bytes_body=False): self.check_service_client_function( self.client.list_security_group_rules, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SECURITY_GROUP_RULES, bytes_body, mock_args='v2.0/security-group-rules') def _test_create_security_group_rule(self, bytes_body=False): kwargs = {'direction': 'egress', 'security_group_id': '85cc3048-abc3-43cc-89b3-377341426ac5', 'remote_ip_prefix': None} payload = json.dumps({"security_group_rule": kwargs}, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(network_base.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.create_security_group_rule, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SECURITY_GROUP_RULE, bytes_body, status=201, mock_args=['v2.0/security-group-rules', payload], **kwargs) def _test_show_security_group_rule(self, bytes_body=False): self.check_service_client_function( self.client.show_security_group_rule, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SECURITY_GROUP_RULE, bytes_body, security_group_rule_id=self.FAKE_SEC_GROUP_RULE_ID, mock_args='v2.0/security-group-rules/%s' % self.FAKE_SEC_GROUP_RULE_ID) def test_list_security_group_rules_with_str_body(self): self._test_list_security_group_rules() def test_list_security_group_rules_with_bytes_body(self): self._test_list_security_group_rules(bytes_body=True) 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_show_security_group_rule_with_str_body(self): self._test_show_security_group_rule() def test_show_security_group_rule_with_bytes_body(self): self._test_show_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=204, security_group_rule_id=self.FAKE_SEC_GROUP_RULE_ID, mock_args='v2.0/security-group-rules/%s' % self.FAKE_SEC_GROUP_RULE_ID) tempest-17.2.0/tempest/tests/lib/services/network/test_extensions_client.py0000666000175100017510000001714013207044712027366 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.network import extensions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestExtensionsClient(base.BaseServiceTest): FAKE_EXTENSIONS = { "extensions": [ { "updated": "2013-01-20T00:00:00-00:00", "name": "Neutron Service Type Management", "links": [], "alias": "service-type", "description": "API for retrieving service providers for" " Neutron advanced services" }, { "updated": "2012-10-05T10:00:00-00:00", "name": "security-group", "links": [], "alias": "security-group", "description": "The security groups extension." }, { "updated": "2013-02-07T10:00:00-00:00", "name": "L3 Agent Scheduler", "links": [], "alias": "l3_agent_scheduler", "description": "Schedule routers among l3 agents" }, { "updated": "2013-02-07T10:00:00-00:00", "name": "Loadbalancer Agent Scheduler", "links": [], "alias": "lbaas_agent_scheduler", "description": "Schedule pools among lbaas agents" }, { "updated": "2013-03-28T10:00:00-00:00", "name": "Neutron L3 Configurable external gateway mode", "links": [], "alias": "ext-gw-mode", "description": "Extension of the router abstraction for specifying whether" " SNAT should occur on the external gateway" }, { "updated": "2014-02-03T10:00:00-00:00", "name": "Port Binding", "links": [], "alias": "binding", "description": "Expose port bindings of a virtual port to" " external application" }, { "updated": "2012-09-07T10:00:00-00:00", "name": "Provider Network", "links": [], "alias": "provider", "description": "Expose mapping of virtual networks to" " physical networks" }, { "updated": "2013-02-03T10:00:00-00:00", "name": "agent", "links": [], "alias": "agent", "description": "The agent management extension." }, { "updated": "2012-07-29T10:00:00-00:00", "name": "Quota management support", "links": [], "alias": "quotas", "description": "Expose functions for quotas management per" " tenant" }, { "updated": "2013-02-07T10:00:00-00:00", "name": "DHCP Agent Scheduler", "links": [], "alias": "dhcp_agent_scheduler", "description": "Schedule networks among dhcp agents" }, { "updated": "2013-06-27T10:00:00-00:00", "name": "Multi Provider Network", "links": [], "alias": "multi-provider", "description": "Expose mapping of virtual networks to" " multiple physical networks" }, { "updated": "2013-01-14T10:00:00-00:00", "name": "Neutron external network", "links": [], "alias": "external-net", "description": "Adds external network attribute to network" " resource." }, { "updated": "2012-07-20T10:00:00-00:00", "name": "Neutron L3 Router", "links": [], "alias": "router", "description": "Router abstraction for basic L3 forwarding" " between L2 Neutron networks and access to external" " networks via a NAT gateway." }, { "updated": "2013-07-23T10:00:00-00:00", "name": "Allowed Address Pairs", "links": [], "alias": "allowed-address-pairs", "description": "Provides allowed address pairs" }, { "updated": "2013-03-17T12:00:00-00:00", "name": "Neutron Extra DHCP opts", "links": [], "alias": "extra_dhcp_opt", "description": "Extra options configuration for DHCP. For" " example PXE boot options to DHCP clients can be specified" " (e.g. tftp-server, server-ip-address, bootfile-name)" }, { "updated": "2012-10-07T10:00:00-00:00", "name": "LoadBalancing service", "links": [], "alias": "lbaas", "description": "Extension for LoadBalancing service" }, { "updated": "2013-02-01T10:00:00-00:00", "name": "Neutron Extra Route", "links": [], "alias": "extraroute", "description": "Extra routes configuration for L3 router" }, { "updated": "2016-01-24T10:00:00-00:00", "name": "Neutron Port Data Plane Status", "links": [], "alias": "data-plane-status", "description": "Status of the underlying data plane." } ] } FAKE_EXTENSION_ALIAS = "service-type" def setUp(self): super(TestExtensionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.extensions_client = extensions_client.ExtensionsClient( fake_auth, "network", "regionOne") def _test_list_extensions(self, bytes_body=False): self.check_service_client_function( self.extensions_client.list_extensions, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_EXTENSIONS, bytes_body, 200) def _test_show_extension(self, bytes_body=False): self.check_service_client_function( self.extensions_client.show_extension, "tempest.lib.common.rest_client.RestClient.get", {"extension": self.FAKE_EXTENSIONS["extensions"][0]}, bytes_body, 200, ext_alias=self.FAKE_EXTENSION_ALIAS) 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_with_str_body(self): self._test_show_extension() def test_show_extension_with_bytes_body(self): self._test_show_extension(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/__init__.py0000666000175100017510000000000013207044712024314 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/network/test_subnetpools_client.py0000666000175100017510000001326613207044712027551 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.lib.services.network import subnetpools_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSubnetsClient(base.BaseServiceTest): FAKE_SUBNETPOOLS = { "subnetpools": [ { "min_prefixlen": "64", "address_scope_id": None, "default_prefixlen": "64", "id": "03f761e6-eee0-43fc-a921-8acf64c14988", "max_prefixlen": "64", "name": "my-subnet-pool-ipv6", "default_quota": None, "is_default": False, "project_id": "9fadcee8aa7c40cdb2114fff7d569c08", "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08", "prefixes": [ "2001:db8:0:2::/64", "2001:db8::/63" ], "ip_version": 6, "shared": False, "description": "", "revision_number": 2 }, { "min_prefixlen": "24", "address_scope_id": None, "default_prefixlen": "25", "id": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68", "max_prefixlen": "30", "name": "my-subnet-pool-ipv4", "default_quota": None, "is_default": False, "project_id": "9fadcee8aa7c40cdb2114fff7d569c08", "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08", "prefixes": [ "10.10.0.0/21", "192.168.0.0/16" ], "ip_version": 4, "shared": False, "description": "", "revision_number": 2 } ] } FAKE_SUBNETPOOL_ID = "03f761e6-eee0-43fc-a921-8acf64c14988" def setUp(self): super(TestSubnetsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.subnetpools_client = subnetpools_client.SubnetpoolsClient( fake_auth, 'compute', 'regionOne') def _test_list_subnetpools(self, bytes_body=False): self.check_service_client_function( self.subnetpools_client.list_subnetpools, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SUBNETPOOLS, bytes_body, 200) def _test_create_subnetpool(self, bytes_body=False): self.check_service_client_function( self.subnetpools_client.create_subnetpool, 'tempest.lib.common.rest_client.RestClient.post', {'subnetpool': self.FAKE_SUBNETPOOLS['subnetpools'][1]}, bytes_body, 201, name="my-subnet-pool-ipv4", prefixes=["192.168.0.0/16", "10.10.0.0/21"]) def _test_show_subnetpool(self, bytes_body=False): self.check_service_client_function( self.subnetpools_client.show_subnetpool, 'tempest.lib.common.rest_client.RestClient.get', {'subnetpool': self.FAKE_SUBNETPOOLS['subnetpools'][0]}, bytes_body, 200, subnetpool_id=self.FAKE_SUBNETPOOL_ID) def _test_update_subnetpool(self, bytes_body=False): update_kwargs = { "name": "my-new-subnetpool-name", "prefixes": [ "2001:db8::/64", "2001:db8:0:1::/64", "2001:db8:0:2::/64" ], "min_prefixlen": 64, "default_prefixlen": 64, "max_prefixlen": 64 } resp_body = { 'subnetpool': copy.deepcopy( self.FAKE_SUBNETPOOLS['subnetpools'][0]) } resp_body['subnetpool'].update(update_kwargs) self.check_service_client_function( self.subnetpools_client.update_subnetpool, 'tempest.lib.common.rest_client.RestClient.put', resp_body, bytes_body, 200, subnetpool_id=self.FAKE_SUBNETPOOL_ID, **update_kwargs) def test_list_subnetpools_with_str_body(self): self._test_list_subnetpools() def test_list_subnetpools_with_bytes_body(self): self._test_list_subnetpools(bytes_body=True) def test_create_subnetpool_with_str_body(self): self._test_create_subnetpool() def test_create_subnetpool_with_bytes_body(self): self._test_create_subnetpool(bytes_body=True) def test_show_subnetpool_with_str_body(self): self._test_show_subnetpool() def test_show_subnetpool_with_bytes_body(self): self._test_show_subnetpool(bytes_body=True) def test_update_subnet_with_str_body(self): self._test_update_subnetpool() def test_update_subnet_with_bytes_body(self): self._test_update_subnetpool(bytes_body=True) def test_delete_subnetpool(self): self.check_service_client_function( self.subnetpools_client.delete_subnetpool, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, subnetpool_id=self.FAKE_SUBNETPOOL_ID) tempest-17.2.0/tempest/tests/lib/services/network/test_tags_client.py0000666000175100017510000001014313207044712026121 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.network import tags_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestTagsClient(base.BaseServiceTest): FAKE_TAGS = { "tags": [ "red", "blue" ] } FAKE_RESOURCE_TYPE = 'network' FAKE_RESOURCE_ID = '7a8f904b-c1ed-4446-a87d-60440c02934b' def setUp(self): super(TestTagsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = tags_client.TagsClient( fake_auth, 'network', 'regionOne') def _test_update_all_tags(self, bytes_body=False): self.check_service_client_function( self.client.update_all_tags, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_TAGS, bytes_body, resource_type=self.FAKE_RESOURCE_TYPE, resource_id=self.FAKE_RESOURCE_ID, tags=self.FAKE_TAGS) def _test_check_tag_existence(self, bytes_body=False): self.check_service_client_function( self.client.check_tag_existence, 'tempest.lib.common.rest_client.RestClient.get', {}, bytes_body, resource_type=self.FAKE_RESOURCE_TYPE, resource_id=self.FAKE_RESOURCE_ID, tag=self.FAKE_TAGS['tags'][0], status=204) def _test_create_tag(self, bytes_body=False): self.check_service_client_function( self.client.create_tag, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, resource_type=self.FAKE_RESOURCE_TYPE, resource_id=self.FAKE_RESOURCE_ID, tag=self.FAKE_TAGS['tags'][0], status=201) def _test_list_tags(self, bytes_body=False): self.check_service_client_function( self.client.list_tags, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_TAGS, bytes_body, resource_type=self.FAKE_RESOURCE_TYPE, resource_id=self.FAKE_RESOURCE_ID) def test_update_all_tags_with_str_body(self): self._test_update_all_tags() def test_update_all_tags_with_bytes_body(self): self._test_update_all_tags(bytes_body=True) def test_delete_all_tags(self): self.check_service_client_function( self.client.delete_all_tags, 'tempest.lib.common.rest_client.RestClient.delete', {}, resource_type=self.FAKE_RESOURCE_TYPE, resource_id=self.FAKE_RESOURCE_ID, status=204) def test_check_tag_existence_with_str_body(self): self._test_check_tag_existence() def test_check_tag_existence_with_bytes_body(self): self._test_check_tag_existence(bytes_body=True) def test_create_tag_with_str_body(self): self._test_create_tag() def test_create_tag_with_bytes_body(self): self._test_create_tag(bytes_body=True) def test_list_tags_with_str_body(self): self._test_list_tags() def test_list_tags_with_bytes_body(self): self._test_list_tags(bytes_body=True) def test_delete_tag(self): self.check_service_client_function( self.client.delete_tag, 'tempest.lib.common.rest_client.RestClient.delete', {}, resource_type=self.FAKE_RESOURCE_TYPE, resource_id=self.FAKE_RESOURCE_ID, tag=self.FAKE_TAGS['tags'][0], status=204) tempest-17.2.0/tempest/tests/lib/services/network/test_routers_client.py0000666000175100017510000000755113207044712026677 0ustar zuulzuul00000000000000# 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.services.network import routers_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestRoutersClient(base.BaseServiceTest): FAKE_CREATE_ROUTER = { "router": { "name": u'\u2740(*\xb4\u25e1`*)\u2740', "external_gateway_info": { "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", "enable_snat": True, "external_fixed_ips": [ { "subnet_id": "255.255.255.0", "ip": "192.168.10.1" } ] }, "admin_state_up": True, "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e" } } FAKE_UPDATE_ROUTER = { "router": { "name": u'\u2740(*\xb4\u25e1`*)\u2740', "external_gateway_info": { "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", "enable_snat": True, "external_fixed_ips": [ { "subnet_id": "255.255.255.0", "ip": "192.168.10.1" } ] }, "admin_state_up": False, "id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e" } } def setUp(self): super(TestRoutersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = routers_client.RoutersClient(fake_auth, 'network', 'regionOne') def _test_list_routers(self, bytes_body=False): self.check_service_client_function( self.client.list_routers, 'tempest.lib.common.rest_client.RestClient.get', {"routers": []}, bytes_body) def _test_create_router(self, bytes_body=False): self.check_service_client_function( self.client.create_router, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_ROUTER, bytes_body, name="another_router", admin_state_up="true", status=201) def _test_update_router(self, bytes_body=False): self.check_service_client_function( self.client.update_router, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_ROUTER, bytes_body, router_id="8604a0de-7f6b-409a-a47c-a1cc7bc77b2e", admin_state_up=False) def test_list_routers_with_str_body(self): self._test_list_routers() def test_list_routers_with_bytes_body(self): self._test_list_routers(bytes_body=True) def test_create_router_with_str_body(self): self._test_create_router() def test_create_router_with_bytes_body(self): self._test_create_router(bytes_body=True) def test_delete_router(self): self.check_service_client_function( self.client.delete_router, 'tempest.lib.common.rest_client.RestClient.delete', {}, router_id="1", status=204) def test_update_router_with_str_body(self): self._test_update_router() def test_update_router_with_bytes_body(self): self._test_update_router(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_metering_labels_client.py0000666000175100017510000001001213207044712030312 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.network import metering_labels_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestMeteringLabelsClient(base.BaseServiceTest): FAKE_METERING_LABELS = { "metering_labels": [ { "project_id": "45345b0ee1ea477fac0f541b2cb79cd4", "tenant_id": "45345b0ee1ea477fac0f541b2cb79cd4", "description": "label1 description", "name": "label1", "id": "a6700594-5b7a-4105-8bfe-723b346ce866", "shared": False }, { "project_id": "45345b0ee1ea477fac0f541b2cb79cd4", "tenant_id": "45345b0ee1ea477fac0f541b2cb79cd4", "description": "label2 description", "name": "label2", "id": "e131d186-b02d-4c0b-83d5-0c0725c4f812", "shared": False } ] } FAKE_METERING_LABEL_ID = "a6700594-5b7a-4105-8bfe-723b346ce866" def setUp(self): super(TestMeteringLabelsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.metering_labels_client = \ metering_labels_client.MeteringLabelsClient( fake_auth, "network", "regionOne") def _test_list_metering_labels(self, bytes_body=False): self.check_service_client_function( self.metering_labels_client.list_metering_labels, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_METERING_LABELS, bytes_body, 200) def _test_create_metering_label(self, bytes_body=False): self.check_service_client_function( self.metering_labels_client.create_metering_label, "tempest.lib.common.rest_client.RestClient.post", {"metering_label": self.FAKE_METERING_LABELS[ "metering_labels"][1]}, bytes_body, 201, name="label1", description="label1 description", shared=False) def _test_show_metering_label(self, bytes_body=False): self.check_service_client_function( self.metering_labels_client.show_metering_label, "tempest.lib.common.rest_client.RestClient.get", {"metering_label": self.FAKE_METERING_LABELS[ "metering_labels"][0]}, bytes_body, 200, metering_label_id=self.FAKE_METERING_LABEL_ID) def test_delete_metering_label(self): self.check_service_client_function( self.metering_labels_client.delete_metering_label, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, metering_label_id=self.FAKE_METERING_LABEL_ID) def test_list_metering_labels_with_str_body(self): self._test_list_metering_labels() def test_list_metering_labels_with_bytes_body(self): self._test_list_metering_labels(bytes_body=True) def test_create_metering_label_with_str_body(self): self._test_create_metering_label() def test_create_metering_label_with_bytes_body(self): self._test_create_metering_label(bytes_body=True) def test_show_metering_label_with_str_body(self): self._test_show_metering_label() def test_show_metering_label_with_bytes_body(self): self._test_show_metering_label(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_ports_client.py0000666000175100017510000001473713207044712026347 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.lib.services.network import ports_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestPortsClient(base.BaseServiceTest): FAKE_PORTS = { "ports": [ { "admin_state_up": True, "allowed_address_pairs": [], "data_plane_status": None, "description": "", "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", "device_owner": "network:router_gateway", "extra_dhcp_opts": [], "fixed_ips": [ { "ip_address": "172.24.4.2", "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062" } ], "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", "mac_address": "fa:16:3e:58:42:ed", "name": "", "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", "project_id": "", "security_groups": [], "status": "ACTIVE", "tenant_id": "" }, { "admin_state_up": True, "allowed_address_pairs": [], "data_plane_status": None, "description": "", "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824", "device_owner": "network:router_interface", "extra_dhcp_opts": [], "fixed_ips": [ { "ip_address": "10.0.0.1", "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17" } ], "id": "f71a6703-d6de-4be1-a91a-a570ede1d159", "mac_address": "fa:16:3e:bb:3c:e4", "name": "", "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2", "project_id": "d397de8a63f341818f198abb0966f6f3", "security_groups": [], "status": "ACTIVE", "tenant_id": "d397de8a63f341818f198abb0966f6f3" } ] } FAKE_PORT_ID = "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b" FAKE_PORT1 = { "admin_state_up": True, "name": "", "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3" } FAKE_PORT2 = { "admin_state_up": True, "name": "", "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2" } FAKE_PORTS_REQ = { "ports": [ FAKE_PORT1, FAKE_PORT2 ] } def setUp(self): super(TestPortsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.ports_client = ports_client.PortsClient( fake_auth, "network", "regionOne") def _test_list_ports(self, bytes_body=False): self.check_service_client_function( self.ports_client.list_ports, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_PORTS, bytes_body, 200) def _test_create_port(self, bytes_body=False): self.check_service_client_function( self.ports_client.create_port, "tempest.lib.common.rest_client.RestClient.post", {"port": self.FAKE_PORTS["ports"][0]}, bytes_body, 201, **self.FAKE_PORT1) def _test_create_bulk_ports(self, bytes_body=False): self.check_service_client_function( self.ports_client.create_bulk_ports, "tempest.lib.common.rest_client.RestClient.post", self.FAKE_PORTS, bytes_body, 201, ports=self.FAKE_PORTS_REQ) def _test_show_port(self, bytes_body=False): self.check_service_client_function( self.ports_client.show_port, "tempest.lib.common.rest_client.RestClient.get", {"port": self.FAKE_PORTS["ports"][0]}, bytes_body, 200, port_id=self.FAKE_PORT_ID) def _test_update_port(self, bytes_body=False): update_kwargs = { "admin_state_up": True, "device_id": "d90a13da-be41-461f-9f99-1dbcf438fdf2", "device_owner": "compute:nova", "name": "test-for-port-update" } resp_body = { "port": copy.deepcopy( self.FAKE_PORTS["ports"][0] ) } resp_body["port"].update(update_kwargs) self.check_service_client_function( self.ports_client.update_port, "tempest.lib.common.rest_client.RestClient.put", resp_body, bytes_body, 200, port_id=self.FAKE_PORT_ID, **update_kwargs) def test_delete_port(self): self.check_service_client_function( self.ports_client.delete_port, "tempest.lib.common.rest_client.RestClient.delete", {}, status=204, port_id=self.FAKE_PORT_ID) def test_list_ports_with_str_body(self): self._test_list_ports() def test_list_ports_with_bytes_body(self): self._test_list_ports(bytes_body=True) def test_create_port_with_str_body(self): self._test_create_port() def test_create_port_with_bytes_body(self): self._test_create_port(bytes_body=True) def test_create_bulk_port_with_str_body(self): self._test_create_bulk_ports() def test_create_bulk_port_with_bytes_body(self): self._test_create_bulk_ports(bytes_body=True) def test_show_port_with_str_body(self): self._test_show_port() def test_show_port_with_bytes_body(self): self._test_show_port(bytes_body=True) def test_update_port_with_str_body(self): self._test_update_port() def test_update_port_with_bytes_body(self): self._test_update_port(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_security_groups_client.py0000666000175100017510000001576713207044712030452 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import mock from oslo_serialization import jsonutils as json from tempest.lib.services.network import base as network_base from tempest.lib.services.network import security_groups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSecurityGroupsClient(base.BaseServiceTest): FAKE_SEC_GROUP_ID = "85cc3048-abc3-43cc-89b3-377341426ac5" FAKE_SECURITY_GROUPS = { "security_groups": [ { "description": "default", "id": FAKE_SEC_GROUP_ID, "name": "fake-security-group-name", "security_group_rules": [ { "direction": "egress", "ethertype": "IPv4", "id": "38ce2d8e-e8f1-48bd-83c2-d33cb9f50c3d", "port_range_max": None, "port_range_min": None, "protocol": None, "remote_group_id": None, "remote_ip_prefix": None, "security_group_id": FAKE_SEC_GROUP_ID, "project_id": "e4f50856753b4dc6afee5fa6b9b6c550", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550", "description": "" }, { "direction": "egress", "ethertype": "IPv6", "id": "565b9502-12de-4ffd-91e9-68885cff6ae1", "port_range_max": None, "port_range_min": None, "protocol": None, "remote_group_id": None, "remote_ip_prefix": None, "security_group_id": FAKE_SEC_GROUP_ID, "project_id": "e4f50856753b4dc6afee5fa6b9b6c550", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550", "description": "" } ], "project_id": "e4f50856753b4dc6afee5fa6b9b6c550", "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" } ] } FAKE_SECURITY_GROUP = { "security_group": copy.deepcopy( FAKE_SECURITY_GROUPS["security_groups"][0]) } def setUp(self): super(TestSecurityGroupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = security_groups_client.SecurityGroupsClient( fake_auth, 'network', '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', self.FAKE_SECURITY_GROUPS, bytes_body, mock_args='v2.0/security-groups') def _test_create_security_group(self, bytes_body=False): kwargs = {'name': 'fake-security-group-name'} payload = json.dumps({"security_group": kwargs}, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(network_base.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.create_security_group, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SECURITY_GROUP, bytes_body, status=201, mock_args=['v2.0/security-groups', payload], **kwargs) 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', self.FAKE_SECURITY_GROUP, bytes_body, security_group_id=self.FAKE_SEC_GROUP_ID, mock_args='v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID) def _test_update_security_group(self, bytes_body=False): kwargs = {'name': 'updated-security-group-name'} resp_body = copy.deepcopy(self.FAKE_SECURITY_GROUP) resp_body["security_group"]["name"] = 'updated-security-group-name' payload = json.dumps({'security_group': kwargs}, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(network_base.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.update_security_group, 'tempest.lib.common.rest_client.RestClient.put', resp_body, bytes_body, security_group_id=self.FAKE_SEC_GROUP_ID, mock_args=['v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID, payload], **kwargs) 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_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_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_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=204, security_group_id=self.FAKE_SEC_GROUP_ID, mock_args='v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID) tempest-17.2.0/tempest/tests/lib/services/network/test_subnets_client.py0000666000175100017510000002100713207044712026647 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.lib.services.network import subnets_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSubnetsClient(base.BaseServiceTest): FAKE_SUBNET = { "subnet": { "name": "", "enable_dhcp": True, "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", "segment_id": None, "project_id": "4fd44f30292945e481c7b8a0c8908869", "tenant_id": "4fd44f30292945e481c7b8a0c8908869", "dns_nameservers": [], "allocation_pools": [ { "start": "192.168.199.2", "end": "192.168.199.254" } ], "host_routes": [], "ip_version": 4, "gateway_ip": "192.168.199.1", "cidr": "192.168.199.0/24", "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126", "created_at": "2016-10-10T14:35:47Z", "description": "", "ipv6_address_mode": None, "ipv6_ra_mode": None, "revision_number": 2, "service_types": [], "subnetpool_id": None, "updated_at": "2016-10-10T14:35:47Z" } } FAKE_UPDATED_SUBNET = { "subnet": { "name": "my_subnet", "enable_dhcp": True, "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", "segment_id": None, "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "dns_nameservers": [], "allocation_pools": [ { "start": "10.0.0.2", "end": "10.0.0.254" } ], "host_routes": [], "ip_version": 4, "gateway_ip": "10.0.0.1", "cidr": "10.0.0.0/24", "id": "08eae331-0402-425a-923c-34f7cfe39c1b", "description": "" } } FAKE_SUBNETS = { "subnets": [ { "name": "private-subnet", "enable_dhcp": True, "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", "segment_id": None, "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", "dns_nameservers": [], "allocation_pools": [ { "start": "10.0.0.2", "end": "10.0.0.254" } ], "host_routes": [], "ip_version": 4, "gateway_ip": "10.0.0.1", "cidr": "10.0.0.0/24", "id": "08eae331-0402-425a-923c-34f7cfe39c1b", "created_at": "2016-10-10T14:35:34Z", "description": "", "ipv6_address_mode": None, "ipv6_ra_mode": None, "revision_number": 2, "service_types": [], "subnetpool_id": None, "updated_at": "2016-10-10T14:35:34Z" }, { "name": "my_subnet", "enable_dhcp": True, "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", "segment_id": None, "project_id": "4fd44f30292945e481c7b8a0c8908869", "tenant_id": "4fd44f30292945e481c7b8a0c8908869", "dns_nameservers": [], "allocation_pools": [ { "start": "192.0.0.2", "end": "192.255.255.254" } ], "host_routes": [], "ip_version": 4, "gateway_ip": "192.0.0.1", "cidr": "192.0.0.0/8", "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", "created_at": "2016-10-10T14:35:47Z", "description": "", "ipv6_address_mode": None, "ipv6_ra_mode": None, "revision_number": 2, "service_types": [], "subnetpool_id": None, "updated_at": "2016-10-10T14:35:47Z" } ] } FAKE_BULK_SUBNETS = copy.deepcopy(FAKE_SUBNETS) FAKE_SUBNET_ID = "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22" def setUp(self): super(TestSubnetsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.subnets_client = subnets_client.SubnetsClient( fake_auth, 'compute', 'regionOne') def _test_create_subnet(self, bytes_body=False): self.check_service_client_function( self.subnets_client.create_subnet, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SUBNET, bytes_body, 201, network_id=self.FAKE_NETWORK_ID, ip_version=4, cidr="192.168.199.0/24") def _test_update_subnet(self, bytes_body=False): self.check_service_client_function( self.subnets_client.update_subnet, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATED_SUBNET, bytes_body, 200, subnet_id=self.FAKE_SUBNET_ID, name="fake_updated_subnet_name") def _test_show_subnet(self, bytes_body=False): self.check_service_client_function( self.subnets_client.show_subnet, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SUBNET, bytes_body, 200, subnet_id=self.FAKE_SUBNET_ID) def _test_list_subnets(self, bytes_body=False): self.check_service_client_function( self.subnets_client.list_subnets, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SUBNETS, bytes_body, 200) def _test_create_bulk_subnets(self, bytes_body=False): kwargs = { "subnets": [ { "cidr": "192.168.199.0/24", "ip_version": 4, "network_id": "e6031bc2-901a-4c66-82da-f4c32ed89406" }, { "cidr": "10.56.4.0/22", "ip_version": 4, "network_id": "64239a54-dcc4-4b39-920b-b37c2144effa" } ] } self.check_service_client_function( self.subnets_client.create_bulk_subnets, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SUBNETS, bytes_body, 201, **kwargs) def test_create_subnet_with_str_body(self): self._test_create_subnet() def test_create_subnet_with_bytes_body(self): self._test_create_subnet(bytes_body=True) def test_update_subnet_with_str_body(self): self._test_update_subnet() def test_update_subnet_with_bytes_body(self): self._test_update_subnet(bytes_body=True) def test_show_subnet_with_str_body(self): self._test_show_subnet() def test_show_subnet_with_bytes_body(self): self._test_show_subnet(bytes_body=True) def test_list_subnets_with_str_body(self): self._test_list_subnets() def test_list_subnets_with_bytes_body(self): self._test_list_subnets(bytes_body=True) def test_delete_subnet(self): self.check_service_client_function( self.subnets_client.delete_subnet, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, subnet_id=self.FAKE_SUBNET_ID) def test_create_bulk_subnets_with_str_body(self): self._test_create_bulk_subnets() def test_create_bulk_subnets_with_bytes_body(self): self._test_create_bulk_subnets(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/network/test_floating_ips_client.py0000666000175100017510000001256413207044712027652 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.lib.services.network import floating_ips_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestFloatingIPsClient(base.BaseServiceTest): FAKE_FLOATING_IPS = { "floatingips": [ { "router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f", "description": "for test", "created_at": "2016-12-21T10:55:50Z", "updated_at": "2016-12-21T10:55:53Z", "revision_number": 1, "project_id": "4969c491a3c74ee4af974e6d800c62de", "tenant_id": "4969c491a3c74ee4af974e6d800c62de", "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57", "fixed_ip_address": "10.0.0.3", "floating_ip_address": "172.24.4.228", "port_id": "ce705c24-c1ef-408a-bda3-7bbd946164ab", "id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7", "status": "ACTIVE" }, { "router_id": None, "description": "for test", "created_at": "2016-12-21T11:55:50Z", "updated_at": "2016-12-21T11:55:53Z", "revision_number": 2, "project_id": "4969c491a3c74ee4af974e6d800c62de", "tenant_id": "4969c491a3c74ee4af974e6d800c62de", "floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57", "fixed_ip_address": None, "floating_ip_address": "172.24.4.227", "port_id": None, "id": "61cea855-49cb-4846-997d-801b70c71bdd", "status": "DOWN" } ] } FAKE_FLOATING_IP_ID = "2f245a7b-796b-4f26-9cf9-9e82d248fda7" def setUp(self): super(TestFloatingIPsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.floating_ips_client = floating_ips_client.FloatingIPsClient( fake_auth, "compute", "regionOne") def _test_list_floatingips(self, bytes_body=False): self.check_service_client_function( self.floating_ips_client.list_floatingips, "tempest.lib.common.rest_client.RestClient.get", self.FAKE_FLOATING_IPS, bytes_body, 200) def _test_create_floatingip(self, bytes_body=False): self.check_service_client_function( self.floating_ips_client.create_floatingip, "tempest.lib.common.rest_client.RestClient.post", {"floatingip": self.FAKE_FLOATING_IPS["floatingips"][1]}, bytes_body, 201, floating_network_id="172.24.4.228") def _test_show_floatingip(self, bytes_body=False): self.check_service_client_function( self.floating_ips_client.show_floatingip, "tempest.lib.common.rest_client.RestClient.get", {"floatingip": self.FAKE_FLOATING_IPS["floatingips"][0]}, bytes_body, 200, floatingip_id=self.FAKE_FLOATING_IP_ID) def _test_update_floatingip(self, bytes_body=False): update_kwargs = { "port_id": "fc861431-0e6c-4842-a0ed-e2363f9bc3a8" } resp_body = { "floatingip": copy.deepcopy( self.FAKE_FLOATING_IPS["floatingips"][0] ) } resp_body["floatingip"].update(update_kwargs) self.check_service_client_function( self.floating_ips_client.update_floatingip, "tempest.lib.common.rest_client.RestClient.put", resp_body, bytes_body, 200, floatingip_id=self.FAKE_FLOATING_IP_ID, **update_kwargs) def test_list_floatingips_with_str_body(self): self._test_list_floatingips() def test_list_floatingips_with_bytes_body(self): self._test_list_floatingips(bytes_body=True) def test_create_floatingip_with_str_body(self): self._test_create_floatingip() def test_create_floatingip_with_bytes_body(self): self._test_create_floatingip(bytes_body=True) def test_show_floatingips_with_str_body(self): self._test_show_floatingip() def test_show_floatingips_with_bytes_body(self): self._test_show_floatingip(bytes_body=True) def test_update_floatingip_with_str_body(self): self._test_update_floatingip() def test_update_floatingip_with_bytes_body(self): self._test_update_floatingip(bytes_body=True) def test_delete_floatingip(self): self.check_service_client_function( self.floating_ips_client.delete_floatingip, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, floatingip_id=self.FAKE_FLOATING_IP_ID) tempest-17.2.0/tempest/tests/lib/services/identity/0000775000175100017510000000000013207045130022346 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/identity/__init__.py0000666000175100017510000000000013207044712024454 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/identity/v3/0000775000175100017510000000000013207045130022676 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_oauth_token_client.py0000666000175100017510000001751513207044712030205 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures from tempest.lib.services.identity.v3 import oauth_token_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_http from tempest.tests.lib.services import base class TestOAUTHTokenClient(base.BaseServiceTest): FAKE_CREATE_REQUEST_TOKEN = { 'oauth_token': '29971f', 'oauth_token_secret': '238eb8', 'oauth_expires_at': '2013-09-11T06:07:51.501805Z' } FAKE_AUTHORIZE_REQUEST_TOKEN = { 'token': { 'oauth_verifier': '8171' } } FAKE_CREATE_ACCESS_TOKEN = { 'oauth_token': 'accd36', 'oauth_token_secret': 'aa47da', 'oauth_expires_at': '2013-09-11T06:07:51.501805Z' } FAKE_ACCESS_TOKEN_INFO = { 'access_token': { 'consumer_id': '7fea2d', 'id': '6be26a', 'expires_at': '2013-09-11T06:07:51.501805Z', 'links': { 'roles': 'http://example.com/identity/v3/' + 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles', 'self': 'http://example.com/identity/v3/' + 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a' }, 'project_id': 'b9fca3', 'authorizing_user_id': 'ce9e07' } } FAKE_LIST_ACCESS_TOKENS = { 'access_tokens': [ { 'consumer_id': '7fea2d', 'id': '6be26a', 'expires_at': '2013-09-11T06:07:51.501805Z', 'links': { 'roles': 'http://example.com/identity/v3/' + 'users/ce9e07/OS-OAUTH1/access_tokens/' + '6be26a/roles', 'self': 'http://example.com/identity/v3/' + 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a' }, 'project_id': 'b9fca3', 'authorizing_user_id': 'ce9e07' } ], 'links': { 'next': None, 'previous': None, 'self': 'http://example.com/identity/v3/' + 'users/ce9e07/OS-OAUTH1/access_tokens' } } FAKE_LIST_ACCESS_TOKEN_ROLES = { 'roles': [ { 'id': '26b860', 'domain_id': 'fake_domain', 'links': { 'self': 'http://example.com/identity/v3/' + 'roles/26b860' }, 'name': 'fake_role' } ], 'links': { 'next': None, 'previous': None, 'self': 'http://example.com/identity/v3/' + 'users/ce9e07/OS-OAUTH1/access_tokens/6be26a/roles' } } FAKE_ACCESS_TOKEN_ROLE_INFO = { 'role': { 'id': '26b860', 'domain_id': 'fake_domain', 'links': { 'self': 'http://example.com/identity/v3/' + 'roles/26b860' }, 'name': 'fake_role' } } def setUp(self): super(TestOAUTHTokenClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = oauth_token_client.OAUTHTokenClient(fake_auth, 'identity', 'regionOne') def _mock_token_response(self, body): temp_response = [key + '=' + value for key, value in body.items()] return '&'.join(temp_response) def _test_authorize_request_token(self, bytes_body=False): self.check_service_client_function( self.client.authorize_request_token, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_AUTHORIZE_REQUEST_TOKEN, bytes_body, request_token_id=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'], role_ids=['26b860'], status=200) def test_create_request_token(self): mock_resp = self._mock_token_response(self.FAKE_CREATE_REQUEST_TOKEN) resp = fake_http.fake_http_response(None, status=201), mock_resp self.useFixture(fixtures.MockPatch( 'tempest.lib.common.rest_client.RestClient.post', return_value=resp)) resp = self.client.create_request_token( consumer_key='12345', consumer_secret='23456', project_id='c8f58432c6f00162f04d3250f') self.assertEqual(self.FAKE_CREATE_REQUEST_TOKEN, resp) def test_authorize_token_request_with_str_body(self): self._test_authorize_request_token() def test_authorize_token_request_with_bytes_body(self): self._test_authorize_request_token(bytes_body=True) def test_create_access_token(self): mock_resp = self._mock_token_response(self.FAKE_CREATE_ACCESS_TOKEN) req_secret = self.FAKE_CREATE_REQUEST_TOKEN['oauth_token_secret'] resp = fake_http.fake_http_response(None, status=201), mock_resp self.useFixture(fixtures.MockPatch( 'tempest.lib.common.rest_client.RestClient.post', return_value=resp)) resp = self.client.create_access_token( consumer_key='12345', consumer_secret='23456', request_key=self.FAKE_CREATE_REQUEST_TOKEN['oauth_token'], request_secret=req_secret, oauth_verifier='8171') self.assertEqual(self.FAKE_CREATE_ACCESS_TOKEN, resp) def test_get_access_token(self): self.check_service_client_function( self.client.get_access_token, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ACCESS_TOKEN_INFO, user_id='ce9e07', access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'], status=200) def test_list_access_tokens(self): self.check_service_client_function( self.client.list_access_tokens, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ACCESS_TOKENS, user_id='ce9e07', status=200) def test_revoke_access_token(self): self.check_service_client_function( self.client.revoke_access_token, 'tempest.lib.common.rest_client.RestClient.delete', {}, user_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['consumer_id'], access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'], status=204) def test_list_access_token_roles(self): self.check_service_client_function( self.client.list_access_token_roles, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ACCESS_TOKEN_ROLES, user_id='ce9e07', access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'], status=200) def test_get_access_token_role(self): self.check_service_client_function( self.client.get_access_token_role, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ACCESS_TOKEN_ROLE_INFO, user_id='ce9e07', access_token_id=self.FAKE_ACCESS_TOKEN_INFO['access_token']['id'], role_id=self.FAKE_ACCESS_TOKEN_ROLE_INFO['role']['id'], status=200) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_regions_client.py0000666000175100017510000001010013207044712027312 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import regions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestRegionsClient(base.BaseServiceTest): FAKE_CREATE_REGION = { "region": { "description": "My subregion", "id": "RegionOneSubRegion", "parent_region_id": "RegionOne" } } FAKE_REGION_INFO = { "region": { "description": "My subregion 3", "id": "RegionThree", "links": { "self": "http://example.com/identity/v3/regions/RegionThree" }, "parent_region_id": "RegionOne" } } FAKE_LIST_REGIONS = { "links": { "next": None, "previous": None, "self": "http://example.com/identity/v3/regions" }, "regions": [ { "description": "", "id": "RegionOne", "links": { "self": "http://example.com/identity/v3/regions/RegionOne" }, "parent_region_id": None } ] } def setUp(self): super(TestRegionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = regions_client.RegionsClient(fake_auth, 'identity', 'regionOne') def _test_create_region(self, bytes_body=False): self.check_service_client_function( self.client.create_region, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_REGION, bytes_body, status=201) def _test_show_region(self, bytes_body=False): self.check_service_client_function( self.client.show_region, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_REGION_INFO, bytes_body, region_id="RegionThree") def _test_list_regions(self, bytes_body=False): self.check_service_client_function( self.client.list_regions, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_REGIONS, bytes_body) def _test_update_region(self, bytes_body=False): self.check_service_client_function( self.client.update_region, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_REGION_INFO, bytes_body, region_id="RegionThree") def test_create_region_with_str_body(self): self._test_create_region() def test_create_region_with_bytes_body(self): self._test_create_region(bytes_body=True) def test_show_region_with_str_body(self): self._test_show_region() def test_show_region_with_bytes_body(self): self._test_show_region(bytes_body=True) def test_list_regions_with_str_body(self): self._test_list_regions() def test_list_regions_with_bytes_body(self): self._test_list_regions(bytes_body=True) def test_update_region_with_str_body(self): self._test_update_region() def test_update_region_with_bytes_body(self): self._test_update_region(bytes_body=True) def test_delete_region(self): self.check_service_client_function( self.client.delete_region, 'tempest.lib.common.rest_client.RestClient.delete', {}, region_id="RegionThree", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_versions_client.py0000666000175100017510000000524313207044712027530 0ustar zuulzuul00000000000000# Copyright 2017 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.identity.v3 import versions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestIdentityClient(base.BaseServiceTest): FAKE_VERSIONS_INFO = { "versions": { "values": [ {"status": "stable", "updated": "2017-02-22T00:00:00Z", "media-types": [ {"base": "application/json", "type": "application/vnd.openstack.identity-v3+json"} ], "id": "v3.8", "links": [ {"href": "https://15.184.67.226/identity_admin/v3/", "rel": "self"} ]}, {"status": "deprecated", "updated": "2016-08-04T00:00:00Z", "media-types": [ {"base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json"} ], "id": "v2.0", "links": [ {"href": "https://15.184.67.226/identity_admin/v2.0/", "rel": "self"}, {"href": "https://docs.openstack.org/", "type": "text/html", "rel": "describedby"} ]} ] } } def setUp(self): super(TestIdentityClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = versions_client.VersionsClient(fake_auth, 'identity', 'regionOne') def _test_list_versions(self, bytes_body=False): self.check_service_client_function( self.client.list_versions, 'tempest.lib.common.rest_client.RestClient.raw_request', self.FAKE_VERSIONS_INFO, bytes_body, 300) def test_list_versions_with_str_body(self): self._test_list_versions() def test_list_versions_with_bytes_body(self): self._test_list_versions(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_projects_client.py0000666000175100017510000001414713207044712027514 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import projects_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestProjectsClient(base.BaseServiceTest): FAKE_CREATE_PROJECT = { "project": { "description": "My new project", "domain_id": "default", "enabled": True, "is_domain": False, "name": "myNewProject" } } FAKE_PROJECT_INFO = { "project": { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "0c4e939acacf4376bdcd1129f1a054ad", "links": { "self": "http://example.com/identity/v3/projects/0c4e" + "939acacf4376bdcd1129f1a054ad" }, "name": "admin", "parent_id": "default" } } FAKE_LIST_PROJECTS = { "links": { "next": None, "previous": None, "self": "http://example.com/identity/v3/projects" }, "projects": [ { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "0c4e939acacf4376bdcd1129f1a054ad", "links": { "self": "http://example.com/identity/v3/projects" + "/0c4e939acacf4376bdcd1129f1a054ad" }, "name": "admin", "parent_id": None }, { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "0cbd49cbf76d405d9c86562e1d579bd3", "links": { "self": "http://example.com/identity/v3/projects" + "/0cbd49cbf76d405d9c86562e1d579bd3" }, "name": "demo", "parent_id": None }, { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "2db68fed84324f29bb73130c6c2094fb", "links": { "self": "http://example.com/identity/v3/projects" + "/2db68fed84324f29bb73130c6c2094fb" }, "name": "swifttenanttest2", "parent_id": None }, { "is_domain": False, "description": None, "domain_id": "default", "enabled": True, "id": "3d594eb0f04741069dbbb521635b21c7", "links": { "self": "http://example.com/identity/v3/projects" + "/3d594eb0f04741069dbbb521635b21c7" }, "name": "service", "parent_id": None } ] } def setUp(self): super(TestProjectsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = projects_client.ProjectsClient(fake_auth, 'identity', 'regionOne') def _test_create_project(self, bytes_body=False): self.check_service_client_function( self.client.create_project, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_PROJECT, bytes_body, name=self.FAKE_CREATE_PROJECT["project"]["name"], status=201) def _test_show_project(self, bytes_body=False): self.check_service_client_function( self.client.show_project, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_PROJECT_INFO, bytes_body, project_id="0c4e939acacf4376bdcd1129f1a054ad") def _test_list_projects(self, bytes_body=False): self.check_service_client_function( self.client.list_projects, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_PROJECTS, bytes_body) def _test_update_project(self, bytes_body=False): self.check_service_client_function( self.client.update_project, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_PROJECT_INFO, bytes_body, project_id="0c4e939acacf4376bdcd1129f1a054ad") def test_create_project_with_str_body(self): self._test_create_project() def test_create_project_with_bytes_body(self): self._test_create_project(bytes_body=True) def test_show_project_with_str_body(self): self._test_show_project() def test_show_project_with_bytes_body(self): self._test_show_project(bytes_body=True) def test_list_projects_with_str_body(self): self._test_list_projects() def test_list_projects_with_bytes_body(self): self._test_list_projects(bytes_body=True) def test_update_project_with_str_body(self): self._test_update_project() def test_update_project_with_bytes_body(self): self._test_update_project(bytes_body=True) def test_delete_project(self): self.check_service_client_function( self.client.delete_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id="0c4e939acacf4376bdcd1129f1a054ad", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_trusts_client.py0000666000175100017510000001154513207044712027226 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import trusts_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestTrustsClient(base.BaseServiceTest): FAKE_CREATE_TRUST = { "trust": { "expires_at": "2013-02-27T18:30:59.999999Z", "impersonation": True, "allow_redelegation": True, "project_id": "ddef321", "roles": [ { "name": "member" } ], "trustee_user_id": "86c0d5", "trustor_user_id": "a0fdfd" } } FAKE_LIST_TRUSTS = { "trusts": [ { "id": "1ff900", "expires_at": "2013-02-27T18:30:59.999999Z", "impersonation": True, "links": { "self": "http://example.com/identity/v3/OS-TRUST/trusts/1ff900" }, "project_id": "0f1233", "trustee_user_id": "86c0d5", "trustor_user_id": "a0fdfd" }, { "id": "f4513a", "impersonation": False, "links": { "self": "http://example.com/identity/v3/OS-TRUST/trusts/f45513a" }, "project_id": "0f1233", "trustee_user_id": "86c0d5", "trustor_user_id": "3cd2ce" } ] } FAKE_TRUST_INFO = { "trust": { "id": "987fe8", "expires_at": "2013-02-27T18:30:59.999999Z", "impersonation": True, "links": { "self": "http://example.com/identity/v3/OS-TRUST/trusts/987fe8" }, "roles": [ { "id": "ed7b78", "links": { "self": "http://example.com/identity/v3/roles/ed7b78" }, "name": "member" } ], "roles_links": { "next": None, "previous": None, "self": "http://example.com/identity/v3/OS-TRUST/trusts/1ff900/roles" }, "project_id": "0f1233", "trustee_user_id": "be34d1", "trustor_user_id": "56ae32" } } def setUp(self): super(TestTrustsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = trusts_client.TrustsClient(fake_auth, 'identity', 'regionOne') def _test_create_trust(self, bytes_body=False): self.check_service_client_function( self.client.create_trust, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_TRUST, bytes_body, status=201) def _test_show_trust(self, bytes_body=False): self.check_service_client_function( self.client.show_trust, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_TRUST_INFO, bytes_body, trust_id="1ff900") def _test_list_trusts(self, bytes_body=False): self.check_service_client_function( self.client.list_trusts, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_TRUSTS, bytes_body) def test_create_trust_with_str_body(self): self._test_create_trust() def test_create_trust_with_bytes_body(self): self._test_create_trust(bytes_body=True) def test_show_trust_with_str_body(self): self._test_show_trust() def test_show_trust_with_bytes_body(self): self._test_show_trust(bytes_body=True) def test_list_trusts_with_str_body(self): self._test_list_trusts() def test_list_trusts_with_bytes_body(self): self._test_list_trusts(bytes_body=True) def test_delete_trust(self): self.check_service_client_function( self.client.delete_trust, 'tempest.lib.common.rest_client.RestClient.delete', {}, trust_id="1ff900", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_identity_client.py0000666000175100017510000001257413207044712027516 0ustar zuulzuul00000000000000# 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.services.identity.v3 import identity_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestIdentityClient(base.BaseServiceTest): FAKE_TOKEN = { "tokens": { "id": "cbc36478b0bd8e67e89", "name": "FakeToken", "type": "token", } } FAKE_API_INFO = { "name": "API_info", "type": "API", "description": "test_description" } FAKE_AUTH_PROJECTS = { "projects": [ { "domain_id": "1789d1", "enabled": True, "id": "263fd9", "links": { "self": "https://example.com/identity/v3/projects/263fd9" }, "name": "Test Group" }, { "domain_id": "1789d1", "enabled": True, "id": "50ef01", "links": { "self": "https://example.com/identity/v3/projects/50ef01" }, "name": "Build Group" } ], "links": { "self": "https://example.com/identity/v3/auth/projects", "previous": None, "next": None } } FAKE_AUTH_DOMAINS = { "domains": [ { "description": "my domain description", "enabled": True, "id": "1789d1", "links": { "self": "https://example.com/identity/v3/domains/1789d1" }, "name": "my domain" }, { "description": "description of my other domain", "enabled": True, "id": "43e8da", "links": { "self": "https://example.com/identity/v3/domains/43e8da" }, "name": "another domain" } ], "links": { "self": "https://example.com/identity/v3/auth/domains", "previous": None, "next": None } } def setUp(self): super(TestIdentityClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = identity_client.IdentityClient(fake_auth, 'identity', 'regionOne') def _test_show_api_description(self, bytes_body=False): self.check_service_client_function( self.client.show_api_description, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_API_INFO, bytes_body) def _test_show_token(self, bytes_body=False): self.check_service_client_function( self.client.show_token, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_TOKEN, bytes_body, resp_token="cbc36478b0bd8e67e89") def _test_list_auth_projects(self, bytes_body=False): self.check_service_client_function( self.client.list_auth_projects, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_AUTH_PROJECTS, bytes_body) def _test_list_auth_domains(self, bytes_body=False): self.check_service_client_function( self.client.list_auth_domains, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_AUTH_DOMAINS, bytes_body) def test_show_api_description_with_str_body(self): self._test_show_api_description() def test_show_api_description_with_bytes_body(self): self._test_show_api_description(bytes_body=True) def test_show_token_with_str_body(self): self._test_show_token() def test_show_token_with_bytes_body(self): self._test_show_token(bytes_body=True) def test_delete_token(self): self.check_service_client_function( self.client.delete_token, 'tempest.lib.common.rest_client.RestClient.delete', {}, resp_token="cbc36478b0bd8e67e89", status=204) def test_check_token_existence(self): self.check_service_client_function( self.client.check_token_existence, 'tempest.lib.common.rest_client.RestClient.head', {}, resp_token="cbc36478b0bd8e67e89", status=200) def test_list_auth_projects_with_str_body(self): self._test_list_auth_projects() def test_list_auth_projects_with_bytes_body(self): self._test_list_auth_projects(bytes_body=True) def test_list_auth_domains_with_str_body(self): self._test_list_auth_domains() def test_list_auth_domains_with_bytes_body(self): self._test_list_auth_domains(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_inherited_roles_client.py0000666000175100017510000002072413207044712031040 0ustar zuulzuul00000000000000# 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.services.identity.v3 import inherited_roles_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestInheritedRolesClient(base.BaseServiceTest): FAKE_LIST_INHERITED_ROLES = { "roles": [ { "id": "1", "name": "test", "links": "example.com" }, { "id": "2", "name": "test2", "links": "example.com" } ] } def setUp(self): super(TestInheritedRolesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = inherited_roles_client.InheritedRolesClient( fake_auth, 'identity', 'regionOne') def _test_create_inherited_role_on_domains_user(self, bytes_body=False): self.check_service_client_function( self.client.create_inherited_role_on_domains_user, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def _test_list_inherited_project_role_for_user_on_domain( self, bytes_body=False): self.check_service_client_function( self.client.list_inherited_project_role_for_user_on_domain, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_INHERITED_ROLES, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", user_id="123") def _test_create_inherited_role_on_domains_group(self, bytes_body=False): self.check_service_client_function( self.client.create_inherited_role_on_domains_group, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def _test_list_inherited_project_role_for_group_on_domain( self, bytes_body=False): self.check_service_client_function( self.client.list_inherited_project_role_for_group_on_domain, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_INHERITED_ROLES, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", group_id="123") def _test_create_inherited_role_on_projects_user(self, bytes_body=False): self.check_service_client_function( self.client.create_inherited_role_on_projects_user, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, project_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def _test_create_inherited_role_on_projects_group(self, bytes_body=False): self.check_service_client_function( self.client.create_inherited_role_on_projects_group, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, project_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_create_inherited_role_on_domains_user_with_str_body(self): self._test_create_inherited_role_on_domains_user() def test_create_inherited_role_on_domains_user_with_bytes_body(self): self._test_create_inherited_role_on_domains_user(bytes_body=True) def test_create_inherited_role_on_domains_group_with_str_body(self): self._test_create_inherited_role_on_domains_group() def test_create_inherited_role_on_domains_group_with_bytes_body(self): self._test_create_inherited_role_on_domains_group(bytes_body=True) def test_create_inherited_role_on_projects_user_with_str_body(self): self._test_create_inherited_role_on_projects_user() def test_create_inherited_role_on_projects_group_with_bytes_body(self): self._test_create_inherited_role_on_projects_group(bytes_body=True) def test_list_inherited_project_role_for_user_on_domain_with_str_body( self): self._test_list_inherited_project_role_for_user_on_domain() def test_list_inherited_project_role_for_user_on_domain_with_bytes_body( self): self._test_list_inherited_project_role_for_user_on_domain( bytes_body=True) def test_list_inherited_project_role_for_group_on_domain_with_str_body( self): self._test_list_inherited_project_role_for_group_on_domain() def test_list_inherited_project_role_for_group_on_domain_with_bytes_body( self): self._test_list_inherited_project_role_for_group_on_domain( bytes_body=True) def test_delete_inherited_role_from_user_on_domain(self): self.check_service_client_function( self.client.delete_inherited_role_from_user_on_domain, 'tempest.lib.common.rest_client.RestClient.delete', {}, domain_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_check_user_inherited_project_role_on_domain(self): self.check_service_client_function( self.client.check_user_inherited_project_role_on_domain, 'tempest.lib.common.rest_client.RestClient.head', {}, domain_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_delete_inherited_role_from_group_on_domain(self): self.check_service_client_function( self.client.delete_inherited_role_from_group_on_domain, 'tempest.lib.common.rest_client.RestClient.delete', {}, domain_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_check_group_inherited_project_role_on_domain(self): self.check_service_client_function( self.client.check_group_inherited_project_role_on_domain, 'tempest.lib.common.rest_client.RestClient.head', {}, domain_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_delete_inherited_role_from_user_on_project(self): self.check_service_client_function( self.client.delete_inherited_role_from_user_on_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_check_user_has_flag_on_inherited_to_project(self): self.check_service_client_function( self.client.check_user_has_flag_on_inherited_to_project, 'tempest.lib.common.rest_client.RestClient.head', {}, project_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_delete_inherited_role_from_group_on_project(self): self.check_service_client_function( self.client.delete_inherited_role_from_group_on_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_check_group_has_flag_on_inherited_to_project(self): self.check_service_client_function( self.client.check_group_has_flag_on_inherited_to_project, 'tempest.lib.common.rest_client.RestClient.head', {}, project_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_groups_client.py0000666000175100017510000001627713207044712027210 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import groups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestGroupsClient(base.BaseServiceTest): FAKE_CREATE_GROUP = { 'group': { 'description': 'Tempest Group Description', 'domain_id': 'TempestDomain', 'name': 'Tempest Group', } } FAKE_GROUP_INFO = { 'group': { 'description': 'Tempest Group Description', 'domain_id': 'TempestDomain', 'id': '6e13e2068cf9466e98950595baf6bb35', 'links': { 'self': 'http://example.com/identity/v3/groups/' + '6e13e2068cf9466e98950595baf6bb35' }, 'name': 'Tempest Group', } } FAKE_GROUP_LIST = { 'links': { 'self': 'http://example.com/identity/v3/groups', 'previous': None, 'next': None, }, 'groups': [ { 'description': 'Tempest Group One Description', 'domain_id': 'TempestDomain', 'id': '1c92f3453ed34291a074b87493455b8f', 'links': { 'self': 'http://example.com/identity/v3/groups/' + '1c92f3453ed34291a074b87493455b8f' }, 'name': 'Tempest Group One', }, { 'description': 'Tempest Group Two Description', 'domain_id': 'TempestDomain', 'id': 'ce9e7dafed3b4877a7d4466ed730a9ee', 'links': { 'self': 'http://example.com/identity/v3/groups/' + 'ce9e7dafed3b4877a7d4466ed730a9ee' }, 'name': 'Tempest Group Two', }, ] } FAKE_USER_LIST = { 'links': { 'self': 'http://example.com/identity/v3/groups/' + '6e13e2068cf9466e98950595baf6bb35/users', 'previous': None, 'next': None, }, 'users': [ { 'domain_id': 'TempestDomain', 'description': 'Tempest Test User One Description', 'enabled': True, 'id': '642688fa65a84217b86cef3c063de2b9', 'name': 'TempestUserOne', 'links': { 'self': 'http://example.com/identity/v3/users/' + '642688fa65a84217b86cef3c063de2b9' } }, { 'domain_id': 'TempestDomain', 'description': 'Tempest Test User Two Description', 'enabled': True, 'id': '1048ead6f8ef4a859b44ffbce3ac0b52', 'name': 'TempestUserTwo', 'links': { 'self': 'http://example.com/identity/v3/users/' + '1048ead6f8ef4a859b44ffbce3ac0b52' } }, ] } def setUp(self): super(TestGroupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = groups_client.GroupsClient(fake_auth, 'identity', 'regionOne') def _test_create_group(self, bytes_body=False): self.check_service_client_function( self.client.create_group, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP, bytes_body, status=201, ) def _test_show_group(self, bytes_body=False): self.check_service_client_function( self.client.show_group, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_GROUP_INFO, bytes_body, group_id='6e13e2068cf9466e98950595baf6bb35', ) def _test_list_groups(self, bytes_body=False): self.check_service_client_function( self.client.list_groups, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_GROUP_LIST, bytes_body, ) def _test_update_group(self, bytes_body=False): self.check_service_client_function( self.client.update_group, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_GROUP_INFO, bytes_body, group_id='6e13e2068cf9466e98950595baf6bb35', name='NewName', ) def _test_list_users_in_group(self, bytes_body=False): self.check_service_client_function( self.client.list_group_users, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_USER_LIST, bytes_body, group_id='6e13e2068cf9466e98950595baf6bb35', ) def test_create_group_with_string_body(self): self._test_create_group() def test_create_group_with_bytes_body(self): self._test_create_group(bytes_body=True) def test_show_group_with_string_body(self): self._test_show_group() def test_show_group_with_bytes_body(self): self._test_show_group(bytes_body=True) def test_list_groups_with_string_body(self): self._test_list_groups() def test_list_groups_with_bytes_body(self): self._test_list_groups(bytes_body=True) def test_update_group_with_string_body(self): self._test_update_group() def test_update_group_with_bytes_body(self): self._test_update_group(bytes_body=True) def test_list_users_in_group_with_string_body(self): self._test_list_users_in_group() def test_list_users_in_group_with_bytes_body(self): self._test_list_users_in_group(bytes_body=True) def test_delete_group(self): self.check_service_client_function( self.client.delete_group, 'tempest.lib.common.rest_client.RestClient.delete', {}, group_id='6e13e2068cf9466e98950595baf6bb35', status=204, ) def test_add_user_to_group(self): self.check_service_client_function( self.client.add_group_user, 'tempest.lib.common.rest_client.RestClient.put', {}, status=204, group_id='6e13e2068cf9466e98950595baf6bb35', user_id='642688fa65a84217b86cef3c063de2b9', ) def test_check_user_in_group(self): self.check_service_client_function( self.client.check_group_user_existence, 'tempest.lib.common.rest_client.RestClient.head', {}, status=204, group_id='6e13e2068cf9466e98950595baf6bb35', user_id='642688fa65a84217b86cef3c063de2b9', ) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_domains_client.py0000666000175100017510000001100313207044712027301 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import domains_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestDomainsClient(base.BaseServiceTest): FAKE_CREATE_DOMAIN = { "domain": { "description": "Domain description", "enabled": True, "name": "myDomain" } } FAKE_DOMAIN_INFO = { "domain": { "description": "Used for swift functional testing", "enabled": True, "id": "5a75994a3", "links": { "self": "http://example.com/identity/v3/domains/5a75994a3" }, "name": "swift_test" } } FAKE_LIST_DOMAINS = { "domains": [ { "description": "Used for swift functional testing", "enabled": True, "id": "5a75994a3", "links": { "self": "http://example.com/identity/v3/domains/5a75994a3" }, "name": "swift_test" }, { "description": "Owns users and tenants available on " + "Identity API", "enabled": True, "id": "default", "links": { "self": "http://example.com/identity/v3/domains/default" }, "name": "Default" } ], "links": { "next": None, "previous": None, "self": "http://example.com/identity/v3/domains" } } def setUp(self): super(TestDomainsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = domains_client.DomainsClient(fake_auth, 'identity', 'regionOne') def _test_create_domain(self, bytes_body=False): self.check_service_client_function( self.client.create_domain, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_DOMAIN, bytes_body, status=201) def _test_show_domain(self, bytes_body=False): self.check_service_client_function( self.client.show_domain, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_DOMAIN_INFO, bytes_body, domain_id="5a75994a3") def _test_list_domains(self, bytes_body=False): self.check_service_client_function( self.client.list_domains, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_DOMAINS, bytes_body) def _test_update_domain(self, bytes_body=False): self.check_service_client_function( self.client.update_domain, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_DOMAIN_INFO, bytes_body, domain_id="5a75994a3") def test_create_domain_with_str_body(self): self._test_create_domain() def test_create_domain_with_bytes_body(self): self._test_create_domain(bytes_body=True) def test_show_domain_with_str_body(self): self._test_show_domain() def test_show_domain_with_bytes_body(self): self._test_show_domain(bytes_body=True) def test_list_domain_with_str_body(self): self._test_list_domains() def test_list_domain_with_bytes_body(self): self._test_list_domains(bytes_body=True) def test_update_domain_with_str_body(self): self._test_update_domain() def test_update_domain_with_bytes_body(self): self._test_update_domain(bytes_body=True) def test_delete_domain(self): self.check_service_client_function( self.client.delete_domain, 'tempest.lib.common.rest_client.RestClient.delete', {}, domain_id="5a75994a3", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_policies_client.py0000666000175100017510000001170413207044712027466 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import policies_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestPoliciesClient(base.BaseServiceTest): FAKE_CREATE_POLICY = { "policy": { "blob": "{'foobar_user': 'role:compute-user'}", "project_id": "0426ac1e48f642ef9544c2251e07e261", "type": "application/json", "user_id": "0ffd248c55b443eaac5253b4e9cbf9b5" } } FAKE_POLICY_INFO = { "policy": { "blob": { "foobar_user": [ "role:compute-user" ] }, "id": "717273", "links": { "self": "http://example.com/identity/v3/policies/717273" }, "project_id": "456789", "type": "application/json", "user_id": "616263" } } FAKE_LIST_POLICIES = { "links": { "next": None, "previous": None, "self": "http://example.com/identity/v3/policies" }, "policies": [ { "blob": { "foobar_user": [ "role:compute-user" ] }, "id": "717273", "links": { "self": "http://example.com/identity/v3/policies/717273" }, "project_id": "456789", "type": "application/json", "user_id": "616263" }, { "blob": { "foobar_user": [ "role:compute-user" ] }, "id": "717274", "links": { "self": "http://example.com/identity/v3/policies/717274" }, "project_id": "456789", "type": "application/json", "user_id": "616263" } ] } def setUp(self): super(TestPoliciesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = policies_client.PoliciesClient(fake_auth, 'identity', 'regionOne') def _test_create_policy(self, bytes_body=False): self.check_service_client_function( self.client.create_policy, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_POLICY, bytes_body, status=201) def _test_show_policy(self, bytes_body=False): self.check_service_client_function( self.client.show_policy, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_POLICY_INFO, bytes_body, policy_id="717273") def _test_list_policies(self, bytes_body=False): self.check_service_client_function( self.client.list_policies, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_POLICIES, bytes_body) def _test_update_policy(self, bytes_body=False): self.check_service_client_function( self.client.update_policy, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_POLICY_INFO, bytes_body, policy_id="717273") def test_create_policy_with_str_body(self): self._test_create_policy() def test_create_policy_with_bytes_body(self): self._test_create_policy(bytes_body=True) def test_show_policy_with_str_body(self): self._test_show_policy() def test_show_policy_with_bytes_body(self): self._test_show_policy(bytes_body=True) def test_list_policies_with_str_body(self): self._test_list_policies() def test_list_policies_with_bytes_body(self): self._test_list_policies(bytes_body=True) def test_update_policy_with_str_body(self): self._test_update_policy() def test_update_policy_with_bytes_body(self): self._test_update_policy(bytes_body=True) def test_delete_policy(self): self.check_service_client_function( self.client.delete_policy, 'tempest.lib.common.rest_client.RestClient.delete', {}, policy_id="717273", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/__init__.py0000666000175100017510000000000013207044712025004 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_roles_client.py0000666000175100017510000004322113207044712027002 0ustar zuulzuul00000000000000# 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.services.identity.v3 import roles_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestRolesClient(base.BaseServiceTest): FAKE_ROLE_ID = "1" FAKE_ROLE_NAME = "test" FAKE_DOMAIN_ID = "1" FAKE_ROLE_ID_2 = "2" FAKE_ROLE_NAME_2 = "test2" FAKE_ROLE_ID_3 = "3" FAKE_ROLE_NAME_3 = "test3" FAKE_ROLE_ID_4 = "4" FAKE_ROLE_NAME_4 = "test4" FAKE_ROLE_ID_5 = "5" FAKE_ROLE_NAME_5 = "test5" FAKE_ROLE_ID_6 = "6" FAKE_ROLE_NAME_6 = "test6" FAKE_ROLE_INFO = { "role": { "domain_id": FAKE_DOMAIN_ID, "id": FAKE_ROLE_ID, "name": FAKE_ROLE_NAME, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID) } } } FAKE_ROLE_INFO_2 = { "role": { "domain_id": FAKE_DOMAIN_ID, "id": FAKE_ROLE_ID_2, "name": FAKE_ROLE_NAME_2, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_2) } } } FAKE_LIST_ROLES = {"roles": [FAKE_ROLE_INFO, FAKE_ROLE_INFO_2]} FAKE_ROLE_INFERENCE_RULE = { "role_inference": { "prior_role": { "id": FAKE_ROLE_ID, "name": FAKE_ROLE_NAME, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID) } }, "implies": { "id": FAKE_ROLE_ID_2, "name": FAKE_ROLE_NAME_2, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_2) } } }, "links": { "self": "http://example.com/identity/v3/roles/" "%s/implies/%s" % (FAKE_ROLE_ID, FAKE_ROLE_ID_2) } } COMMON_FAKE_LIST_ROLE_INFERENCE_RULES = [ { "prior_role": { "id": FAKE_ROLE_ID, "name": FAKE_ROLE_NAME, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID) } }, "implies": [ { "id": FAKE_ROLE_ID_2, "name": FAKE_ROLE_NAME_2, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_2) } }, { "id": FAKE_ROLE_ID_3, "name": FAKE_ROLE_NAME_3, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_3) } } ] }, { "prior_role": { "id": FAKE_ROLE_ID_4, "name": FAKE_ROLE_NAME_4, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_4) } }, "implies": [ { "id": FAKE_ROLE_ID_5, "name": FAKE_ROLE_NAME_5, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_5) } }, { "id": FAKE_ROLE_ID_6, "name": FAKE_ROLE_NAME_6, "links": { "self": "http://example.com/identity/v3/roles/%s" % ( FAKE_ROLE_ID_6) } } ] } ] FAKE_LIST_ROLE_INFERENCE_RULES = { "role_inference": COMMON_FAKE_LIST_ROLE_INFERENCE_RULES[0], "links": { "self": "http://example.com/identity/v3/roles/" "%s/implies" % FAKE_ROLE_ID } } FAKE_LIST_ALL_ROLE_INFERENCE_RULES = { "role_inferences": COMMON_FAKE_LIST_ROLE_INFERENCE_RULES, "links": { "self": "http://example.com/identity/v3/role_inferences" } } def setUp(self): super(TestRolesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = roles_client.RolesClient(fake_auth, 'identity', 'regionOne') def _test_create_role(self, bytes_body=False): self.check_service_client_function( self.client.create_role, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_ROLE_INFO, bytes_body, domain_id=self.FAKE_DOMAIN_ID, name=self.FAKE_ROLE_NAME, status=201) def _test_show_role(self, bytes_body=False): self.check_service_client_function( self.client.show_role, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ROLE_INFO, bytes_body, role_id=self.FAKE_ROLE_ID) def _test_list_roles(self, bytes_body=False): self.check_service_client_function( self.client.list_roles, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body) def _test_update_role(self, bytes_body=False): self.check_service_client_function( self.client.update_role, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_ROLE_INFO, bytes_body, role_id=self.FAKE_ROLE_ID, name=self.FAKE_ROLE_NAME) def _test_create_user_role_on_project(self, bytes_body=False): self.check_service_client_function( self.client.create_user_role_on_project, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, project_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def _test_create_user_role_on_domain(self, bytes_body=False): self.check_service_client_function( self.client.create_user_role_on_domain, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def _test_list_user_roles_on_project(self, bytes_body=False): self.check_service_client_function( self.client.list_user_roles_on_project, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body, project_id="b344506af7644f6794d9cb316600b020", user_id="123") def _test_list_user_roles_on_domain(self, bytes_body=False): self.check_service_client_function( self.client.list_user_roles_on_domain, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", user_id="123") def _test_create_group_role_on_project(self, bytes_body=False): self.check_service_client_function( self.client.create_group_role_on_project, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, project_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def _test_create_group_role_on_domain(self, bytes_body=False): self.check_service_client_function( self.client.create_group_role_on_domain, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def _test_list_group_roles_on_project(self, bytes_body=False): self.check_service_client_function( self.client.list_group_roles_on_project, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body, project_id="b344506af7644f6794d9cb316600b020", group_id="123") def _test_list_group_roles_on_domain(self, bytes_body=False): self.check_service_client_function( self.client.list_group_roles_on_domain, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body, domain_id="b344506af7644f6794d9cb316600b020", group_id="123") def _test_create_role_inference_rule(self, bytes_body=False): self.check_service_client_function( self.client.create_role_inference_rule, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_ROLE_INFERENCE_RULE, bytes_body, status=201, prior_role=self.FAKE_ROLE_ID, implies_role=self.FAKE_ROLE_ID_2) def _test_show_role_inference_rule(self, bytes_body=False): self.check_service_client_function( self.client.show_role_inference_rule, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ROLE_INFERENCE_RULE, bytes_body, prior_role=self.FAKE_ROLE_ID, implies_role=self.FAKE_ROLE_ID_2) def _test_list_role_inferences_rules(self, bytes_body=False): self.check_service_client_function( self.client.list_role_inferences_rules, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLE_INFERENCE_RULES, bytes_body, prior_role=self.FAKE_ROLE_ID) def _test_list_all_role_inference_rules(self, bytes_body=False): self.check_service_client_function( self.client.list_all_role_inference_rules, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ALL_ROLE_INFERENCE_RULES, bytes_body) def test_create_role_with_str_body(self): self._test_create_role() def test_create_role_with_bytes_body(self): self._test_create_role(bytes_body=True) def test_show_role_with_str_body(self): self._test_show_role() def test_show_role_with_bytes_body(self): self._test_show_role(bytes_body=True) def test_list_roles_with_str_body(self): self._test_list_roles() def test_list_roles_with_bytes_body(self): self._test_list_roles(bytes_body=True) def test_update_role_with_str_body(self): self._test_update_role() def test_update_role_with_bytes_body(self): self._test_update_role(bytes_body=True) def test_delete_role(self): self.check_service_client_function( self.client.delete_role, 'tempest.lib.common.rest_client.RestClient.delete', {}, role_id=self.FAKE_ROLE_ID, status=204) def test_create_user_role_on_project_with_str_body(self): self._test_create_user_role_on_project() def test_create_user_role_on_project_with_bytes_body(self): self._test_create_user_role_on_project(bytes_body=True) def test_create_user_role_on_domain_with_str_body(self): self._test_create_user_role_on_domain() def test_create_user_role_on_domain_with_bytes_body(self): self._test_create_user_role_on_domain(bytes_body=True) def test_create_group_role_on_domain_with_str_body(self): self._test_create_group_role_on_domain() def test_create_group_role_on_domain_with_bytes_body(self): self._test_create_group_role_on_domain(bytes_body=True) def test_list_user_roles_on_project_with_str_body(self): self._test_list_user_roles_on_project() def test_list_user_roles_on_project_with_bytes_body(self): self._test_list_user_roles_on_project(bytes_body=True) def test_list_user_roles_on_domain_with_str_body(self): self._test_list_user_roles_on_domain() def test_list_user_roles_on_domain_with_bytes_body(self): self._test_list_user_roles_on_domain(bytes_body=True) def test_list_group_roles_on_domain_with_str_body(self): self._test_list_group_roles_on_domain() def test_list_group_roles_on_domain_with_bytes_body(self): self._test_list_group_roles_on_domain(bytes_body=True) def test_delete_role_from_user_on_project(self): self.check_service_client_function( self.client.delete_role_from_user_on_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_delete_role_from_user_on_domain(self): self.check_service_client_function( self.client.delete_role_from_user_on_domain, 'tempest.lib.common.rest_client.RestClient.delete', {}, domain_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_delete_role_from_group_on_project(self): self.check_service_client_function( self.client.delete_role_from_group_on_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, project_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_delete_role_from_group_on_domain(self): self.check_service_client_function( self.client.delete_role_from_group_on_domain, 'tempest.lib.common.rest_client.RestClient.delete', {}, domain_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_check_user_role_existence_on_project(self): self.check_service_client_function( self.client.check_user_role_existence_on_project, 'tempest.lib.common.rest_client.RestClient.head', {}, project_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_check_user_role_existence_on_domain(self): self.check_service_client_function( self.client.check_user_role_existence_on_domain, 'tempest.lib.common.rest_client.RestClient.head', {}, domain_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) def test_check_role_from_group_on_project_existence(self): self.check_service_client_function( self.client.check_role_from_group_on_project_existence, 'tempest.lib.common.rest_client.RestClient.head', {}, project_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_check_role_from_group_on_domain_existence(self): self.check_service_client_function( self.client.check_role_from_group_on_domain_existence, 'tempest.lib.common.rest_client.RestClient.head', {}, domain_id="b344506af7644f6794d9cb316600b020", group_id="123", role_id="1234", status=204) def test_create_role_inference_rule_with_str_body(self): self._test_create_role_inference_rule() def test_create_role_inference_rule_with_bytes_body(self): self._test_create_role_inference_rule(bytes_body=True) def test_show_role_inference_rule_with_str_body(self): self._test_show_role_inference_rule() def test_show_role_inference_rule_with_bytes_body(self): self._test_show_role_inference_rule(bytes_body=True) def test_list_role_inferences_rules_with_str_body(self): self._test_list_role_inferences_rules() def test_list_role_inferences_rules_with_bytes_body(self): self._test_list_role_inferences_rules(bytes_body=True) def test_check_role_inference_rule(self): self.check_service_client_function( self.client.check_role_inference_rule, 'tempest.lib.common.rest_client.RestClient.head', {}, status=204, prior_role=self.FAKE_ROLE_ID, implies_role=self.FAKE_ROLE_ID_2) def test_delete_role_inference_rule(self): self.check_service_client_function( self.client.delete_role_inference_rule, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, prior_role=self.FAKE_ROLE_ID, implies_role=self.FAKE_ROLE_ID_2) def test_list_all_role_inference_rules_with_str_body(self): self._test_list_all_role_inference_rules() def test_list_all_role_inference_rules_with_bytes_body(self): self._test_list_all_role_inference_rules(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_domain_configuration_client.py0000666000175100017510000002014713207044712032056 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import domain_configuration_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestDomainConfigurationClient(base.BaseServiceTest): FAKE_CONFIG_SETTINGS = { "config": { "identity": { "driver": "ldap" }, "ldap": { "url": "ldap://localhost", "user": "", "suffix": "cn=example,cn=com", } } } FAKE_DOMAIN_ID = '07ef7d04-2941-4bee-8551-f79f08a021de' def setUp(self): super(TestDomainConfigurationClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = domain_configuration_client.DomainConfigurationClient( fake_auth, 'identity', 'regionOne') def _test_show_default_config_settings(self, bytes_body=False): self.check_service_client_function( self.client.show_default_config_settings, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CONFIG_SETTINGS, bytes_body) def _test_show_default_group_config(self, bytes_body=False): self.check_service_client_function( self.client.show_default_group_config, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CONFIG_SETTINGS['config']['ldap'], bytes_body, group='ldap') def _test_show_default_group_option(self, bytes_body=False): self.check_service_client_function( self.client.show_default_group_option, 'tempest.lib.common.rest_client.RestClient.get', {'driver': 'ldap'}, bytes_body, group='identity', option='driver') def _test_show_domain_group_option_config(self, bytes_body=False): self.check_service_client_function( self.client.show_domain_group_option_config, 'tempest.lib.common.rest_client.RestClient.get', {'driver': 'ldap'}, bytes_body, domain_id=self.FAKE_DOMAIN_ID, group='identity', option='driver') def _test_update_domain_group_option_config(self, bytes_body=False): self.check_service_client_function( self.client.update_domain_group_option_config, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_CONFIG_SETTINGS, bytes_body, domain_id=self.FAKE_DOMAIN_ID, group='identity', option='driver', url='http://myldap/my_other_root') def _test_show_domain_group_config(self, bytes_body=False): self.check_service_client_function( self.client.show_domain_group_config, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CONFIG_SETTINGS['config']['ldap'], bytes_body, domain_id=self.FAKE_DOMAIN_ID, group='ldap') def _test_update_domain_group_config(self, bytes_body=False): self.check_service_client_function( self.client.update_domain_group_config, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_CONFIG_SETTINGS['config']['ldap'], bytes_body, domain_id=self.FAKE_DOMAIN_ID, group='ldap', **self.FAKE_CONFIG_SETTINGS['config']) def _test_create_domain_config(self, bytes_body=False): self.check_service_client_function( self.client.create_domain_config, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_CONFIG_SETTINGS, bytes_body, domain_id=self.FAKE_DOMAIN_ID, status=201) def _test_show_domain_config(self, bytes_body=False): self.check_service_client_function( self.client.show_domain_config, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CONFIG_SETTINGS, bytes_body, domain_id=self.FAKE_DOMAIN_ID) def _test_update_domain_config(self, bytes_body=False): self.check_service_client_function( self.client.update_domain_config, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_CONFIG_SETTINGS, bytes_body, domain_id=self.FAKE_DOMAIN_ID) def test_show_default_config_settings_with_str_body(self): self._test_show_default_config_settings() def test_show_default_config_settings_with_bytes_body(self): self._test_show_default_config_settings(bytes_body=True) def test_show_default_group_config_with_str_body(self): self._test_show_default_group_config() def test_show_default_group_config_with_bytes_body(self): self._test_show_default_group_config(bytes_body=True) def test_show_default_group_option_with_str_body(self): self._test_show_default_group_option() def test_show_default_group_option_with_bytes_body(self): self._test_show_default_group_option(bytes_body=True) def test_show_domain_group_option_config_with_str_body(self): self._test_show_domain_group_option_config() def test_show_domain_group_option_config_with_bytes_body(self): self._test_show_domain_group_option_config(bytes_body=True) def test_update_domain_group_option_config_with_str_body(self): self._test_update_domain_group_option_config() def test_update_domain_group_option_config_with_bytes_body(self): self._test_update_domain_group_option_config(bytes_body=True) def test_delete_domain_group_option_config(self): self.check_service_client_function( self.client.delete_domain_group_option_config, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, domain_id=self.FAKE_DOMAIN_ID, group='identity', option='driver') def test_show_domain_group_config_with_str_body(self): self._test_show_domain_group_config() def test_show_domain_group_config_with_bytes_body(self): self._test_show_domain_group_config(bytes_body=True) def test_test_update_domain_group_config_with_str_body(self): self._test_update_domain_group_config() def test_update_domain_group_config_with_bytes_body(self): self._test_update_domain_group_config(bytes_body=True) def test_delete_domain_group_config(self): self.check_service_client_function( self.client.delete_domain_group_config, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, domain_id=self.FAKE_DOMAIN_ID, group='identity') def test_create_domain_config_with_str_body(self): self._test_create_domain_config() def test_create_domain_config_with_bytes_body(self): self._test_create_domain_config(bytes_body=True) def test_show_domain_config_with_str_body(self): self._test_show_domain_config() def test_show_domain_config_with_bytes_body(self): self._test_show_domain_config(bytes_body=True) def test_update_domain_config_with_str_body(self): self._test_update_domain_config() def test_update_domain_config_with_bytes_body(self): self._test_update_domain_config(bytes_body=True) def test_delete_domain_config(self): self.check_service_client_function( self.client.delete_domain_config, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=204, domain_id=self.FAKE_DOMAIN_ID) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_credentials_client.py0000666000175100017510000001660013207044712030154 0ustar zuulzuul00000000000000# 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.services.identity.v3 import credentials_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestCredentialsClient(base.BaseServiceTest): FAKE_CREATE_CREDENTIAL = { "credential": { "blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}", "project_id": "731fc6f265cd486d900f16e84c5cb594", "type": "ec2", "user_id": "bb5476fd12884539b41d5a88f838d773" } } FAKE_INFO_CREDENTIAL = { "credential": { "user_id": "bb5476fd12884539b41d5a88f838d773", "links": { "self": "http://example.com/identity/v3/credentials/" + "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4" + "ada4ff8b76165a07311d7" }, "blob": "{\"access\": \"a42a27755ce6442596b049bd7dd8a563\"," + " \"secret\": \"71faf1d40bb24c82b479b1c6fbbd9f0c\"}", "project_id": "6e01855f345f4c59812999b5e459137d", "type": "ec2", "id": "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f" } } FAKE_LIST_CREDENTIALS = { "credentials": [ { "user_id": "bb5476fd12884539b41d5a88f838d773", "links": { "self": "http://example.com/identity/v3/credentials/" + "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4" + "ada4ff8b76165a07311d7" }, "blob": "{\"access\": \"a42a27755ce6442596b049bd7dd8a563\"," + " \"secret\": \"71faf1d40bb24c82b479b1c6fbbd9f0c\"," + " \"trust_id\": null}", "project_id": "6e01855f345f4c59812999b5e459137d", "type": "ec2", "id": "207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f" }, { "user_id": "6f556708d04b4ea6bc72d7df2296b71a", "links": { "self": "http://example.com/identity/v3/credentials/" + "2441494e52ab6d594a34d74586075cb299489bdd1e9" + "389e3ab06467a4f460609" }, "blob": "{\"access\": \"7da79ff0aa364e1396f067e352b9b79a\"," + " \"secret\": \"7a18d68ba8834b799d396f3ff6f1e98c\"," + " \"trust_id\": null}", "project_id": "1a1d14690f3c4ec5bf5f321c5fde3c16", "type": "ec2", "id": "2441494e52ab6d594a34d74586075cb299489bdd1e9389e3" }, { "user_id": "c14107e65d5c4a7f8894fc4b3fc209ff", "links": { "self": "http://example.com/identity/v3/credentials/" + "3397b204b5f04c495bcdc8f34c8a39996f280f91726" + "58241873e15f070ec79d7" }, "blob": "{\"access\": \"db9c58a558534a10a070110de4f9f20c\"," + " \"secret\": \"973e790b88db447ba6f93bca02bc745b\"," + " \"trust_id\": null}", "project_id": "7396e43183db40dcbf40dd727637b548", "type": "ec2", "id": "3397b204b5f04c495bcdc8f34c8a39996f280f9172658241" }, { "user_id": "bb5476fd12884539b41d5a88f838d773", "links": { "self": "http://example.com/identity/v3/credentials/" + "7ef4faa904ae7b8b4ddc7bad15b05ee359dad7d7a9b" + "82861d4ad92fdbbb2eb4e" }, "blob": "{\"access\": \"7d7559359b57419eb5f5f5dcd65ab57d\"," + " \"secret\": \"570652bcf8c2483c86eb29e9734eed3c\"," + " \"trust_id\": null}", "project_id": "731fc6f265cd486d900f16e84c5cb594", "type": "ec2", "id": "7ef4faa904ae7b8b4ddc7bad15b05ee359dad7d7a9b82861" }, ], "links": { "self": "http://example.com/identity/v3/credentials", "previous": None, "next": None } } def setUp(self): super(TestCredentialsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = credentials_client.CredentialsClient(fake_auth, 'identity', 'regionOne') def _test_create_credential(self, bytes_body=False): self.check_service_client_function( self.client.create_credential, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_CREDENTIAL, bytes_body, status=201) def _test_show_credential(self, bytes_body=False): self.check_service_client_function( self.client.show_credential, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_CREDENTIAL, bytes_body, credential_id="207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f") def _test_update_credential(self, bytes_body=False): self.check_service_client_function( self.client.update_credential, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_INFO_CREDENTIAL, bytes_body, credential_id="207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f") def _test_list_credentials(self, bytes_body=False): self.check_service_client_function( self.client.list_credentials, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_CREDENTIALS, bytes_body) def test_create_credential_with_str_body(self): self._test_create_credential() def test_create_credential_with_bytes_body(self): self._test_create_credential(bytes_body=True) def test_show_credential_with_str_body(self): self._test_show_credential() def test_show_credential_with_bytes_body(self): self._test_show_credential(bytes_body=True) def test_update_credential_with_str_body(self): self._test_update_credential() def test_update_credential_with_bytes_body(self): self._test_update_credential(bytes_body=True) def test_list_credentials_with_str_body(self): self._test_list_credentials() def test_list_credentials_with_bytes_body(self): self._test_list_credentials(bytes_body=True) def test_delete_credential(self): self.check_service_client_function( self.client.delete_credential, 'tempest.lib.common.rest_client.RestClient.delete', {}, credential_id="207e9b76935efc03804d3dd6ab52d22e9b22a0711e4ada4f", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_endpoint_groups_client.py0000666000175100017510000001306113207044712031074 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.identity.v3 import endpoint_groups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestEndPointGroupsClient(base.BaseServiceTest): FAKE_CREATE_ENDPOINT_GROUP = { "endpoint_group": { "id": 1, "name": "FAKE_ENDPOINT_GROUP", "description": "FAKE SERVICE ENDPOINT GROUP", "filters": { "service_id": 1 } } } FAKE_ENDPOINT_GROUP_INFO = { "endpoint_group": { "id": 1, "name": "FAKE_ENDPOINT_GROUP", "description": "FAKE SERVICE ENDPOINT GROUP", "links": { "self": "http://example.com/identity/v3/OS-EP-FILTER/" + "endpoint_groups/1" }, "filters": { "service_id": 1 } } } FAKE_LIST_ENDPOINT_GROUPS = { "endpoint_groups": [ { "id": 1, "name": "SERVICE_GROUP1", "description": "FAKE SERVICE ENDPOINT GROUP", "links": { "self": "http://example.com/identity/v3/OS-EP-FILTER/" + "endpoint_groups/1" }, "filters": { "service_id": 1 } }, { "id": 2, "name": "SERVICE_GROUP2", "description": "FAKE SERVICE ENDPOINT GROUP", "links": { "self": "http://example.com/identity/v3/OS-EP-FILTER/" + "endpoint_groups/2" }, "filters": { "service_id": 2 } } ] } def setUp(self): super(TestEndPointGroupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = endpoint_groups_client.EndPointGroupsClient( fake_auth, 'identity', 'regionOne') def _test_create_endpoint_group(self, bytes_body=False): self.check_service_client_function( self.client.create_endpoint_group, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_ENDPOINT_GROUP, bytes_body, status=201, name="FAKE_ENDPOINT_GROUP", filters={'service_id': "1"}) def _test_show_endpoint_group(self, bytes_body=False): self.check_service_client_function( self.client.show_endpoint_group, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ENDPOINT_GROUP_INFO, bytes_body, endpoint_group_id="1") def _test_check_endpoint_group(self, bytes_body=False): self.check_service_client_function( self.client.check_endpoint_group, 'tempest.lib.common.rest_client.RestClient.head', {}, bytes_body, status=200, endpoint_group_id="1") def _test_update_endpoint_group(self, bytes_body=False): self.check_service_client_function( self.client.update_endpoint_group, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_ENDPOINT_GROUP_INFO, bytes_body, endpoint_group_id="1", name="NewName") def _test_list_endpoint_groups(self, bytes_body=False): self.check_service_client_function( self.client.list_endpoint_groups, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ENDPOINT_GROUPS, bytes_body) def test_create_endpoint_group_with_str_body(self): self._test_create_endpoint_group() def test_create_endpoint_group_with_bytes_body(self): self._test_create_endpoint_group(bytes_body=True) def test_show_endpoint_group_with_str_body(self): self._test_show_endpoint_group() def test_show_endpoint_group_with_bytes_body(self): self._test_show_endpoint_group(bytes_body=True) def test_check_endpoint_group_with_str_body(self): self._test_check_endpoint_group() def test_check_endpoint_group_with_bytes_body(self): self._test_check_endpoint_group(bytes_body=True) def test_list_endpoint_groups_with_str_body(self): self._test_list_endpoint_groups() def test_list_endpoint_groups_with_bytes_body(self): self._test_list_endpoint_groups(bytes_body=True) def test_update_endpoint_group_with_str_body(self): self._test_update_endpoint_group() def test_update_endpoint_group_with_bytes_body(self): self._test_update_endpoint_group(bytes_body=True) def test_delete_endpoint_group(self): self.check_service_client_function( self.client.delete_endpoint_group, 'tempest.lib.common.rest_client.RestClient.delete', {}, endpoint_group_id="1", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_endpoints_client.py0000666000175100017510000001045613207044712027665 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import endpoints_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestEndpointsClient(base.BaseServiceTest): FAKE_CREATE_ENDPOINT = { "endpoint": { "id": 1, "tenantId": 1, "region": "North", "type": "compute", "publicURL": "https://compute.north.public.com/v1", "internalURL": "https://compute.north.internal.com/v1", "adminURL": "https://compute.north.internal.com/v1" } } FAKE_LIST_ENDPOINTS = { "endpoints": [ { "id": 1, "tenantId": "1", "region": "North", "type": "compute", "publicURL": "https://compute.north.public.com/v1", "internalURL": "https://compute.north.internal.com/v1", "adminURL": "https://compute.north.internal.com/v1" }, { "id": 2, "tenantId": "1", "region": "South", "type": "compute", "publicURL": "https://compute.north.public.com/v1", "internalURL": "https://compute.north.internal.com/v1", "adminURL": "https://compute.north.internal.com/v1" } ] } FAKE_SERVICE_ID = "a4dc5060-f757-4662-b658-edd2aefbb41d" def setUp(self): super(TestEndpointsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = endpoints_client.EndPointsClient(fake_auth, 'identity', 'regionOne') def _test_create_endpoint(self, bytes_body=False): self.check_service_client_function( self.client.create_endpoint, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_ENDPOINT, bytes_body, status=201, service_id="b344506af7644f6794d9cb316600b020", region="region-demo", publicurl="https://compute.north.public.com/v1", adminurl="https://compute.north.internal.com/v1", internalurl="https://compute.north.internal.com/v1") def _test_list_endpoints(self, bytes_body=False, mock_args='endpoints', **params): self.check_service_client_function( self.client.list_endpoints, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ENDPOINTS, bytes_body, mock_args=[mock_args], **params) def test_create_endpoint_with_str_body(self): self._test_create_endpoint() def test_create_endpoint_with_bytes_body(self): self._test_create_endpoint(bytes_body=True) def test_list_endpoints_with_str_body(self): self._test_list_endpoints() def test_list_endpoints_with_bytes_body(self): self._test_list_endpoints(bytes_body=True) def test_list_endpoints_with_params(self): # Run the test separately for each param, to avoid assertion error # resulting from randomized params order. mock_args = 'endpoints?service_id=%s' % self.FAKE_SERVICE_ID self._test_list_endpoints(mock_args=mock_args, service_id=self.FAKE_SERVICE_ID) mock_args = 'endpoints?interface=public' self._test_list_endpoints(mock_args=mock_args, interface='public') def test_delete_endpoint(self): self.check_service_client_function( self.client.delete_endpoint, 'tempest.lib.common.rest_client.RestClient.delete', {}, endpoint_id="b344506af7644f6794d9cb316600b020", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_services_client.py0000666000175100017510000001220613207044712027500 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import services_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestServicesClient(base.BaseServiceTest): FAKE_CREATE_SERVICE = { "service": { "type": "compute", "name": "compute2", "description": "Compute service 2" } } FAKE_SERVICE_INFO = { "service": { "description": "Keystone Identity Service", "enabled": True, "id": "686766", "links": { "self": "http://example.com/identity/v3/services/686766" }, "name": "keystone", "type": "identity" } } FAKE_LIST_SERVICES = { "links": { "next": None, "previous": None, "self": "http://example.com/identity/v3/services" }, "services": [ { "description": "Nova Compute Service", "enabled": True, "id": "1999c3", "links": { "self": "http://example.com/identity/v3/services/1999c3" }, "name": "nova", "type": "compute" }, { "description": "Cinder Volume Service V2", "enabled": True, "id": "392166", "links": { "self": "http://example.com/identity/v3/services/392166" }, "name": "cinderv2", "type": "volumev2" }, { "description": "Neutron Service", "enabled": True, "id": "4fe41a", "links": { "self": "http://example.com/identity/v3/services/4fe41a" }, "name": "neutron", "type": "network" } ] } def setUp(self): super(TestServicesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = services_client.ServicesClient(fake_auth, 'identity', 'regionOne') def _test_create_service(self, bytes_body=False): self.check_service_client_function( self.client.create_service, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_SERVICE, bytes_body, status=201) def _test_show_service(self, bytes_body=False): self.check_service_client_function( self.client.show_service, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SERVICE_INFO, bytes_body, service_id="686766") def _test_list_services(self, bytes_body=False, mock_args='services', **params): self.check_service_client_function( self.client.list_services, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_SERVICES, bytes_body, mock_args=[mock_args], **params) def _test_update_service(self, bytes_body=False): self.check_service_client_function( self.client.update_service, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_SERVICE_INFO, bytes_body, service_id="686766") def test_create_service_with_str_body(self): self._test_create_service() def test_create_service_with_bytes_body(self): self._test_create_service(bytes_body=True) def test_show_service_with_str_body(self): self._test_show_service() def test_show_service_with_bytes_body(self): self._test_show_service(bytes_body=True) def test_list_services_with_str_body(self): self._test_list_services() def test_list_services_with_bytes_body(self): self._test_list_services(bytes_body=True) def test_list_services_with_params(self): self._test_list_services( type='fake-type', mock_args='services?type=fake-type') def test_update_service_with_str_body(self): self._test_update_service() def test_update_service_with_bytes_body(self): self._test_update_service(bytes_body=True) def test_delete_service(self): self.check_service_client_function( self.client.delete_service, 'tempest.lib.common.rest_client.RestClient.delete', {}, service_id="686766", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_token_client.py0000666000175100017510000001340313207044712026775 0ustar zuulzuul00000000000000# 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 mock from tempest.lib.common import rest_client from tempest.lib import exceptions from tempest.lib.services.identity.v3 import token_client from tempest.tests import base from tempest.tests.lib import fake_identity class TestTokenClientV3(base.TestCase): 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') response, body_text = fake_identity._fake_v3_response(None, None) body = json.loads(body_text) with mock.patch.object(token_client_v3, 'post') as post_mock: post_mock.return_value = response, body 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.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') response, body_text = fake_identity._fake_v3_response(None, None) body = json.loads(body_text) with mock.patch.object(token_client_v3, 'post') as post_mock: post_mock.return_value = response, body 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.assert_called_once_with('fake_url/auth/tokens', body=req_dict) def test_auth_with_tenant(self): token_client_v3 = token_client.V3TokenClient('fake_url') response, body_text = fake_identity._fake_v3_response(None, None) body = json.loads(body_text) with mock.patch.object(token_client_v3, 'post') as post_mock: post_mock.return_value = response, body resp = token_client_v3.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.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') with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r: mock_raw_r.return_value = ( fake_identity._fake_v3_response(None, None)) resp, body = token_client_v3.request('GET', 'fake_uri') self.assertIsInstance(body, dict) def test_request_with_bytes_body(self): token_client_v3 = token_client.V3TokenClient('fake_url') response, body_text = fake_identity._fake_v3_response(None, None) body = body_text.encode('utf-8') with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r: mock_raw_r.return_value = response, body resp, body = token_client_v3.request('GET', 'fake_uri') self.assertIsInstance(body, dict) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py0000666000175100017510000001337313207044712031050 0ustar zuulzuul00000000000000# Copyright 2017 AT&T 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 tempest.lib.services.identity.v3 import endpoint_filter_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestEndPointsFilterClient(base.BaseServiceTest): FAKE_LIST_PROJECTS_FOR_ENDPOINTS = { "projects": [ { "domain_id": "1777c7", "enabled": True, "id": "1234ab1", "type": "compute", "links": { "self": "http://example.com/identity/v3/projects/1234ab1" }, "name": "Project 1", "description": "Project 1 description", }, { "domain_id": "1777c7", "enabled": True, "id": "5678cd2", "type": "compute", "links": { "self": "http://example.com/identity/v3/projects/5678cd2" }, "name": "Project 2", "description": "Project 2 description", } ], "links": { "self": "http://example.com/identity/v3/OS-EP-FILTER/endpoints/\ u6ay5u/projects", "previous": None, "next": None } } FAKE_LIST_ENDPOINTS_FOR_PROJECTS = { "endpoints": [ { "id": "u6ay5u", "interface": "public", "url": "http://example.com/identity/", "region": "north", "links": { "self": "http://example.com/identity/v3/endpoints/u6ay5u" }, "service_id": "5um4r", }, { "id": "u6ay5u", "interface": "internal", "url": "http://example.com/identity/", "region": "south", "links": { "self": "http://example.com/identity/v3/endpoints/u6ay5u" }, "service_id": "5um4r", }, ], "links": { "self": "http://example.com/identity/v3/OS-EP-FILTER/projects/\ 1234ab1/endpoints", "previous": None, "next": None } } def setUp(self): super(TestEndPointsFilterClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = endpoint_filter_client.EndPointsFilterClient( fake_auth, 'identity', 'regionOne') def _test_add_endpoint_to_project(self, bytes_body=False): self.check_service_client_function( self.client.add_endpoint_to_project, 'tempest.lib.common.rest_client.RestClient.put', {}, bytes_body, status=204, project_id=3, endpoint_id=4) def _test_check_endpoint_in_project(self, bytes_body=False): self.check_service_client_function( self.client.check_endpoint_in_project, 'tempest.lib.common.rest_client.RestClient.head', {}, bytes_body, status=204, project_id=3, endpoint_id=4) def _test_list_projects_for_endpoint(self, bytes_body=False): self.check_service_client_function( self.client.list_projects_for_endpoint, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_PROJECTS_FOR_ENDPOINTS, bytes_body, status=200, endpoint_id=3) def _test_list_endpoints_in_project(self, bytes_body=False): self.check_service_client_function( self.client.list_endpoints_in_project, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ENDPOINTS_FOR_PROJECTS, bytes_body, status=200, project_id=4) def _test_delete_endpoint_from_project(self, bytes_body=False): self.check_service_client_function( self.client.delete_endpoint_from_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, bytes_body, status=204, project_id=3, endpoint_id=4) def test_add_endpoint_to_project_with_str_body(self): self._test_add_endpoint_to_project() def test_add_endpoint_to_project_with_bytes_body(self): self._test_add_endpoint_to_project(bytes_body=True) def test_check_endpoint_in_project_with_str_body(self): self._test_check_endpoint_in_project() def test_check_endpoint_in_project_with_bytes_body(self): self._test_check_endpoint_in_project(bytes_body=True) def test_list_projects_for_endpoint_with_str_body(self): self._test_list_projects_for_endpoint() def test_list_projects_for_endpoint_with_bytes_body(self): self._test_list_projects_for_endpoint(bytes_body=True) def test_list_endpoints_in_project_with_str_body(self): self._test_list_endpoints_in_project() def test_list_endpoints_in_project_with_bytes_body(self): self._test_list_endpoints_in_project(bytes_body=True) def test_delete_endpoint_from_project(self): self._test_delete_endpoint_from_project() tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_role_assignments_client.py0000666000175100017510000001562013207044712031234 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import role_assignments_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestRoleAssignmentsClient(base.BaseServiceTest): FAKE_USER_ID = "313234" FAKE_GROUP_ID = "101112" FAKE_ROLE1_ID = "123456" FAKE_ROLE2_ID = "123457" FAKE_PROJECT_ID = "456789" FAKE_DOMAIN_ID = "102030" FAKE_USER_PROJECT_ASSIGNMENT = { "links": { "assignment": "http://example.com/identity/v3/projects/" "%s/users/%s/roles/%s" % (FAKE_PROJECT_ID, FAKE_USER_ID, FAKE_ROLE2_ID) }, "role": { "id": FAKE_ROLE2_ID }, "scope": { "project": { "id": FAKE_PROJECT_ID } }, "user": { "id": FAKE_USER_ID } } FAKE_GROUP_PROJECT_ASSIGNMENT = { "links": { "assignment": "http://example.com/identity/v3/projects/" "%s/groups/%s/roles/%s" % (FAKE_PROJECT_ID, FAKE_GROUP_ID, FAKE_ROLE1_ID) }, "role": { "id": FAKE_ROLE1_ID }, "scope": { "project": { "id": FAKE_PROJECT_ID } }, "group": { "id": FAKE_GROUP_ID } } FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENT = { "links": { "assignment": "http://example.com/identity/v3/projects/" "%s/groups/%s/roles/%s" % (FAKE_PROJECT_ID, FAKE_GROUP_ID, FAKE_ROLE1_ID), "membership": "http://example.com/identity/v3/groups/" "%s/users/%s" % (FAKE_GROUP_ID, FAKE_USER_ID) }, "role": { "id": FAKE_ROLE1_ID }, "scope": { "project": { "id": FAKE_PROJECT_ID } }, "user": { "id": FAKE_USER_ID } } FAKE_USER_DOMAIN_ASSIGNMENT = { "links": { "assignment": "http://example.com/identity/v3/domains/" "%s/users/%s/roles/%s" % (FAKE_DOMAIN_ID, FAKE_USER_ID, FAKE_ROLE1_ID) }, "role": { "id": FAKE_ROLE1_ID }, "scope": { "domain": { "id": FAKE_DOMAIN_ID } }, "user": { "id": FAKE_USER_ID } } FAKE_GROUP_PROJECT_ASSIGNMENTS = { "role_assignments": [ FAKE_GROUP_PROJECT_ASSIGNMENT ], "links": { "self": "http://example.com/identity/v3/role_assignments?" "scope.project.id=%s&group.id=%s&effective" % ( FAKE_PROJECT_ID, FAKE_GROUP_ID), "previous": None, "next": None } } FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENTS = { "role_assignments": [ FAKE_USER_PROJECT_ASSIGNMENT, FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENT ], "links": { "self": "http://example.com/identity/v3/role_assignments?" "scope.project.id=%s&user.id=%s&effective" % ( FAKE_PROJECT_ID, FAKE_USER_ID), "previous": None, "next": None } } FAKE_USER_DOMAIN_ASSIGNMENTS = { "role_assignments": [ FAKE_USER_DOMAIN_ASSIGNMENT ], "links": { "self": "http://example.com/identity/v3/role_assignments?" "scope.domain.id=%s&user.id=%s&effective" % ( FAKE_DOMAIN_ID, FAKE_USER_ID), "previous": None, "next": None } } def setUp(self): super(TestRoleAssignmentsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = role_assignments_client.RoleAssignmentsClient( fake_auth, 'identity', 'regionOne') def _test_list_user_project_effective_assignments(self, bytes_body=False): params = {'scope.project.id': self.FAKE_PROJECT_ID, 'user.id': self.FAKE_USER_ID} self.check_service_client_function( self.client.list_role_assignments, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENTS, bytes_body, effective=True, **params) def test_list_user_project_effective_assignments_with_str_body(self): self._test_list_user_project_effective_assignments() def test_list_user_project_effective_assignments_with_bytes_body(self): self._test_list_user_project_effective_assignments(bytes_body=True) def _test_list_group_project_assignments(self, bytes_body=False): params = {'scope.project.id': self.FAKE_PROJECT_ID, 'group.id': self.FAKE_GROUP_ID} self.check_service_client_function( self.client.list_role_assignments, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_GROUP_PROJECT_ASSIGNMENTS, bytes_body, **params) def test_list_group_project_assignments_with_str_body(self): self._test_list_group_project_assignments() def test_list_group_project_assignments_with_bytes_body(self): self._test_list_group_project_assignments(bytes_body=True) def _test_list_user_domain_assignments(self, bytes_body=False): params = {'scope.domain.id': self.FAKE_DOMAIN_ID, 'user.id': self.FAKE_USER_ID} self.check_service_client_function( self.client.list_role_assignments, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_GROUP_PROJECT_ASSIGNMENTS, bytes_body, **params) def test_list_user_domain_assignments_with_str_body(self): self._test_list_user_domain_assignments() def test_list_user_domain_assignments_with_bytes_body(self): self._test_list_user_domain_assignments(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_users_client.py0000666000175100017510000001561013207044712027020 0ustar zuulzuul00000000000000# 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 tempest.lib.services.identity.v3 import users_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestUsersClient(base.BaseServiceTest): FAKE_CREATE_USER = { 'user': { 'default_project_id': '95f8c3f8e7b54409a418fc30717f9ae0', 'domain_id': '8347b31afc3545c4b311cb4cce788a08', 'enabled': True, 'name': 'Tempest User', 'password': 'TempestPassword', } } FAKE_USER_INFO = { 'user': { 'default_project_id': '95f8c3f8e7b54409a418fc30717f9ae0', 'domain_id': '8347b31afc3545c4b311cb4cce788a08', 'enabled': True, 'id': '817fb3c23fd7465ba6d7fe1b1320121d', 'links': { 'self': 'http://example.com/identity', }, 'name': 'Tempest User', 'password_expires_at': '2016-11-06T15:32:17.000000', } } FAKE_USER_LIST = { 'links': { 'next': None, 'previous': None, 'self': 'http://example.com/identity/v3/users', }, 'users': [ { 'domain_id': 'TempestDomain', 'enabled': True, 'id': '817fb3c23fd7465ba6d7fe1b1320121d', 'links': { 'self': 'http://example.com/identity/v3/users/' + '817fb3c23fd7465ba6d7fe1b1320121d', }, 'name': 'Tempest User', 'password_expires_at': '2016-11-06T15:32:17.000000', }, { 'domain_id': 'TempestDomain', 'enabled': True, 'id': 'bdbfb1e2f1344be197e90a778379cca1', 'links': { 'self': 'http://example.com/identity/v3/users/' + 'bdbfb1e2f1344be197e90a778379cca1', }, 'name': 'Tempest User', 'password_expires_at': None, }, ] } FAKE_GROUP_LIST = { 'links': { 'self': 'http://example.com/identity/v3/groups', 'previous': None, 'next': None, }, 'groups': [ { 'description': 'Tempest Group One Description', 'domain_id': 'TempestDomain', 'id': '1c92f3453ed34291a074b87493455b8f', 'links': { 'self': 'http://example.com/identity/v3/groups/' + '1c92f3453ed34291a074b87493455b8f' }, 'name': 'Tempest Group One', }, { 'description': 'Tempest Group Two Description', 'domain_id': 'TempestDomain', 'id': 'ce9e7dafed3b4877a7d4466ed730a9ee', 'links': { 'self': 'http://example.com/identity/v3/groups/' + 'ce9e7dafed3b4877a7d4466ed730a9ee' }, 'name': 'Tempest Group Two', }, ] } def setUp(self): super(TestUsersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = users_client.UsersClient(fake_auth, 'identity', 'regionOne') def _test_create_user(self, bytes_body=False): self.check_service_client_function( self.client.create_user, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_USER, bytes_body, status=201, ) def _test_show_user(self, bytes_body=False): self.check_service_client_function( self.client.show_user, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_USER_INFO, bytes_body, user_id='817fb3c23fd7465ba6d7fe1b1320121d', ) def _test_list_users(self, bytes_body=False): self.check_service_client_function( self.client.list_users, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_USER_LIST, bytes_body, ) def _test_update_user(self, bytes_body=False): self.check_service_client_function( self.client.update_user, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_USER_INFO, bytes_body, user_id='817fb3c23fd7465ba6d7fe1b1320121d', name='NewName', ) def _test_list_user_groups(self, bytes_body=False): self.check_service_client_function( self.client.list_user_groups, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_GROUP_LIST, bytes_body, user_id='817fb3c23fd7465ba6d7fe1b1320121d', ) def test_create_user_with_string_body(self): self._test_create_user() def test_create_user_with_bytes_body(self): self._test_create_user(bytes_body=True) def test_show_user_with_string_body(self): self._test_show_user() def test_show_user_with_bytes_body(self): self._test_show_user(bytes_body=True) def test_list_users_with_string_body(self): self._test_list_users() def test_list_users_with_bytes_body(self): self._test_list_users(bytes_body=True) def test_update_user_with_string_body(self): self._test_update_user() def test_update_user_with_bytes_body(self): self._test_update_user(bytes_body=True) def test_list_user_groups_with_string_body(self): self._test_list_user_groups() def test_list_user_groups_with_bytes_body(self): self._test_list_user_groups(bytes_body=True) def test_delete_user(self): self.check_service_client_function( self.client.delete_user, 'tempest.lib.common.rest_client.RestClient.delete', {}, user_id='817fb3c23fd7465ba6d7fe1b1320121d', status=204, ) def test_change_user_password(self): self.check_service_client_function( self.client.update_user_password, 'tempest.lib.common.rest_client.RestClient.post', {}, status=204, user_id='817fb3c23fd7465ba6d7fe1b1320121d', password='NewTempestPassword', original_password='OldTempestPassword') tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_oauth_consumers_client.py0000666000175100017510000001313613207044712031076 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.identity.v3 import oauth_consumers_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestOAUTHConsumerClient(base.BaseServiceTest): FAKE_CREATE_CONSUMER = { "consumer": { 'description': 'A fake description 1' } } FAKE_CONSUMER_INFO = { "consumer": { 'id': '6392c7d3b7a2062e09a07aa377', 'links': { 'self': 'http://example.com/identity/v3/' + 'OS-OAUTH1/consumers/g6f2l9' }, 'description': 'A description that is fake' } } FAKE_LIST_CONSUMERS = { 'links': { 'self': 'http://example.com/identity/v3/OS-OAUTH1/consumers/', 'next': None, 'previous': None }, 'consumers': [ { 'id': '6392c7d3b7a2062e09a07aa377', 'links': { 'self': 'http://example.com/identity/v3/' + 'OS-OAUTH1/consumers/6b9f2g5' }, 'description': 'A description that is fake' }, { 'id': '677a855c9e3eb3a3954b36aca6', 'links': { 'self': 'http://example.com/identity/v3/' + 'OS-OAUTH1/consumers/6a9f2366' }, 'description': 'A very fake description 2' }, { 'id': '9d3ac57b08d65e07826b5e506', 'links': { 'self': 'http://example.com/identity/v3/' + 'OS-OAUTH1/consumers/626b5e506' }, 'description': 'A very fake description 3' }, { 'id': 'b522d163b1a18e928aca9y426', 'links': { 'self': 'http://example.com/identity/v3/' + 'OS-OAUTH1/consumers/g7ca9426' }, 'description': 'A very fake description 4' }, { 'id': 'b7e47321b5ef9051f93c2049e', 'links': { 'self': 'http://example.com/identity/v3/' + 'OS-OAUTH1/consumers/23d82049e' }, 'description': 'A very fake description 5' } ] } def setUp(self): super(TestOAUTHConsumerClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = oauth_consumers_client.OAUTHConsumerClient(fake_auth, 'identity', 'regionOne') def _test_create_consumer(self, bytes_body=False): self.check_service_client_function( self.client.create_consumer, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_CONSUMER, bytes_body, description=self.FAKE_CREATE_CONSUMER["consumer"]["description"], status=201) def _test_show_consumer(self, bytes_body=False): self.check_service_client_function( self.client.show_consumer, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CONSUMER_INFO, bytes_body, consumer_id="6392c7d3b7a2062e09a07aa377") def _test_list_consumers(self, bytes_body=False): self.check_service_client_function( self.client.list_consumers, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_CONSUMERS, bytes_body) def _test_update_consumer(self, bytes_body=False): self.check_service_client_function( self.client.update_consumer, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_CONSUMER_INFO, bytes_body, consumer_id="6392c7d3b7a2062e09a07aa377") def test_create_consumer_with_str_body(self): self._test_create_consumer() def test_create_consumer_with_bytes_body(self): self._test_create_consumer(bytes_body=True) def test_show_consumer_with_str_body(self): self._test_show_consumer() def test_show_consumer_with_bytes_body(self): self._test_show_consumer(bytes_body=True) def test_list_consumers_with_str_body(self): self._test_list_consumers() def test_list_consumers_with_bytes_body(self): self._test_list_consumers(bytes_body=True) def test_update_consumer_with_str_body(self): self._test_update_consumer() def test_update_consumer_with_bytes_body(self): self._test_update_consumer(bytes_body=True) def test_delete_consumer(self): self.check_service_client_function( self.client.delete_consumer, 'tempest.lib.common.rest_client.RestClient.delete', {}, consumer_id="6392c7d3b7a2062e09a07aa377", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v3/test_catalog_client.py0000666000175100017510000000575113207044712027276 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT 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.identity.v3 import catalog_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestCatalogClient(base.BaseServiceTest): FAKE_CATALOG_INFO = { 'catalog': [ { 'endpoints': [ { 'id': '39dc322ce86c4111b4f06c2eeae0841b', 'interface': 'public', 'region': 'RegionOne', 'url': 'http://localhost:5000' }, ], 'id': 'ac58672276f848a7b1727850b3ebe826', 'type': 'compute', 'name': 'nova' }, { 'endpoints': [ { 'id': '39dc322ce86c4111b4f06c2eeae0841b', 'interface': 'public', 'region': 'RegionOne', 'url': 'http://localhost:5000' }, ], 'id': 'b7c5ed2b486a46dbb4c221499d22991c', 'type': 'image', 'name': 'glance' }, { 'endpoints': [ { 'id': '39dc322ce86c4111b4f06c2eeae0841b', 'interface': 'public', 'region': 'RegionOne', 'url': 'http://localhost:5000' }, ], 'id': '4363ae44bdf34a3981fde3b823cb9aa2', 'type': 'identity', 'name': 'keystone' } ], 'links': { 'self': 'http://localhost/identity/v3/catalog', 'previous': None, 'next': None } } def setUp(self): super(TestCatalogClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = catalog_client.CatalogClient(fake_auth, 'identity', 'RegionOne') def test_show_catalog_with_bytes_body(self): self._test_show_catalog(bytes_body=True) def test_show_catalog_with_str_body(self): self._test_show_catalog() def _test_show_catalog(self, bytes_body=False): self.check_service_client_function( self.client.show_catalog, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_CATALOG_INFO, bytes_body) tempest-17.2.0/tempest/tests/lib/services/identity/v2/0000775000175100017510000000000013207045130022675 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_identity_client.py0000666000175100017510000002146413207044712027513 0ustar zuulzuul00000000000000# 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.services.identity.v2 import identity_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestIdentityClient(base.BaseServiceTest): FAKE_TOKEN = { "tokens": { "id": "cbc36478b0bd8e67e89", "name": "FakeToken", "type": "token", } } FAKE_ENDPOINTS_FOR_TOKEN = { "endpoints_links": [], "endpoints": [ { "name": "nova", "adminURL": "https://nova.region-one.internal.com/" + "v2/be1319401cfa4a0aa590b97cc7b64d8d", "region": "RegionOne", "internalURL": "https://nova.region-one.internal.com/" + "v2/be1319401cfa4a0aa590b97cc7b64d8d", "type": "compute", "id": "11b41ee1b00841128b7333d4bf1a6140", "publicURL": "https://nova.region-one.public.com/v2/" + "be1319401cfa4a0aa590b97cc7b64d8d" }, { "name": "neutron", "adminURL": "https://neutron.region-one.internal.com/", "region": "RegionOne", "internalURL": "https://neutron.region-one.internal.com/", "type": "network", "id": "cdbfa3c416d741a9b5c968f2dc628acb", "publicURL": "https://neutron.region-one.public.com/" } ] } FAKE_API_INFO = { "name": "API_info", "type": "API", "description": "test_description" } FAKE_LIST_EXTENSIONS = { "extensions": { "values": [ { "updated": "2013-07-07T12:00:0-00:00", "name": "OpenStack S3 API", "links": [ { "href": "https://github.com/openstack/" + "identity-api", "type": "text/html", "rel": "describedby" } ], "namespace": "http://docs.openstack.org/identity/" + "api/ext/s3tokens/v1.0", "alias": "s3tokens", "description": "OpenStack S3 API." }, { "updated": "2013-12-17T12:00:0-00:00", "name": "OpenStack Federation APIs", "links": [ { "href": "https://github.com/openstack/" + "identity-api", "type": "text/html", "rel": "describedby" } ], "namespace": "http://docs.openstack.org/identity/" + "api/ext/OS-FEDERATION/v1.0", "alias": "OS-FEDERATION", "description": "OpenStack Identity Providers Mechanism." }, { "updated": "2014-01-20T12:00:0-00:00", "name": "OpenStack Simple Certificate API", "links": [ { "href": "https://github.com/openstack/" + "identity-api", "type": "text/html", "rel": "describedby" } ], "namespace": "http://docs.openstack.org/identity/api/" + "ext/OS-SIMPLE-CERT/v1.0", "alias": "OS-SIMPLE-CERT", "description": "OpenStack simple certificate extension" }, { "updated": "2013-07-07T12:00:0-00:00", "name": "OpenStack OAUTH1 API", "links": [ { "href": "https://github.com/openstack/" + "identity-api", "type": "text/html", "rel": "describedby" } ], "namespace": "http://docs.openstack.org/identity/" + "api/ext/OS-OAUTH1/v1.0", "alias": "OS-OAUTH1", "description": "OpenStack OAuth Delegated Auth Mechanism." }, { "updated": "2013-07-07T12:00:0-00:00", "name": "OpenStack EC2 API", "links": [ { "href": "https://github.com/openstack/" + "identity-api", "type": "text/html", "rel": "describedby" } ], "namespace": "http://docs.openstack.org/identity/api/" + "ext/OS-EC2/v1.0", "alias": "OS-EC2", "description": "OpenStack EC2 Credentials backend." } ] } } def setUp(self): super(TestIdentityClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = identity_client.IdentityClient(fake_auth, 'identity', 'regionOne') def _test_show_api_description(self, bytes_body=False): self.check_service_client_function( self.client.show_api_description, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_API_INFO, bytes_body) def _test_list_extensions(self, bytes_body=False): self.check_service_client_function( self.client.list_extensions, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_EXTENSIONS, bytes_body) def _test_show_token(self, bytes_body=False): self.check_service_client_function( self.client.show_token, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_TOKEN, bytes_body, token_id="cbc36478b0bd8e67e89") def _test_list_endpoints_for_token(self, bytes_body=False): self.check_service_client_function( self.client.list_endpoints_for_token, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ENDPOINTS_FOR_TOKEN, bytes_body, token_id="cbc36478b0bd8e67e89") def _test_check_token_existence(self, bytes_body=False): self.check_service_client_function( self.client.check_token_existence, 'tempest.lib.common.rest_client.RestClient.head', {}, bytes_body, token_id="cbc36478b0bd8e67e89") def test_show_api_description_with_str_body(self): self._test_show_api_description() def test_show_api_description_with_bytes_body(self): self._test_show_api_description(bytes_body=True) def test_show_list_extensions_with_str_body(self): self._test_list_extensions() def test_show_list_extensions_with_bytes_body(self): self._test_list_extensions(bytes_body=True) def test_show_token_with_str_body(self): self._test_show_token() def test_show_token_with_bytes_body(self): self._test_show_token(bytes_body=True) def test_list_endpoints_for_token_with_str_body(self): self._test_list_endpoints_for_token() def test_list_endpoints_for_token_with_bytes_body(self): self._test_list_endpoints_for_token(bytes_body=True) def test_check_token_existence_with_bytes_body(self): self._test_check_token_existence(bytes_body=True) def test_check_token_existence_with_str_body(self): self._test_check_token_existence() def test_delete_token(self): self.check_service_client_function( self.client.delete_token, 'tempest.lib.common.rest_client.RestClient.delete', {}, token_id="cbc36478b0bd8e67e89", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v2/__init__.py0000666000175100017510000000000013207044712025003 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_roles_client.py0000666000175100017510000001132213207044712026776 0ustar zuulzuul00000000000000# 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.services.identity.v2 import roles_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestRolesClient(base.BaseServiceTest): FAKE_ROLE_INFO = { "role": { "id": "1", "name": "test", "description": "test_description" } } FAKE_LIST_ROLES = { "roles": [ { "id": "1", "name": "test", "description": "test_description" }, { "id": "2", "name": "test2", "description": "test2_description" } ] } def setUp(self): super(TestRolesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = roles_client.RolesClient(fake_auth, 'identity', 'regionOne') def _test_create_role(self, bytes_body=False): self.check_service_client_function( self.client.create_role, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_ROLE_INFO, bytes_body, id="1", name="test", description="test_description") def _test_show_role(self, bytes_body=False): self.check_service_client_function( self.client.show_role, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_ROLE_INFO, bytes_body, role_id_or_name="1") def _test_list_roles(self, bytes_body=False): self.check_service_client_function( self.client.list_roles, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body) def _test_create_user_role_on_project(self, bytes_body=False): self.check_service_client_function( self.client.create_user_role_on_project, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_ROLE_INFO, bytes_body, tenant_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=200) def _test_list_user_roles_on_project(self, bytes_body=False): self.check_service_client_function( self.client.list_user_roles_on_project, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ROLES, bytes_body, tenant_id="b344506af7644f6794d9cb316600b020", user_id="123") def test_create_role_with_str_body(self): self._test_create_role() def test_create_role_with_bytes_body(self): self._test_create_role(bytes_body=True) def test_show_role_with_str_body(self): self._test_show_role() def test_show_role_with_bytes_body(self): self._test_show_role(bytes_body=True) def test_list_roles_with_str_body(self): self._test_list_roles() def test_list_roles_with_bytes_body(self): self._test_list_roles(bytes_body=True) def test_delete_role(self): self.check_service_client_function( self.client.delete_role, 'tempest.lib.common.rest_client.RestClient.delete', {}, role_id="1", status=204) def test_create_user_role_on_project_with_str_body(self): self._test_create_user_role_on_project() def test_create_user_role_on_project_with_bytes_body(self): self._test_create_user_role_on_project(bytes_body=True) def test_list_user_roles_on_project_with_str_body(self): self._test_list_user_roles_on_project() def test_list_user_roles_on_project_with_bytes_body(self): self._test_list_user_roles_on_project(bytes_body=True) def test_delete_role_from_user_on_project(self): self.check_service_client_function( self.client.delete_role_from_user_on_project, 'tempest.lib.common.rest_client.RestClient.delete', {}, tenant_id="b344506af7644f6794d9cb316600b020", user_id="123", role_id="1234", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_endpoints_client.py0000666000175100017510000000722313207044712027662 0ustar zuulzuul00000000000000# 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.services.identity.v2 import endpoints_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestEndpointsClient(base.BaseServiceTest): FAKE_CREATE_ENDPOINT = { "endpoint": { "id": 1, "tenantId": 1, "region": "North", "type": "compute", "publicURL": "https://compute.north.public.com/v1", "internalURL": "https://compute.north.internal.com/v1", "adminURL": "https://compute.north.internal.com/v1" } } FAKE_LIST_ENDPOINTS = { "endpoints": [ { "id": 1, "tenantId": "1", "region": "North", "type": "compute", "publicURL": "https://compute.north.public.com/v1", "internalURL": "https://compute.north.internal.com/v1", "adminURL": "https://compute.north.internal.com/v1" }, { "id": 2, "tenantId": "1", "region": "South", "type": "compute", "publicURL": "https://compute.north.public.com/v1", "internalURL": "https://compute.north.internal.com/v1", "adminURL": "https://compute.north.internal.com/v1" } ] } def setUp(self): super(TestEndpointsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = endpoints_client.EndpointsClient(fake_auth, 'identity', 'regionOne') def _test_create_endpoint(self, bytes_body=False): self.check_service_client_function( self.client.create_endpoint, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_ENDPOINT, bytes_body, service_id="b344506af7644f6794d9cb316600b020", region="region-demo", publicurl="https://compute.north.public.com/v1", adminurl="https://compute.north.internal.com/v1", internalurl="https://compute.north.internal.com/v1") def _test_list_endpoints(self, bytes_body=False): self.check_service_client_function( self.client.list_endpoints, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_ENDPOINTS, bytes_body) def test_create_endpoint_with_str_body(self): self._test_create_endpoint() def test_create_endpoint_with_bytes_body(self): self._test_create_endpoint(bytes_body=True) def test_list_endpoints_with_str_body(self): self._test_list_endpoints() def test_list_endpoints_with_bytes_body(self): self._test_list_endpoints(bytes_body=True) def test_delete_endpoint(self): self.check_service_client_function( self.client.delete_endpoint, 'tempest.lib.common.rest_client.RestClient.delete', {}, endpoint_id="b344506af7644f6794d9cb316600b020", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_services_client.py0000666000175100017510000000625213207044712027503 0ustar zuulzuul00000000000000# 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.services.identity.v2 import services_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestServicesClient(base.BaseServiceTest): FAKE_SERVICE_INFO = { "OS-KSADM:service": { "id": "1", "name": "test", "type": "compute", "description": "test_description" } } FAKE_LIST_SERVICES = { "OS-KSADM:services": [ { "id": "1", "name": "test", "type": "compute", "description": "test_description" }, { "id": "2", "name": "test2", "type": "compute", "description": "test2_description" } ] } def setUp(self): super(TestServicesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = services_client.ServicesClient(fake_auth, 'identity', 'regionOne') def _test_create_service(self, bytes_body=False): self.check_service_client_function( self.client.create_service, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SERVICE_INFO, bytes_body, id="1", name="test", type="compute", description="test_description") def _test_show_service(self, bytes_body=False): self.check_service_client_function( self.client.show_service, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SERVICE_INFO, bytes_body, service_id="1") def _test_list_services(self, bytes_body=False): self.check_service_client_function( self.client.list_services, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_SERVICES, bytes_body) def test_create_service_with_str_body(self): self._test_create_service() def test_create_service_with_bytes_body(self): self._test_create_service(bytes_body=True) def test_show_service_with_str_body(self): self._test_show_service() def test_show_service_with_bytes_body(self): self._test_show_service(bytes_body=True) def test_delete_service(self): self.check_service_client_function( self.client.delete_service, 'tempest.lib.common.rest_client.RestClient.delete', {}, service_id="1", status=204) tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_token_client.py0000666000175100017510000000735713207044712027007 0ustar zuulzuul00000000000000# 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 mock from tempest.lib.common import rest_client from tempest.lib import exceptions from tempest.lib.services.identity.v2 import token_client from tempest.tests import base from tempest.tests.lib import fake_http class TestTokenClientV2(base.TestCase): 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') response = fake_http.fake_http_response( None, status=200, ) body = {'access': {'token': 'fake_token'}} with mock.patch.object(token_client_v2, 'post') as post_mock: post_mock.return_value = response, body 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.assert_called_once_with('fake_url/tokens', body=req_dict) def test_auth_with_tenant(self): token_client_v2 = token_client.TokenClient('fake_url') response = fake_http.fake_http_response( None, status=200, ) body = {'access': {'token': 'fake_token'}} with mock.patch.object(token_client_v2, 'post') as post_mock: post_mock.return_value = response, body 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.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') response = fake_http.fake_http_response( None, status=200, ) body = str('{"access": {"token": "fake_token"}}') with mock.patch.object(token_client_v2, 'raw_request') as mock_raw_r: mock_raw_r.return_value = response, body resp, body = token_client_v2.request('GET', 'fake_uri') self.assertIsInstance(body, dict) def test_request_with_bytes_body(self): token_client_v2 = token_client.TokenClient('fake_url') response = fake_http.fake_http_response( None, status=200, ) body = b'{"access": {"token": "fake_token"}}' with mock.patch.object(token_client_v2, 'raw_request') as mock_raw_r: mock_raw_r.return_value = response, body resp, body = token_client_v2.request('GET', 'fake_uri') self.assertIsInstance(body, dict) tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_tenants_client.py0000666000175100017510000001040013207044712027322 0ustar zuulzuul00000000000000# 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.services.identity.v2 import tenants_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestTenantsClient(base.BaseServiceTest): FAKE_TENANT_INFO = { "tenant": { "id": "1", "name": "test", "description": "test_description", "enabled": True } } FAKE_LIST_TENANTS = { "tenants": [ { "id": "1", "name": "test", "description": "test_description", "enabled": True }, { "id": "2", "name": "test2", "description": "test2_description", "enabled": True } ] } def setUp(self): super(TestTenantsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = tenants_client.TenantsClient(fake_auth, 'identity', 'regionOne') def _test_create_tenant(self, bytes_body=False): self.check_service_client_function( self.client.create_tenant, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_TENANT_INFO, bytes_body, name="test", description="test_description") def _test_show_tenant(self, bytes_body=False): self.check_service_client_function( self.client.show_tenant, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_TENANT_INFO, bytes_body, tenant_id="1") def _test_update_tenant(self, bytes_body=False): self.check_service_client_function( self.client.update_tenant, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_TENANT_INFO, bytes_body, tenant_id="1", name="test", description="test_description") def _test_list_tenants(self, bytes_body=False): self.check_service_client_function( self.client.list_tenants, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_TENANTS, bytes_body) def _test_list_tenant_users(self, bytes_body=False): self.check_service_client_function( self.client.list_tenant_users, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_TENANTS, bytes_body, tenant_id="1") def test_create_tenant_with_str_body(self): self._test_create_tenant() def test_create_tenant_with_bytes_body(self): self._test_create_tenant(bytes_body=True) def test_show_tenant_with_str_body(self): self._test_show_tenant() def test_show_tenant_with_bytes_body(self): self._test_show_tenant(bytes_body=True) def test_update_tenant_with_str_body(self): self._test_update_tenant() def test_update_tenant_with_bytes_body(self): self._test_update_tenant(bytes_body=True) def test_list_tenants_with_str_body(self): self._test_list_tenants() def test_list_tenants_with_bytes_body(self): self._test_list_tenants(bytes_body=True) def test_delete_tenant(self): self.check_service_client_function( self.client.delete_tenant, 'tempest.lib.common.rest_client.RestClient.delete', {}, tenant_id="1", status=204) def test_list_tenant_users_with_str_body(self): self._test_list_tenant_users() def test_list_tenant_users_with_bytes_body(self): self._test_list_tenant_users(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/identity/v2/test_users_client.py0000666000175100017510000002034013207044712027013 0ustar zuulzuul00000000000000# 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.services.identity.v2 import users_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestUsersClient(base.BaseServiceTest): FAKE_USER_INFO = { "user": { "id": "1", "name": "test", "email": "john.smith@example.org", "enabled": True } } FAKE_LIST_USERS = { "users": [ { "id": "1", "name": "test", "email": "john.smith@example.org", "enabled": True }, { "id": "2", "name": "test2", "email": "john.smith@example.org", "enabled": True } ] } FAKE_USER_EC2_CREDENTIAL_INFO = { "credential": { 'user_id': '9beb0e12f3e5416db8d7cccfc785db3b', 'access': '79abf59acc77492a86170cbe2f1feafa', 'secret': 'c4e7d3a691fd4563873d381a40320f46', 'trust_id': None, 'tenant_id': '596557269d7b4dd78631a602eb9f151d' } } FAKE_LIST_USER_EC2_CREDENTIALS = { "credentials": [ { 'user_id': '9beb0e12f3e5416db8d7cccfc785db3b', 'access': '79abf59acc77492a86170cbe2f1feafa', 'secret': 'c4e7d3a691fd4563873d381a40320f46', 'trust_id': None, 'tenant_id': '596557269d7b4dd78631a602eb9f151d' }, { 'user_id': '3beb0e12f3e5416db8d7cccfc785de4r', 'access': '45abf59acc77492a86170cbe2f1fesde', 'secret': 'g4e7d3a691fd4563873d381a40320e45', 'trust_id': None, 'tenant_id': '123557269d7b4dd78631a602eb9f112f' } ] } def setUp(self): super(TestUsersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = users_client.UsersClient(fake_auth, 'identity', 'regionOne') def _test_create_user(self, bytes_body=False): self.check_service_client_function( self.client.create_user, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_USER_INFO, bytes_body, name="test", email="john.smith@example.org") def _test_update_user(self, bytes_body=False): self.check_service_client_function( self.client.update_user, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_USER_INFO, bytes_body, user_id="1", name="test") def _test_show_user(self, bytes_body=False): self.check_service_client_function( self.client.show_user, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_USER_INFO, bytes_body, user_id="1") def _test_list_users(self, bytes_body=False): self.check_service_client_function( self.client.list_users, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_USERS, bytes_body) def _test_update_user_enabled(self, bytes_body=False): self.check_service_client_function( self.client.update_user_enabled, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_USER_INFO, bytes_body, user_id="1", enabled=True) def _test_update_user_password(self, bytes_body=False): self.check_service_client_function( self.client.update_user_password, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_USER_INFO, bytes_body, user_id="1", password="pass") def _test_update_user_own_password(self, bytes_body=False): self.check_service_client_function( self.client.update_user_own_password, 'tempest.lib.common.rest_client.RestClient.patch', self.FAKE_USER_INFO, bytes_body, user_id="1", password="pass") def _test_create_user_ec2_credential(self, bytes_body=False): self.check_service_client_function( self.client.create_user_ec2_credential, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_USER_EC2_CREDENTIAL_INFO, bytes_body, user_id="1", tenant_id="123") def _test_show_user_ec2_credential(self, bytes_body=False): self.check_service_client_function( self.client.show_user_ec2_credential, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_USER_EC2_CREDENTIAL_INFO, bytes_body, user_id="1", access="123") def _test_list_user_ec2_credentials(self, bytes_body=False): self.check_service_client_function( self.client.list_user_ec2_credentials, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_USER_EC2_CREDENTIALS, bytes_body, user_id="1") def test_create_user_with_str_body(self): self._test_create_user() def test_create_user_with_bytes_body(self): self._test_create_user(bytes_body=True) def test_update_user_with_str_body(self): self._test_update_user() def test_update_user_with_bytes_body(self): self._test_update_user(bytes_body=True) def test_show_user_with_str_body(self): self._test_show_user() def test_show_user_with_bytes_body(self): self._test_show_user(bytes_body=True) def test_list_users_with_str_body(self): self._test_list_users() def test_list_users_with_bytes_body(self): self._test_list_users(bytes_body=True) def test_delete_user(self): self.check_service_client_function( self.client.delete_user, 'tempest.lib.common.rest_client.RestClient.delete', {}, user_id="1", status=204) def test_update_user_enabled_with_str_body(self): self._test_update_user_enabled() def test_update_user_enabled_with_bytes_body(self): self._test_update_user_enabled(bytes_body=True) def test_update_user_password_with_str_body(self): self._test_update_user_password() def test_update_user_password_with_bytes_body(self): self._test_update_user_password(bytes_body=True) def test_update_user_own_password_with_str_body(self): self._test_update_user_own_password() def test_update_user_own_password_with_bytes_body(self): self._test_update_user_own_password(bytes_body=True) def test_create_user_ec2_credential_with_str_body(self): self._test_create_user_ec2_credential() def test_create_user_ec2_credential_with_bytes_body(self): self._test_create_user_ec2_credential(bytes_body=True) def test_show_user_ec2_credential_with_str_body(self): self._test_show_user_ec2_credential() def test_show_user_ec2_credential_with_bytes_body(self): self._test_show_user_ec2_credential(bytes_body=True) def test_list_user_ec2_credentials_with_str_body(self): self._test_list_user_ec2_credentials() def test_list_user_ec2_credentials_with_bytes_body(self): self._test_list_user_ec2_credentials(bytes_body=True) def test_delete_user_ec2_credential(self): self.check_service_client_function( self.client.delete_user_ec2_credential, 'tempest.lib.common.rest_client.RestClient.delete', {}, user_id="123", access="1234", status=204) tempest-17.2.0/tempest/tests/lib/services/__init__.py0000666000175100017510000000000013207044712022623 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/base.py0000666000175100017510000000655613207044712022024 0ustar zuulzuul00000000000000# 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 fixtures from oslo_serialization import jsonutils as json from tempest.tests import base from tempest.tests.lib import fake_http class BaseServiceTest(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 = fake_http.fake_http_response(headers, status=status), json_body return resp def check_service_client_function(self, function, function2mock, body, to_utf=False, status=200, headers=None, mock_args=None, resp_as_string=False, **kwargs): """Mock a service client function for unit testing. :param function: The service client function to call. :param function2mock: The REST call to mock inside the service client function. :param body: Expected response body returned by the service client function. :param to_utf: Whether to use UTF-8 encoding for response. :param status: Expected response status returned by the service client function. :param headers: Expected headers returned by the service client function. :param mock_args: List/dict/value of expected args/kwargs called by function2mock. For example: * If mock_args=['foo'] then ``assert_called_once_with('foo')`` is called. * If mock_args={'foo': 'bar'} then ``assert_called_once_with(foo='bar')`` is called. * If mock_args='foo' then ``assert_called_once_with('foo')`` is called. :param resp_as_string: Whether response body is retruned as string. This is for service client methods which return ResponseBodyData object. :param kwargs: kwargs that are passed to function. """ mocked_response = self.create_response(body, to_utf, status, headers) fixture = self.useFixture(fixtures.MockPatch( function2mock, return_value=mocked_response)) if kwargs: resp = function(**kwargs) else: resp = function() if resp_as_string: resp = resp.data self.assertEqual(body, resp) if isinstance(mock_args, list): fixture.mock.assert_called_once_with(*mock_args) elif isinstance(mock_args, dict): fixture.mock.assert_called_once_with(**mock_args) elif mock_args is not None: fixture.mock.assert_called_once_with(mock_args) tempest-17.2.0/tempest/tests/lib/services/volume/0000775000175100017510000000000013207045130022024 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v1/0000775000175100017510000000000013207045130022352 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v1/test_quotas_client.py0000666000175100017510000000655513207044712026657 0ustar zuulzuul00000000000000# 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.services.volume.v1 import quotas_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQuotasClient(base.BaseServiceTest): FAKE_QUOTAS = { "quota_set": { "cores": 20, "fixed_ips": -1, "floating_ips": 10, "id": "fake_tenant", "injected_file_content_bytes": 10240, "injected_file_path_bytes": 255, "injected_files": 5, "instances": 10, "key_pairs": 100, "metadata_items": 128, "ram": 51200, "security_group_rules": 20, "security_groups": 10 } } FAKE_UPDATE_QUOTAS_REQUEST = { "quota_set": { "security_groups": 45 } } def setUp(self): super(TestQuotasClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = quotas_client.QuotasClient(fake_auth, 'volume', 'regionOne') 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_QUOTAS, bytes_body, tenant_id="fake_tenant") def _test_show_quota_set(self, bytes_body=False): self.check_service_client_function( self.client.show_quota_set, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_QUOTAS, bytes_body, tenant_id="fake_tenant") def _test_update_quota_set(self, bytes_body=False): self.check_service_client_function( self.client.update_quota_set, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_QUOTAS_REQUEST, bytes_body, tenant_id="fake_tenant") 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_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_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_delete_quota_set(self): self.check_service_client_function( self.client.delete_quota_set, 'tempest.lib.common.rest_client.RestClient.delete', {}, tenant_id="fake_tenant") tempest-17.2.0/tempest/tests/lib/services/volume/v1/__init__.py0000666000175100017510000000000013207044712024460 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v1/test_encryption_types_client.py0000666000175100017510000000643213207044712030753 0ustar zuulzuul00000000000000# 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.services.volume.v1 import encryption_types_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestEncryptionTypesClient(base.BaseServiceTest): FAKE_CREATE_ENCRYPTION_TYPE = { "encryption": { "id": "cbc36478b0bd8e67e89", "name": "FakeEncryptionType", "type": "fakeType", "provider": "LuksEncryptor", "cipher": "aes-xts-plain64", "key_size": "512", "control_location": "front-end" } } FAKE_INFO_ENCRYPTION_TYPE = { "encryption": { "name": "FakeEncryptionType", "type": "fakeType", "description": "test_description", "volume_type": "fakeType", "provider": "LuksEncryptor", "cipher": "aes-xts-plain64", "key_size": "512", "control_location": "front-end" } } def setUp(self): super(TestEncryptionTypesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = encryption_types_client.EncryptionTypesClient(fake_auth, 'volume', 'regionOne' ) def _test_create_encryption(self, bytes_body=False): self.check_service_client_function( self.client.create_encryption_type, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_ENCRYPTION_TYPE, bytes_body, volume_type_id="cbc36478b0bd8e67e89") def _test_show_encryption_type(self, bytes_body=False): self.check_service_client_function( self.client.show_encryption_type, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_ENCRYPTION_TYPE, bytes_body, volume_type_id="cbc36478b0bd8e67e89") def test_create_encryption_type_with_str_body(self): self._test_create_encryption() def test_create_encryption_type_with_bytes_body(self): self._test_create_encryption(bytes_body=True) def test_show_encryption_type_with_str_body(self): self._test_show_encryption_type() def test_show_encryption_type_with_bytes_body(self): self._test_show_encryption_type(bytes_body=True) def test_delete_encryption_type(self): self.check_service_client_function( self.client.delete_encryption_type, 'tempest.lib.common.rest_client.RestClient.delete', {}, volume_type_id="cbc36478b0bd8e67e89", status=202) tempest-17.2.0/tempest/tests/lib/services/volume/v1/test_snapshots_client.py0000666000175100017510000001643013207044712027356 0ustar zuulzuul00000000000000# 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.services.volume.v1 import snapshots_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSnapshotsClient(base.BaseServiceTest): FAKE_CREATE_SNAPSHOT = { "snapshot": { "display_name": "snap-001", "display_description": "Daily backup", "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "force": True } } FAKE_UPDATE_SNAPSHOT_REQUEST = { "metadata": { "key": "v1" } } FAKE_INFO_SNAPSHOT = { "snapshot": { "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5", "display_name": "snap-001", "display_description": "Daily backup", "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "status": "available", "size": 30, "created_at": "2012-02-29T03:50:07Z" } } FAKE_LIST_SNAPSHOTS = { "snapshots": [ { "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5", "display_name": "snap-001", "display_description": "Daily backup", "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "status": "available", "size": 30, "created_at": "2012-02-29T03:50:07Z", "metadata": { "contents": "junk" } }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "display_name": "snap-002", "display_description": "Weekly backup", "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", "status": "available", "size": 25, "created_at": "2012-03-19T01:52:47Z", "metadata": {} } ] } def setUp(self): super(TestSnapshotsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = snapshots_client.SnapshotsClient(fake_auth, 'volume', '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', self.FAKE_CREATE_SNAPSHOT, bytes_body) def _test_show_snapshot(self, bytes_body=False): self.check_service_client_function( self.client.show_snapshot, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_SNAPSHOT, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_list_snapshots(self, bytes_body=False): self.check_service_client_function( self.client.list_snapshots, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_SNAPSHOTS, bytes_body, detail=True) def _test_create_snapshot_metadata(self, bytes_body=False): self.check_service_client_function( self.client.create_snapshot_metadata, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_INFO_SNAPSHOT, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5", metadata={"key": "v1"}) def _test_update_snapshot(self, bytes_body=False): self.check_service_client_function( self.client.update_snapshot, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_SNAPSHOT_REQUEST, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_show_snapshot_metadata(self, bytes_body=False): self.check_service_client_function( self.client.show_snapshot_metadata, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_UPDATE_SNAPSHOT_REQUEST, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_update_snapshot_metadata(self, bytes_body=False): self.check_service_client_function( self.client.update_snapshot_metadata, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_SNAPSHOT_REQUEST, bytes_body, snapshot_id="cbc36478b0bd8e67e89") def _test_update_snapshot_metadata_item(self, bytes_body=False): self.check_service_client_function( self.client.update_snapshot_metadata_item, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_INFO_SNAPSHOT, bytes_body, volume_type_id="cbc36478b0bd8e67e89") def test_create_snapshot_with_str_body(self): self._test_create_snapshot() def test_create_snapshot_with_bytes_body(self): self._test_create_snapshot(bytes_body=True) 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_with_str_body(self): self._test_list_snapshots() def test_list_snapshots_with_bytes_body(self): self._test_list_snapshots(bytes_body=True) def test_create_snapshot_metadata_with_str_body(self): self._test_create_snapshot_metadata() def test_create_snapshot_metadata_with_bytes_body(self): self._test_create_snapshot_metadata(bytes_body=True) def test_update_snapshot_with_str_body(self): self._test_update_snapshot() def test_update_snapshot_with_bytes_body(self): self._test_update_snapshot(bytes_body=True) def test_show_snapshot_metadata_with_str_body(self): self._test_show_snapshot_metadata() def test_show_snapshot_metadata_with_bytes_body(self): self._test_show_snapshot_metadata(bytes_body=True) def test_update_snapshot_metadata_with_str_body(self): self._test_update_snapshot_metadata() def test_update_snapshot_metadata_with_bytes_body(self): self._test_update_snapshot_metadata(bytes_body=True) def test_force_delete_snapshot(self): self.check_service_client_function( self.client.force_delete_snapshot, 'tempest.lib.common.rest_client.RestClient.post', {}, snapshot_id="521752a6-acf6-4b2d-bc7a-119f9148cd8c", status=202) def test_delete_snapshot(self): self.check_service_client_function( self.client.delete_snapshot, 'tempest.lib.common.rest_client.RestClient.delete', {}, snapshot_id="521752a6-acf6-4b2d-bc7a-119f9148cd8c", status=202) tempest-17.2.0/tempest/tests/lib/services/volume/__init__.py0000666000175100017510000000000013207044712024132 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v3/0000775000175100017510000000000013207045130022354 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py0000666000175100017510000001551213207044712030574 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v3 import group_snapshots_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestGroupSnapshotsClient(base.BaseServiceTest): FAKE_CREATE_GROUP_SNAPSHOT = { "group_snapshot": { "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be", "name": "group-snapshot-001", "description": "Test group snapshot 1" } } FAKE_INFO_GROUP_SNAPSHOT = { "group_snapshot": { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be", "name": "group-snapshot-001", "description": "Test group snapshot 1", "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "status": "available", "created_at": "20127-06-20T03:50:07Z" } } FAKE_LIST_GROUP_SNAPSHOTS = { "group_snapshots": [ { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be", "name": "group-snapshot-001", "description": "Test group snapshot 1", "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "status": "available", "created_at": "2017-06-20T03:50:07Z", }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be", "name": "group-snapshot-002", "description": "Test group snapshot 2", "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "status": "available", "created_at": "2017-06-19T01:52:47Z", }, { "id": "c5c4769e-213c-40a6-a568-8e797bb691d4", "group_id": "49c8c114-0d68-4e89-b8bc-3f5a674d54be", "name": "group-snapshot-003", "description": "Test group snapshot 3", "group_type_id": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "status": "available", "created_at": "2017-06-18T06:34:32Z", } ] } def setUp(self): super(TestGroupSnapshotsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = group_snapshots_client.GroupSnapshotsClient( fake_auth, 'volume', 'regionOne') def _test_create_group_snapshot(self, bytes_body=False): self.check_service_client_function( self.client.create_group_snapshot, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP_SNAPSHOT, bytes_body, group_id="49c8c114-0d68-4e89-b8bc-3f5a674d54be", status=202) def _test_show_group_snapshot(self, bytes_body=False): self.check_service_client_function( self.client.show_group_snapshot, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_GROUP_SNAPSHOT, bytes_body, group_snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_list_group_snapshots(self, detail=False, bytes_body=False, mock_args='group_snapshots', **params): resp_body = [] if detail: resp_body = self.FAKE_LIST_GROUP_SNAPSHOTS else: resp_body = { 'group_snapshots': [{ 'id': group_snapshot['id'], 'name': group_snapshot['name'], 'group_type_id': group_snapshot['group_type_id']} for group_snapshot in self.FAKE_LIST_GROUP_SNAPSHOTS['group_snapshots'] ] } self.check_service_client_function( self.client.list_group_snapshots, 'tempest.lib.common.rest_client.RestClient.get', resp_body, to_utf=bytes_body, mock_args=[mock_args], detail=detail, **params) def test_create_group_snapshot_with_str_body(self): self._test_create_group_snapshot() def test_create_group_snapshot_with_bytes_body(self): self._test_create_group_snapshot(bytes_body=True) def test_show_group_snapshot_with_str_body(self): self._test_show_group_snapshot() def test_show_group_snapshot_with_bytes_body(self): self._test_show_group_snapshot(bytes_body=True) def test_list_group_snapshots_with_str_body(self): self._test_list_group_snapshots() def test_list_group_snapshots_with_bytes_body(self): self._test_list_group_snapshots(bytes_body=True) def test_list_group_snapshots_with_detail_with_str_body(self): mock_args = "group_snapshots/detail" self._test_list_group_snapshots(detail=True, mock_args=mock_args) def test_list_group_snapshots_with_detail_with_bytes_body(self): mock_args = "group_snapshots/detail" self._test_list_group_snapshots(detail=True, bytes_body=True, mock_args=mock_args) def test_list_group_snapshots_with_params(self): # Run the test separately for each param, to avoid assertion error # resulting from randomized params order. mock_args = 'group_snapshots?sort_key=name' self._test_list_group_snapshots(mock_args=mock_args, sort_key='name') mock_args = 'group_snapshots/detail?limit=10' self._test_list_group_snapshots(detail=True, bytes_body=True, mock_args=mock_args, limit=10) def test_delete_group_snapshot(self): self.check_service_client_function( self.client.delete_group_snapshot, 'tempest.lib.common.rest_client.RestClient.delete', {}, group_snapshot_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578', status=202) def test_reset_group_snapshot_status(self): self.check_service_client_function( self.client.reset_group_snapshot_status, 'tempest.lib.common.rest_client.RestClient.post', {}, status=202, group_snapshot_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578', status_to_set='error') tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_user_messages_client.py0000666000175100017510000000722113207044712030201 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v3 import messages_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestUserMessagesClient(base.BaseServiceTest): USER_MESSAGE_INFO = { "created_at": "2016-11-21T06:16:34.000000", "guaranteed_until": "2016-12-21T06:16:34.000000", "user_message": "No storage could be allocated for this volume " "request. You may be able to try another size or" " volume type.", "resource_uuid": "c570b406-bf0b-4067-9398-f0bb09a7d9d7", "request_id": "req-8f68681e-9b6b-4009-b94c-ac0811595451", "message_level": "ERROR", "id": "9a7dafbd-a156-4540-8996-50e71b5dcadf", "resource_type": "VOLUME", "links": [ {"href": "http://192.168.100.230:8776/v3/" "a678cb65f701462ea2257245cd640829/messages/" "9a7dafbd-a156-4540-8996-50e71b5dcadf", "rel": "self"}, {"href": "http://192.168.100.230:8776/" "a678cb65f701462ea2257245cd640829/messages/" "9a7dafbd-a156-4540-8996-50e71b5dcadf", "rel": "bookmark"}] } FAKE_SHOW_USER_MESSAGE = { "message": dict(event_id="000002", **USER_MESSAGE_INFO)} FAKE_LIST_USER_MESSAGES = { "messages": [ dict(event_id="000003", **USER_MESSAGE_INFO), dict(event_id="000004", **USER_MESSAGE_INFO) ] } def setUp(self): super(TestUserMessagesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = messages_client.MessagesClient(fake_auth, 'volume', 'regionOne') def _test_show_user_message(self, bytes_body=False): self.check_service_client_function( self.client.show_message, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SHOW_USER_MESSAGE, bytes_body, message_id="9a7dafbd-a156-4540-8996-50e71b5dcadf") def _test_list_user_message(self, bytes_body=False): self.check_service_client_function( self.client.list_messages, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_USER_MESSAGES, bytes_body) def test_list_user_message_with_str_body(self): self._test_list_user_message() def test_list_user_message_with_bytes_body(self): self._test_list_user_message(bytes_body=True) def test_show_user_message_with_str_body(self): self._test_show_user_message() def test_show_user_message_with_bytes_body(self): self._test_show_user_message(bytes_body=True) def test_delete_user_message(self): self.check_service_client_function( self.client.delete_message, 'tempest.lib.common.rest_client.RestClient.delete', {}, message_id="9a7dafbd-a156-4540-8996-50e71b5dcadf", status=204) tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_versions_client.py0000666000175100017510000000673113207044712027211 0ustar zuulzuul00000000000000# Copyright 2017 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.volume.v3 import versions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestVersionsClient(base.BaseServiceTest): FAKE_VERSIONS_INFO = { "versions": [ { "status": "DEPRECATED", "updated": "2016-05-02T20:25:19Z", "links": [ {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}, {"href": "https://10.30.197.39:8776/v1/", "rel": "self"} ], "min_version": "", "version": "", "media-types": [ {"base": "application/json", "type": "application/vnd.openstack.volume+json;version=1"} ], "id": "v1.0" }, { "status": "DEPRECATED", "updated": "2017-02-25T12:00:00Z", "links": [ {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}, {"href": "https://10.30.197.39:8776/v2/", "rel": "self"} ], "min_version": "", "version": "", "media-types": [ {"base": "application/json", "type": "application/vnd.openstack.volume+json;version=1"} ], "id": "v2.0" }, { "status": "CURRENT", "updated": "2016-02-08T12:20:21Z", "links": [ {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}, {"href": "https://10.30.197.39:8776/v3/", "rel": "self"} ], "min_version": "3.0", "version": "3.28", "media-types": [ {"base": "application/json", "type": "application/vnd.openstack.volume+json;version=1"} ], "id": "v3.0" } ] } def setUp(self): super(TestVersionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = versions_client.VersionsClient(fake_auth, 'volume', 'regionOne') def _test_list_versions(self, bytes_body=False): self.check_service_client_function( self.client.list_versions, 'tempest.lib.common.rest_client.RestClient.raw_request', self.FAKE_VERSIONS_INFO, bytes_body, 300) def test_list_versions_with_str_body(self): self._test_list_versions() def test_list_versions_with_bytes_body(self): self._test_list_versions(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_groups_client.py0000666000175100017510000001607613207044712026663 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v3 import groups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestGroupsClient(base.BaseServiceTest): FAKE_CREATE_GROUP = { "group": { "name": "group-001", "description": "Test group 1", "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"], "availability_zone": "az1", } } FAKE_CREATE_GROUP_FROM_GROUP_SNAPSHOT = { "create-from-src": { "name": "group-002", "description": "Test group 2", "group_snapshot_id": "79c9afdb-7e46-4d71-9249-1f022886963c", } } FAKE_CREATE_GROUP_FROM_GROUP = { "create-from-src": { "name": "group-003", "description": "Test group 3", "source_group_id": "e92f9dc7-0b20-492d-8ab2-3ad8fdac270e", } } FAKE_UPDATE_GROUP = { "group": { "name": "new-group", "description": "New test group", "add_volumes": "27d45037-ade3-4a87-b729-dba3293c06f3," "6e7cd916-d961-41cc-b3bd-0601ca0c701f", "remove_volumes": "4d580519-6467-448e-95e9-5b25c94d83c7," "ea22464c-f095-4a87-a31f-c5d34e0c6fc9" } } FAKE_INFO_GROUP = { "group": { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "name": "group-001", "description": "Test group 1", "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"], "status": "available", "availability_zone": "az1", "created_at": "20127-06-20T03:50:07Z" } } FAKE_LIST_GROUPS = { "groups": [ { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "name": "group-001", "description": "Test group 1", "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"], "status": "available", "availability_zone": "az1", "created_at": "2017-06-20T03:50:07Z", }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "name": "group-002", "description": "Test group 2", "group_snapshot_id": "79c9afdb-7e46-4d71-9249-1f022886963c", "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"], "status": "available", "availability_zone": "az1", "created_at": "2017-06-19T01:52:47Z", }, { "id": "c5c4769e-213c-40a6-a568-8e797bb691d4", "name": "group-003", "description": "Test group 3", "source_group_id": "e92f9dc7-0b20-492d-8ab2-3ad8fdac270e", "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b", "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"], "status": "available", "availability_zone": "az1", "created_at": "2017-06-18T06:34:32Z", } ] } def setUp(self): super(TestGroupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = groups_client.GroupsClient(fake_auth, 'volume', 'regionOne') def _test_create_group(self, bytes_body=False): self.check_service_client_function( self.client.create_group, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP, bytes_body, status=202) def _test_show_group(self, bytes_body=False): self.check_service_client_function( self.client.show_group, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_GROUP, bytes_body, group_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_list_groups(self, bytes_body=False): self.check_service_client_function( self.client.list_groups, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_GROUPS, bytes_body, detail=True) def test_create_group_with_str_body(self): self._test_create_group() def test_create_group_with_bytes_body(self): self._test_create_group(bytes_body=True) def test_show_group_with_str_body(self): self._test_show_group() def test_show_group_with_bytes_body(self): self._test_show_group(bytes_body=True) def test_list_groups_with_str_body(self): self._test_list_groups() def test_list_groups_with_bytes_body(self): self._test_list_groups(bytes_body=True) def test_delete_group(self): self.check_service_client_function( self.client.delete_group, 'tempest.lib.common.rest_client.RestClient.post', {}, group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578', status=202) def test_create_group_from_group_snapshot(self): self.check_service_client_function( self.client.create_group_from_source, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP_FROM_GROUP_SNAPSHOT, status=202) def test_create_group_from_group(self): self.check_service_client_function( self.client.create_group_from_source, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP_FROM_GROUP, status=202) def test_update_group(self): self.check_service_client_function( self.client.update_group, 'tempest.lib.common.rest_client.RestClient.put', {}, group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578', status=202, **self.FAKE_UPDATE_GROUP['group']) def test_reset_group_status(self): self.check_service_client_function( self.client.reset_group_status, 'tempest.lib.common.rest_client.RestClient.post', {}, status=202, group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578', status_to_set='error') tempest-17.2.0/tempest/tests/lib/services/volume/v3/__init__.py0000666000175100017510000000000013207044712024462 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_backups_client.py0000666000175100017510000000353013207044712026763 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v3 import backups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestBackupsClient(base.BaseServiceTest): FAKE_BACKUP_UPDATE = { "backup": { "id": "4c65c15f-a5c5-464b-b92a-90e4c04636a7", "name": "fake-backup-name", "links": "fake-links" } } def setUp(self): super(TestBackupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = backups_client.BackupsClient(fake_auth, 'volume', 'regionOne') def _test_update_backup(self, bytes_body=False): self.check_service_client_function( self.client.update_backup, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_BACKUP_UPDATE, bytes_body, backup_id='4c65c15f-a5c5-464b-b92a-90e4c04636a7') def test_update_backup_with_str_body(self): self._test_update_backup() def test_update_backup_with_bytes_body(self): self._test_update_backup(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_group_types_client.py0000666000175100017510000001056613207044712027722 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v3 import group_types_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestGroupTypesClient(base.BaseServiceTest): FAKE_CREATE_GROUP_TYPE = { "group_type": { "name": "group-type-001", "description": "Test group type 1", "group_specs": {}, "is_public": True, } } FAKE_INFO_GROUP_TYPE = { "group_type": { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "name": "group-type-001", "description": "Test group type 1", "is_public": True, "created_at": "20127-06-20T03:50:07Z", "group_specs": {}, } } FAKE_LIST_GROUP_TYPES = { "group_types": [ { "id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578", "name": "group-type-001", "description": "Test group type 1", "is_public": True, "created_at": "2017-06-20T03:50:07Z", "group_specs": {}, }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "name": "group-type-002", "description": "Test group type 2", "is_public": True, "created_at": "2017-06-19T01:52:47Z", "group_specs": {}, }, { "id": "c5c4769e-213c-40a6-a568-8e797bb691d4", "name": "group-type-003", "description": "Test group type 3", "is_public": True, "created_at": "2017-06-18T06:34:32Z", "group_specs": {}, } ] } def setUp(self): super(TestGroupTypesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = group_types_client.GroupTypesClient(fake_auth, 'volume', 'regionOne') def _test_create_group_type(self, bytes_body=False): self.check_service_client_function( self.client.create_group_type, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_GROUP_TYPE, bytes_body, status=202) def _test_show_group_type(self, bytes_body=False): self.check_service_client_function( self.client.show_group_type, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_GROUP_TYPE, bytes_body, group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_list_group_types(self, bytes_body=False): self.check_service_client_function( self.client.list_group_types, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_GROUP_TYPES, bytes_body) def test_create_group_type_with_str_body(self): self._test_create_group_type() def test_create_group_type_with_bytes_body(self): self._test_create_group_type(bytes_body=True) def test_delete_group_type(self): self.check_service_client_function( self.client.delete_group_type, 'tempest.lib.common.rest_client.RestClient.delete', {}, group_type_id='0e58433f-d108-4bf3-a22c-34e6b71ef86b', status=202) def test_show_group_type_with_str_body(self): self._test_show_group_type() def test_show_group_type_with_bytes_body(self): self._test_show_group_type(bytes_body=True) def test_list_group_types_with_str_body(self): self._test_list_group_types() def test_list_group_types_with_bytes_body(self): self._test_list_group_types(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v3/test_volumes_client.py0000666000175100017510000000337713207044712027036 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v3 import volumes_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestVolumesClient(base.BaseServiceTest): FAKE_VOLUME_SUMMARY = { "volume-summary": { "total_size": 20, "total_count": 5 } } def setUp(self): super(TestVolumesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = volumes_client.VolumesClient(fake_auth, 'volume', 'regionOne') def _test_show_volume_summary(self, bytes_body=False): self.check_service_client_function( self.client.show_volume_summary, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_VOLUME_SUMMARY, bytes_body) def test_show_volume_summary_with_str_body(self): self._test_show_volume_summary() def test_show_volume_summary_with_bytes_body(self): self._test_show_volume_summary(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/0000775000175100017510000000000013207045130022353 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_capabilities_client.py0000666000175100017510000000546013207044712027767 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import capabilities_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestCapabilitiesClient(base.BaseServiceTest): FAKE_BACKEND_CAPABILITIES = { "namespace": "OS::Storage::Capabilities::fake", "vendor_name": "OpenStack", "volume_backend_name": "lvmdriver-1", "pool_name": "pool", "driver_version": "2.0.0", "storage_protocol": "iSCSI", "display_name": "Capabilities of Cinder LVM driver", "description": ( "These are volume type options provided by Cinder LVM driver."), "visibility": "public", "replication_targets": [], "properties": { "compression": { "title": "Compression", "description": "Enables compression.", "type": "boolean" }, "qos": { "title": "QoS", "description": "Enables QoS.", "type": "boolean" }, "replication": { "title": "Replication", "description": "Enables replication.", "type": "boolean" }, "thin_provisioning": { "title": "Thin Provisioning", "description": "Sets thin provisioning.", "type": "boolean" } } } def setUp(self): super(TestCapabilitiesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = capabilities_client.CapabilitiesClient( fake_auth, 'volume', 'regionOne') def _test_show_backend_capabilities(self, bytes_body=False): self.check_service_client_function( self.client.show_backend_capabilities, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_BACKEND_CAPABILITIES, bytes_body, host='lvmdriver-1') def test_show_backend_capabilities_with_str_body(self): self._test_show_backend_capabilities() def test_show_backend_capabilities_with_bytes_body(self): self._test_show_backend_capabilities(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_quotas_client.py0000666000175100017510000000600713207044712026650 0ustar zuulzuul00000000000000# 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.services.volume.v2 import quotas_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQuotasClient(base.BaseServiceTest): FAKE_QUOTAS = { "quota_set": { "gigabytes": 5, "snapshots": 10, "volumes": 20 } } FAKE_UPDATE_QUOTAS_REQUEST = { "quota_set": { "security_groups": 45 } } def setUp(self): super(TestQuotasClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = quotas_client.QuotasClient(fake_auth, 'volume', 'regionOne') 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_QUOTAS, bytes_body, tenant_id="fake_tenant") def _test_show_quota_set(self, bytes_body=False): self.check_service_client_function( self.client.show_quota_set, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_QUOTAS, bytes_body, tenant_id="fake_tenant") def _test_update_quota_set(self, bytes_body=False): self.check_service_client_function( self.client.update_quota_set, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_QUOTAS_REQUEST, bytes_body, tenant_id="fake_tenant") 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_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_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_delete_quota_set(self): self.check_service_client_function( self.client.delete_quota_set, 'tempest.lib.common.rest_client.RestClient.delete', {}, tenant_id="fake_tenant") tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_transfers_client.py0000666000175100017510000001436513207044712027351 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import mock from oslo_serialization import jsonutils as json from tempest.lib.services.volume.v2 import transfers_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestTransfersClient(base.BaseServiceTest): FAKE_VOLUME_TRANSFER_ID = "0e89cdd1-6249-421b-96d8-25fac0623d42" FAKE_VOLUME_TRANSFER_INFO = { "transfer": { "id": FAKE_VOLUME_TRANSFER_ID, "name": "fake-volume-transfer", "volume_id": "47bf04ef-1ea5-4c5f-a375-430a086d6747", "created_at": "2017-04-18T09:10:03.000000", "links": [ { "href": "fake-url-1", "rel": "self" }, { "href": "fake-url-2", "rel": "bookmark" } ] } } def setUp(self): super(TestTransfersClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = transfers_client.TransfersClient(fake_auth, 'volume', 'regionOne') def _test_create_volume_transfer(self, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO) resp_body['transfer'].update({"auth_key": "fake-auth-key"}) kwargs = {"name": "fake-volume-transfer", "volume_id": "47bf04ef-1ea5-4c5f-a375-430a086d6747"} payload = json.dumps({"transfer": kwargs}, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.create_volume_transfer, 'tempest.lib.common.rest_client.RestClient.post', resp_body, to_utf=bytes_body, status=202, mock_args=['os-volume-transfer', payload], **kwargs) def _test_accept_volume_transfer(self, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO) resp_body['transfer'].pop('created_at') kwargs = {"auth_key": "fake-auth-key"} payload = json.dumps({"accept": kwargs}, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.accept_volume_transfer, 'tempest.lib.common.rest_client.RestClient.post', resp_body, to_utf=bytes_body, status=202, mock_args=['os-volume-transfer/%s/accept' % self.FAKE_VOLUME_TRANSFER_ID, payload], transfer_id=self.FAKE_VOLUME_TRANSFER_ID, **kwargs) def _test_show_volume_transfer(self, bytes_body=False): resp_body = self.FAKE_VOLUME_TRANSFER_INFO self.check_service_client_function( self.client.show_volume_transfer, 'tempest.lib.common.rest_client.RestClient.get', resp_body, to_utf=bytes_body, transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42") def _test_list_volume_transfers(self, detail=False, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_VOLUME_TRANSFER_INFO) if not detail: resp_body['transfer'].pop('created_at') resp_body = {"transfers": [resp_body['transfer']]} self.check_service_client_function( self.client.list_volume_transfers, 'tempest.lib.common.rest_client.RestClient.get', resp_body, to_utf=bytes_body, detail=detail) def test_create_volume_transfer_with_str_body(self): self._test_create_volume_transfer() def test_create_volume_transfer_with_bytes_body(self): self._test_create_volume_transfer(bytes_body=True) def test_accept_volume_transfer_with_str_body(self): self._test_accept_volume_transfer() def test_accept_volume_transfer_with_bytes_body(self): self._test_accept_volume_transfer(bytes_body=True) def test_show_volume_transfer_with_str_body(self): self._test_show_volume_transfer() def test_show_volume_transfer_with_bytes_body(self): self._test_show_volume_transfer(bytes_body=True) def test_list_volume_transfers_with_str_body(self): self._test_list_volume_transfers() def test_list_volume_transfers_with_bytes_body(self): self._test_list_volume_transfers(bytes_body=True) def test_list_volume_transfers_with_detail_with_str_body(self): self._test_list_volume_transfers(detail=True) def test_list_volume_transfers_with_detail_with_bytes_body(self): self._test_list_volume_transfers(detail=True, bytes_body=True) def test_delete_volume_transfer(self): self.check_service_client_function( self.client.delete_volume_transfer, 'tempest.lib.common.rest_client.RestClient.delete', {}, status=202, transfer_id="0e89cdd1-6249-421b-96d8-25fac0623d42") tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_hosts_client.py0000666000175100017510000000652113207044712026475 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import hosts_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQuotasClient(base.BaseServiceTest): FAKE_LIST_HOSTS = { "hosts": [ { "service-status": "available", "service": "cinder-scheduler", "zone": "nova", "service-state": "enabled", "host_name": "fake-host", "last-update": "2017-04-12T04:26:03.000000" }, { "service-status": "available", "service": "cinder-volume", "zone": "nova", "service-state": "enabled", "host_name": "fake-host@rbd", "last-update": "2017-04-12T04:26:07.000000" } ] } FAKE_HOST_INFO = { "host": [ { "resource": { "volume_count": "2", "total_volume_gb": "2", "total_snapshot_gb": "0", "project": "(total)", "host": "fake-host", "snapshot_count": "0" } }, { "resource": { "volume_count": "2", "total_volume_gb": "2", "total_snapshot_gb": "0", "project": "f21a9c86d7114bf99c711f4874d80474", "host": "fake-host", "snapshot_count": "0" } } ] } def setUp(self): super(TestQuotasClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = hosts_client.HostsClient(fake_auth, 'volume', 'regionOne') def _test_list_hosts(self, bytes_body=False): self.check_service_client_function( self.client.list_hosts, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_HOSTS, bytes_body) def _test_show_host(self, bytes_body=False): self.check_service_client_function( self.client.show_host, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_HOST_INFO, bytes_body, host_name='fake-host') def test_list_hosts_with_str_body(self): self._test_list_hosts() def test_list_hosts_with_bytes_body(self): self._test_list_hosts(bytes_body=True) def test_show_host_with_str_body(self): self._test_show_host() def test_show_host_with_bytes_body(self): self._test_show_host(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py0000666000175100017510000001010213207044712030142 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_serialization import jsonutils as json from tempest.lib.services.volume.v2 import volume_manage_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestVolumeManageClient(base.BaseServiceTest): VOLUME_MANAGE_REQUEST = { "volume": { "host": "controller1@rbd#rbd", "name": "volume-managed", "availability_zone": "nova", "bootable": False, "metadata": None, "ref": { "source-name": "volume-2ce6ca46-e6c1-4fe5-8268-3a1c536fcbf3" }, "volume_type": None, "description": "volume-manage-description" } } VOLUME_MANAGE_RESPONSE = { "volume": { "migration_status": None, "attachments": [], "links": [ { "href": "fake-url-1", "rel": "self" }, { "href": "fake-url-2", "rel": "bookmark" } ], "availability_zone": "nova", "os-vol-host-attr:host": "controller1@rbd#rbd", "encrypted": False, "updated_at": None, "replication_status": None, "snapshot_id": None, "id": "c07cd4a4-b52b-4511-a176-fbaa2011a227", "size": 0, "user_id": "142d8663efce464c89811c63e45bd82e", "os-vol-tenant-attr:tenant_id": "f21a9c86d7114bf99c711f4874d80474", "os-vol-mig-status-attr:migstat": None, "metadata": {}, "status": "creating", "description": "volume-manage-description", "multiattach": False, "source_volid": None, "consistencygroup_id": None, "os-vol-mig-status-attr:name_id": None, "name": "volume-managed", "bootable": "false", "created_at": "2017-07-11T09:14:01.000000", "volume_type": None } } def setUp(self): super(TestVolumeManageClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = volume_manage_client.VolumeManageClient(fake_auth, 'volume', 'regionOne') def _test_manage_volume(self, bytes_body=False): payload = json.dumps(self.VOLUME_MANAGE_REQUEST, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(volume_manage_client.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.manage_volume, 'tempest.lib.common.rest_client.RestClient.post', self.VOLUME_MANAGE_RESPONSE, to_utf=bytes_body, status=202, mock_args=['os-volume-manage', payload], **self.VOLUME_MANAGE_REQUEST['volume']) def test_manage_volume_with_str_body(self): self._test_manage_volume() def test_manage_volume_with_bytes_body(self): self._test_manage_volume(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py0000666000175100017510000000507613207044712030207 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.lib.services.volume.v2 import quota_classes_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestQuotaClassesClient(base.BaseServiceTest): FAKE_QUOTA_CLASS_SET = { "id": "test", "gigabytes": 2000, "volumes": 200, "snapshots": 50, "backups": 20, "backup_gigabytes": 1500, "per_volume_gigabytes": 500, } def setUp(self): super(TestQuotaClassesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = quota_classes_client.QuotaClassesClient( fake_auth, 'volume', '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_update_quota_class_set(self, bytes_body=False): 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, 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_with_str_boy(self): self._test_update_quota_class_set() def test_update_quota_class_set_with_bytes_body(self): self._test_update_quota_class_set(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py0000666000175100017510000000625513207044712030510 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_serialization import jsonutils as json from tempest.lib.services.volume.v2 import snapshot_manage_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSnapshotManageClient(base.BaseServiceTest): SNAPSHOT_MANAGE_REQUEST = { "snapshot": { "description": "snapshot-manage-description", "metadata": None, "ref": { "source-name": "_snapshot-22b71da0-94f9-4aca-ad45-7522b3fa96bb" }, "name": "snapshot-managed", "volume_id": "7c064b34-1e4b-40bd-93ca-4ac5a973661b" } } SNAPSHOT_MANAGE_RESPONSE = { "snapshot": { "status": "creating", "description": "snapshot-manage-description", "updated_at": None, "volume_id": "32bafcc8-7109-42cd-8342-70d8de2bedef", "id": "8fd6eb9d-0a82-456d-b1ec-dea4ac7f1ee2", "size": 1, "name": "snapshot-managed", "created_at": "2017-07-11T10:07:58.000000", "metadata": {} } } def setUp(self): super(TestSnapshotManageClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = snapshot_manage_client.SnapshotManageClient(fake_auth, 'volume', 'regionOne') def _test_manage_snapshot(self, bytes_body=False): payload = json.dumps(self.SNAPSHOT_MANAGE_REQUEST, sort_keys=True) json_dumps = json.dumps # NOTE: Use sort_keys for json.dumps so that the expected and actual # payloads are guaranteed to be identical for mock_args assert check. with mock.patch.object(snapshot_manage_client.json, 'dumps') as mock_dumps: mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) self.check_service_client_function( self.client.manage_snapshot, 'tempest.lib.common.rest_client.RestClient.post', self.SNAPSHOT_MANAGE_RESPONSE, to_utf=bytes_body, status=202, mock_args=['os-snapshot-manage', payload], **self.SNAPSHOT_MANAGE_REQUEST['snapshot']) def test_manage_snapshot_with_str_body(self): self._test_manage_snapshot() def test_manage_snapshot_with_bytes_body(self): self._test_manage_snapshot(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_extensions_client.py0000666000175100017510000000520513207044712027532 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import extensions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestExtensionsClient(base.BaseServiceTest): FAKE_EXTENSION_LIST = { "extensions": [ { "updated": "2012-03-12T00:00:00+00:00", "name": "QuotaClasses", "links": [], "namespace": "fake-namespace-1", "alias": "os-quota-class-sets", "description": "Quota classes management support." }, { "updated": "2013-05-29T00:00:00+00:00", "name": "VolumeTransfer", "links": [], "namespace": "fake-namespace-2", "alias": "os-volume-transfer", "description": "Volume transfer management support." }, { "updated": "2014-02-10T00:00:00+00:00", "name": "VolumeManage", "links": [], "namespace": "fake-namespace-3", "alias": "os-volume-manage", "description": "Manage existing backend storage by Cinder." } ] } def setUp(self): super(TestExtensionsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = extensions_client.ExtensionsClient(fake_auth, 'volume', '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', self.FAKE_EXTENSION_LIST, 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) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_availability_zone_client.py0000666000175100017510000000350113207044712031035 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import availability_zone_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestAvailabilityZoneClient(base.BaseServiceTest): FAKE_AZ_LIST = { "availabilityZoneInfo": [ { "zoneState": { "available": True }, "zoneName": "nova" } ] } def setUp(self): super(TestAvailabilityZoneClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = availability_zone_client.AvailabilityZoneClient( fake_auth, 'volume', 'regionOne') def _test_list_availability_zones(self, bytes_body=False): self.check_service_client_function( self.client.list_availability_zones, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_AZ_LIST, bytes_body) def test_list_availability_zones_with_str_body(self): self._test_list_availability_zones() def test_list_availability_zones_with_bytes_body(self): self._test_list_availability_zones(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/__init__.py0000666000175100017510000000000013207044712024461 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_encryption_types_client.py0000666000175100017510000000643213207044712030754 0ustar zuulzuul00000000000000# 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.services.volume.v2 import encryption_types_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestEncryptionTypesClient(base.BaseServiceTest): FAKE_CREATE_ENCRYPTION_TYPE = { "encryption": { "id": "cbc36478b0bd8e67e89", "name": "FakeEncryptionType", "type": "fakeType", "provider": "LuksEncryptor", "cipher": "aes-xts-plain64", "key_size": "512", "control_location": "front-end" } } FAKE_INFO_ENCRYPTION_TYPE = { "encryption": { "name": "FakeEncryptionType", "type": "fakeType", "description": "test_description", "volume_type": "fakeType", "provider": "LuksEncryptor", "cipher": "aes-xts-plain64", "key_size": "512", "control_location": "front-end" } } def setUp(self): super(TestEncryptionTypesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = encryption_types_client.EncryptionTypesClient(fake_auth, 'volume', 'regionOne' ) def _test_create_encryption(self, bytes_body=False): self.check_service_client_function( self.client.create_encryption_type, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_CREATE_ENCRYPTION_TYPE, bytes_body, volume_type_id="cbc36478b0bd8e67e89") def _test_show_encryption_type(self, bytes_body=False): self.check_service_client_function( self.client.show_encryption_type, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_ENCRYPTION_TYPE, bytes_body, volume_type_id="cbc36478b0bd8e67e89") def test_create_encryption_type_with_str_body(self): self._test_create_encryption() def test_create_encryption_type_with_bytes_body(self): self._test_create_encryption(bytes_body=True) def test_show_encryption_type_with_str_body(self): self._test_show_encryption_type() def test_show_encryption_type_with_bytes_body(self): self._test_show_encryption_type(bytes_body=True) def test_delete_encryption_type(self): self.check_service_client_function( self.client.delete_encryption_type, 'tempest.lib.common.rest_client.RestClient.delete', {}, volume_type_id="cbc36478b0bd8e67e89", status=202) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_snapshots_client.py0000666000175100017510000002000413207044712027347 0ustar zuulzuul00000000000000# 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.services.volume.v2 import snapshots_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSnapshotsClient(base.BaseServiceTest): FAKE_CREATE_SNAPSHOT = { "snapshot": { "display_name": "snap-001", "display_description": "Daily backup", "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "force": True } } FAKE_UPDATE_SNAPSHOT_REQUEST = { "metadata": { "key": "v1" } } FAKE_INFO_SNAPSHOT = { "snapshot": { "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5", "display_name": "snap-001", "display_description": "Daily backup", "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "status": "available", "size": 30, "created_at": "2012-02-29T03:50:07Z" } } FAKE_LIST_SNAPSHOTS = { "snapshots": [ { "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5", "display_name": "snap-001", "display_description": "Daily backup", "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "status": "available", "size": 30, "created_at": "2012-02-29T03:50:07Z", "metadata": { "contents": "junk" } }, { "id": "e479997c-650b-40a4-9dfe-77655818b0d2", "display_name": "snap-002", "display_description": "Weekly backup", "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", "status": "available", "size": 25, "created_at": "2012-03-19T01:52:47Z", "metadata": {} } ] } FAKE_SNAPSHOT_METADATA_ITEM = { "meta": { "key1": "value1" } } def setUp(self): super(TestSnapshotsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = snapshots_client.SnapshotsClient(fake_auth, 'volume', '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', self.FAKE_CREATE_SNAPSHOT, bytes_body, status=202) def _test_show_snapshot(self, bytes_body=False): self.check_service_client_function( self.client.show_snapshot, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_INFO_SNAPSHOT, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_list_snapshots(self, bytes_body=False): self.check_service_client_function( self.client.list_snapshots, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIST_SNAPSHOTS, bytes_body, detail=True) def _test_create_snapshot_metadata(self, bytes_body=False): self.check_service_client_function( self.client.create_snapshot_metadata, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_INFO_SNAPSHOT, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5", metadata={"key": "v1"}) def _test_update_snapshot(self, bytes_body=False): self.check_service_client_function( self.client.update_snapshot, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_SNAPSHOT_REQUEST, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_show_snapshot_metadata(self, bytes_body=False): self.check_service_client_function( self.client.show_snapshot_metadata, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_UPDATE_SNAPSHOT_REQUEST, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5") def _test_update_snapshot_metadata(self, bytes_body=False): self.check_service_client_function( self.client.update_snapshot_metadata, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_SNAPSHOT_REQUEST, bytes_body, snapshot_id="cbc36478b0bd8e67e89") def _test_update_snapshot_metadata_item(self, bytes_body=False): self.check_service_client_function( self.client.update_snapshot_metadata_item, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_INFO_SNAPSHOT, bytes_body, volume_type_id="cbc36478b0bd8e67e89") def _test_show_snapshot_metadata_item(self, bytes_body=False): self.check_service_client_function( self.client.show_snapshot_metadata_item, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_SNAPSHOT_METADATA_ITEM, bytes_body, snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5", id="key1") def test_create_snapshot_with_str_body(self): self._test_create_snapshot() def test_create_snapshot_with_bytes_body(self): self._test_create_snapshot(bytes_body=True) 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_with_str_body(self): self._test_list_snapshots() def test_list_snapshots_with_bytes_body(self): self._test_list_snapshots(bytes_body=True) def test_create_snapshot_metadata_with_str_body(self): self._test_create_snapshot_metadata() def test_create_snapshot_metadata_with_bytes_body(self): self._test_create_snapshot_metadata(bytes_body=True) def test_update_snapshot_with_str_body(self): self._test_update_snapshot() def test_update_snapshot_with_bytes_body(self): self._test_update_snapshot(bytes_body=True) def test_show_snapshot_metadata_with_str_body(self): self._test_show_snapshot_metadata() def test_show_snapshot_metadata_with_bytes_body(self): self._test_show_snapshot_metadata(bytes_body=True) def test_update_snapshot_metadata_with_str_body(self): self._test_update_snapshot_metadata() def test_update_snapshot_metadata_with_bytes_body(self): self._test_update_snapshot_metadata(bytes_body=True) def test_show_snapshot_metadata_item_with_str_body(self): self._test_show_snapshot_metadata_item() def test_show_snapshot_metadata_item_with_bytes_body(self): self._test_show_snapshot_metadata_item(bytes_body=True) def test_force_delete_snapshot(self): self.check_service_client_function( self.client.force_delete_snapshot, 'tempest.lib.common.rest_client.RestClient.post', {}, snapshot_id="521752a6-acf6-4b2d-bc7a-119f9148cd8c", status=202) def test_delete_snapshot(self): self.check_service_client_function( self.client.delete_snapshot, 'tempest.lib.common.rest_client.RestClient.delete', {}, snapshot_id="521752a6-acf6-4b2d-bc7a-119f9148cd8c", status=202) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_backups_client.py0000666000175100017510000001017713207044712026767 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import backups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestBackupsClient(base.BaseServiceTest): FAKE_BACKUP_LIST = { "backups": [ { "id": "2ef47aee-8844-490c-804d-2a8efe561c65", "links": [ { "href": "fake-url-1", "rel": "self" }, { "href": "fake-url-2", "rel": "bookmark" } ], "name": "backup001" } ] } FAKE_BACKUP_LIST_WITH_DETAIL = { "backups": [ { "availability_zone": "az1", "container": "volumebackups", "created_at": "2013-04-02T10:35:27.000000", "description": None, "fail_reason": None, "id": "2ef47aee-8844-490c-804d-2a8efe561c65", "links": [ { "href": "fake-url-1", "rel": "self" }, { "href": "fake-url-2", "rel": "bookmark" } ], "name": "backup001", "object_count": 22, "size": 1, "status": "available", "volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6", "is_incremental": True, "has_dependent_backups": False } ] } def setUp(self): super(TestBackupsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = backups_client.BackupsClient(fake_auth, 'volume', 'regionOne') def _test_list_backups(self, detail=False, mock_args='backups', bytes_body=False, **params): if detail: resp_body = self.FAKE_BACKUP_LIST_WITH_DETAIL else: resp_body = self.FAKE_BACKUP_LIST self.check_service_client_function( self.client.list_backups, 'tempest.lib.common.rest_client.RestClient.get', resp_body, to_utf=bytes_body, mock_args=[mock_args], detail=detail, **params) def test_list_backups_with_str_body(self): self._test_list_backups() def test_list_backups_with_bytes_body(self): self._test_list_backups(bytes_body=True) def test_list_backups_with_detail_with_str_body(self): mock_args = "backups/detail" self._test_list_backups(detail=True, mock_args=mock_args) def test_list_backups_with_detail_with_bytes_body(self): mock_args = "backups/detail" self._test_list_backups(detail=True, mock_args=mock_args, bytes_body=True) def test_list_backups_with_params(self): # Run the test separately for each param, to avoid assertion error # resulting from randomized params order. mock_args = 'backups?sort_key=name' self._test_list_backups(mock_args=mock_args, sort_key='name') mock_args = 'backups/detail?limit=10' self._test_list_backups(detail=True, mock_args=mock_args, bytes_body=True, limit=10) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py0000666000175100017510000000574013207044712030533 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import scheduler_stats_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestSchedulerStatsClient(base.BaseServiceTest): FAKE_POOLS_LIST = { "pools": [ { "name": "pool1", "capabilities": { "updated": "2014-10-28T00:00:00-00:00", "total_capacity": 1024, "free_capacity": 100, "volume_backend_name": "pool1", "reserved_percentage": 0, "driver_version": "1.0.0", "storage_protocol": "iSCSI", "QoS_support": False } }, { "name": "pool2", "capabilities": { "updated": "2014-10-28T00:00:00-00:00", "total_capacity": 512, "free_capacity": 200, "volume_backend_name": "pool2", "reserved_percentage": 0, "driver_version": "1.0.2", "storage_protocol": "iSER", "QoS_support": True } } ] } def setUp(self): super(TestSchedulerStatsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = scheduler_stats_client.SchedulerStatsClient( fake_auth, 'volume', 'regionOne') def _test_list_pools(self, bytes_body=False, detail=False): resp_body = [] if detail: resp_body = self.FAKE_POOLS_LIST else: resp_body = {'pools': [{'name': pool['name']} for pool in self.FAKE_POOLS_LIST['pools']]} self.check_service_client_function( self.client.list_pools, 'tempest.lib.common.rest_client.RestClient.get', resp_body, bytes_body, detail=detail) def test_list_pools_with_str_body(self): self._test_list_pools() def test_list_pools_with_str_body_and_detail(self): self._test_list_pools(detail=True) def test_list_pools_with_bytes_body(self): self._test_list_pools(bytes_body=True) def test_list_pools_with_bytes_body_and_detail(self): self._test_list_pools(bytes_body=True, detail=True) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_limits_client.py0000666000175100017510000000414213207044712026633 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import limits_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestLimitsClient(base.BaseServiceTest): FAKE_LIMIT_INFO = { "limits": { "rate": [], "absolute": { "totalSnapshotsUsed": 0, "maxTotalBackups": 10, "maxTotalVolumeGigabytes": 1000, "maxTotalSnapshots": 10, "maxTotalBackupGigabytes": 1000, "totalBackupGigabytesUsed": 0, "maxTotalVolumes": 10, "totalVolumesUsed": 0, "totalBackupsUsed": 0, "totalGigabytesUsed": 0 } } } def setUp(self): super(TestLimitsClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = limits_client.LimitsClient(fake_auth, 'volume', 'regionOne') def _test_show_limits(self, bytes_body=False): self.check_service_client_function( self.client.show_limits, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_LIMIT_INFO, 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) tempest-17.2.0/tempest/tests/lib/services/volume/v2/test_volumes_client.py0000666000175100017510000001073013207044712027024 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.services.volume.v2 import volumes_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestVolumesClient(base.BaseServiceTest): FAKE_VOLUME_METADATA_ITEM = { "meta": { "key1": "value1" } } FAKE_VOLUME_IMAGE_METADATA = { "metadata": { "container_format": "bare", "min_ram": "0", "disk_format": "raw", "image_name": "xly-ubuntu16-server", "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc", "checksum": "008f5d22fe3cb825d714da79607a90f9", "min_disk": "0", "size": "8589934592" } } def setUp(self): super(TestVolumesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = volumes_client.VolumesClient(fake_auth, 'volume', 'regionOne') def _test_retype_volume(self, bytes_body=False): kwargs = { "new_type": "dedup-tier-replication", "migration_policy": "never" } self.check_service_client_function( self.client.retype_volume, 'tempest.lib.common.rest_client.RestClient.post', {}, to_utf=bytes_body, status=202, volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8", **kwargs ) def _test_force_detach_volume(self, bytes_body=False): kwargs = { 'attachment_id': '6980e295-920f-412e-b189-05c50d605acd', 'connector': { 'initiator': 'iqn.2017-04.org.fake:01' } } self.check_service_client_function( self.client.force_detach_volume, 'tempest.lib.common.rest_client.RestClient.post', {}, to_utf=bytes_body, status=202, volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8", **kwargs ) def _test_show_volume_metadata_item(self, bytes_body=False): self.check_service_client_function( self.client.show_volume_metadata_item, 'tempest.lib.common.rest_client.RestClient.get', self.FAKE_VOLUME_METADATA_ITEM, to_utf=bytes_body, volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8", id="key1") def _test_show_volume_image_metadata(self, bytes_body=False): fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8" self.check_service_client_function( self.client.show_volume_image_metadata, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_VOLUME_IMAGE_METADATA, to_utf=bytes_body, mock_args=['volumes/%s/action' % fake_volume_id, json.dumps({"os-show_image_metadata": {}})], volume_id=fake_volume_id) def test_force_detach_volume_with_str_body(self): self._test_force_detach_volume() def test_force_detach_volume_with_bytes_body(self): self._test_force_detach_volume(bytes_body=True) def test_show_volume_metadata_item_with_str_body(self): self._test_show_volume_metadata_item() def test_show_volume_metadata_item_with_bytes_body(self): self._test_show_volume_metadata_item(bytes_body=True) def test_show_volume_image_metadata_with_str_body(self): self._test_show_volume_image_metadata() def test_show_volume_image_metadata_with_bytes_body(self): self._test_show_volume_image_metadata(bytes_body=True) def test_retype_volume_with_str_body(self): self._test_retype_volume() def test_retype_volume_with_bytes_body(self): self._test_retype_volume(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/object_storage/0000775000175100017510000000000013207045130023507 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/object_storage/test_capabilities_client.py0000666000175100017510000000355213207044712031123 0ustar zuulzuul00000000000000# Copyright 2016 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 tempest.lib.services.object_storage import capabilities_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestCapabilitiesClient(base.BaseServiceTest): def setUp(self): super(TestCapabilitiesClient, self).setUp() self.fake_auth = fake_auth_provider.FakeAuthProvider() self.url = self.fake_auth.base_url(None) self.client = capabilities_client.CapabilitiesClient( self.fake_auth, 'swift', 'region1') def _test_list_capabilities(self, bytes_body=False): resp = { "swift": { "version": "1.11.0" }, "slo": { "max_manifest_segments": 1000, "max_manifest_size": 2097152, "min_segment_size": 1 }, "staticweb": {}, "tempurl": {} } self.check_service_client_function( self.client.list_capabilities, 'tempest.lib.common.rest_client.RestClient.get', resp, bytes_body) def test_list_capabilities_with_str_body(self): self._test_list_capabilities() def test_list_capabilities_with_bytes_body(self): self._test_list_capabilities(True) tempest-17.2.0/tempest/tests/lib/services/object_storage/__init__.py0000666000175100017510000000000013207044712025615 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py0000666000175100017510000000467513207044712031633 0ustar zuulzuul00000000000000# Copyright 2017 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.object_storage import bulk_middleware_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestBulkMiddlewareClient(base.BaseServiceTest): def setUp(self): super(TestBulkMiddlewareClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = bulk_middleware_client.BulkMiddlewareClient( fake_auth, 'object-storage', 'regionOne') def test_upload_archive(self): url = 'test_path?extract-archive=tar' data = 'test_data' self.check_service_client_function( self.client.upload_archive, 'tempest.lib.common.rest_client.RestClient.put', {}, mock_args=[url, data, {}], resp_as_string=True, upload_path='test_path', data=data, archive_file_format='tar') def test_delete_bulk_data(self): url = '?bulk-delete' data = 'test_data' self.check_service_client_function( self.client.delete_bulk_data, 'tempest.lib.common.rest_client.RestClient.delete', {}, mock_args=[url, {}, data], resp_as_string=True, data=data) def _test_delete_bulk_data_with_post(self, status): url = '?bulk-delete' data = 'test_data' self.check_service_client_function( self.client.delete_bulk_data_with_post, 'tempest.lib.common.rest_client.RestClient.post', {}, mock_args=[url, data, {}], resp_as_string=True, status=status, data=data) def test_delete_bulk_data_with_post_200(self): self._test_delete_bulk_data_with_post(200) def test_delete_bulk_data_with_post_204(self): self._test_delete_bulk_data_with_post(204) tempest-17.2.0/tempest/tests/lib/services/object_storage/test_object_client.py0000666000175100017510000001066313207044712027741 0ustar zuulzuul00000000000000# Copyright 2016 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 mock from tempest.lib import exceptions from tempest.lib.services.object_storage import object_client from tempest.tests import base from tempest.tests.lib import fake_auth_provider class TestObjectClient(base.TestCase): def setUp(self): super(TestObjectClient, self).setUp() self.fake_auth = fake_auth_provider.FakeAuthProvider() self.url = self.fake_auth.base_url(None) self.object_client = object_client.ObjectClient(self.fake_auth, 'swift', 'region1') @mock.patch.object(object_client, '_create_connection') def test_create_object_continue_no_data(self, mock_poc): self._validate_create_object_continue(None, mock_poc) @mock.patch.object(object_client, '_create_connection') def test_create_object_continue_with_data(self, mock_poc): self._validate_create_object_continue('hello', mock_poc) @mock.patch.object(object_client, '_create_connection') def test_create_continue_with_no_continue_received(self, mock_poc): self._validate_create_object_continue('hello', mock_poc, initial_status=201) def _validate_create_object_continue(self, req_data, mock_poc, initial_status=100): expected_hdrs = { 'X-Auth-Token': self.fake_auth.get_token(), 'content-length': 0 if req_data is None else len(req_data), 'Expect': '100-continue'} # Setup the Mocks prior to invoking the object creation mock_resp_cls = mock.Mock() mock_resp_cls._read_status.return_value = ("1", initial_status, "OK") mock_poc.return_value.response_class.return_value = mock_resp_cls # This is the final expected return value mock_poc.return_value.getresponse.return_value.status = 201 mock_poc.return_value.getresponse.return_value.reason = 'OK' # Call method to PUT object using expect:100-continue cnt = "container1" obj = "object1" path = "/%s/%s" % (cnt, obj) # If the expected initial status is not 100, then an exception # should be thrown and the connection closed if initial_status is 100: status, reason = \ self.object_client.create_object_continue(cnt, obj, req_data) else: self.assertRaises(exceptions.UnexpectedResponseCode, self.object_client.create_object_continue, cnt, obj, req_data) mock_poc.return_value.close.assert_called_once_with() # Verify that putrequest is called 1 time with the appropriate values mock_poc.return_value.putrequest.assert_called_once_with('PUT', path) # Verify that headers were written, including "Expect:100-continue" calls = [] for header, value in expected_hdrs.items(): calls.append(mock.call(header, value)) mock_poc.return_value.putheader.assert_has_calls(calls, False) mock_poc.return_value.endheaders.assert_called_once_with() # The following steps are only taken if the initial status is 100 if initial_status is 100: # Verify that the method returned what it was supposed to self.assertEqual(status, 201) # Verify that _safe_read was called once to remove the CRLF # after the 100 response mock_rc = mock_poc.return_value.response_class.return_value mock_rc._safe_read.assert_called_once_with(2) # Verify the actual data was written via send mock_poc.return_value.send.assert_called_once_with(req_data) # Verify that the getresponse method was called to receive # the final mock_poc.return_value.getresponse.assert_called_once_with() tempest-17.2.0/tempest/tests/lib/services/test_clients.py0000666000175100017510000004705213207044712023606 0ustar zuulzuul00000000000000# 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 types import fixtures import mock import six import testtools from tempest.lib import auth from tempest.lib import exceptions from tempest.lib.services import clients from tempest.tests import base from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_credentials has_attribute = testtools.matchers.MatchesPredicateWithParams( lambda x, y: hasattr(x, y), '{0} does not have an attribute {1}') class TestClientsFactory(base.TestCase): def setUp(self): super(TestClientsFactory, self).setUp() self.classes = [] def _setup_fake_module(self, class_names=None, extra_dict=None): class_names = class_names or [] fake_module = types.ModuleType('fake_service_client') _dict = {} # Add fake classes to the fake module for name in class_names: _dict[name] = type(name, (object,), {}) # Store it for assertions self.classes.append(_dict[name]) if extra_dict: _dict[extra_dict] = extra_dict fake_module.__dict__.update(_dict) fixture_importlib = self.useFixture(fixtures.MockPatch( 'importlib.import_module', return_value=fake_module)) return fixture_importlib.mock def test___init___one_class(self): fake_partial = 'fake_partial' partial_mock = self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.ClientsFactory._get_partial_class', return_value=fake_partial)).mock class_names = ['FakeServiceClient1'] mock_importlib = self._setup_fake_module(class_names=class_names) auth_provider = fake_auth_provider.FakeAuthProvider() params = {'k1': 'v1', 'k2': 'v2'} factory = clients.ClientsFactory('fake_path', class_names, auth_provider, **params) # Assert module has been imported mock_importlib.assert_called_once_with('fake_path') # All attributes have been created for client in class_names: self.assertThat(factory, has_attribute(client)) # Partial have been invoked correctly partial_mock.assert_called_once_with( self.classes[0], auth_provider, params) # Get the clients for name in class_names: self.assertEqual(fake_partial, getattr(factory, name)) def test___init___two_classes(self): fake_partial = 'fake_partial' partial_mock = self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.ClientsFactory._get_partial_class', return_value=fake_partial)).mock class_names = ['FakeServiceClient1', 'FakeServiceClient2'] mock_importlib = self._setup_fake_module(class_names=class_names) auth_provider = fake_auth_provider.FakeAuthProvider() params = {'k1': 'v1', 'k2': 'v2'} factory = clients.ClientsFactory('fake_path', class_names, auth_provider, **params) # Assert module has been imported mock_importlib.assert_called_once_with('fake_path') # All attributes have been created for client in class_names: self.assertThat(factory, has_attribute(client)) # Partial have been invoked the right number of times partial_mock.call_count = len(class_names) # Get the clients for name in class_names: self.assertEqual(fake_partial, getattr(factory, name)) def test___init___no_module(self): auth_provider = fake_auth_provider.FakeAuthProvider() class_names = ['FakeServiceClient1', 'FakeServiceClient2'] self.assertRaises(ImportError, clients.ClientsFactory, 'fake_module', class_names, auth_provider) def test___init___not_a_class(self): class_names = ['FakeServiceClient1', 'FakeServiceClient2'] extended_class_names = class_names + ['not_really_a_class'] self._setup_fake_module( class_names=class_names, extra_dict='not_really_a_class') auth_provider = fake_auth_provider.FakeAuthProvider() expected_msg = '.*not_really_a_class.*str.*' with testtools.ExpectedException(TypeError, expected_msg): clients.ClientsFactory('fake_module', extended_class_names, auth_provider) def test___init___class_not_found(self): class_names = ['FakeServiceClient1', 'FakeServiceClient2'] extended_class_names = class_names + ['not_really_a_class'] self._setup_fake_module(class_names=class_names) auth_provider = fake_auth_provider.FakeAuthProvider() expected_msg = '.*not_really_a_class.*fake_service_client.*' with testtools.ExpectedException(AttributeError, expected_msg): clients.ClientsFactory('fake_module', extended_class_names, auth_provider) def test__get_partial_class_no_later_kwargs(self): expected_fake_client = 'not_really_a_client' self._setup_fake_module(class_names=[]) auth_provider = fake_auth_provider.FakeAuthProvider() params = {'k1': 'v1', 'k2': 'v2'} factory = clients.ClientsFactory( 'fake_path', [], auth_provider, **params) klass_mock = mock.Mock(return_value=expected_fake_client) partial = factory._get_partial_class(klass_mock, auth_provider, params) # Class has not be initialised yet klass_mock.assert_not_called() # Use partial and assert on parameters client = partial() self.assertEqual(expected_fake_client, client) klass_mock.assert_called_once_with(auth_provider=auth_provider, **params) def test__get_partial_class_later_kwargs(self): expected_fake_client = 'not_really_a_client' self._setup_fake_module(class_names=[]) auth_provider = fake_auth_provider.FakeAuthProvider() params = {'k1': 'v1', 'k2': 'v2'} later_params = {'k2': 'v4', 'k3': 'v3'} factory = clients.ClientsFactory( 'fake_path', [], auth_provider, **params) klass_mock = mock.Mock(return_value=expected_fake_client) partial = factory._get_partial_class(klass_mock, auth_provider, params) # Class has not be initialised yet klass_mock.assert_not_called() # Use partial and assert on parameters client = partial(**later_params) params.update(later_params) self.assertEqual(expected_fake_client, client) klass_mock.assert_called_once_with(auth_provider=auth_provider, **params) def test__get_partial_class_with_alias(self): expected_fake_client = 'not_really_a_client' client_alias = 'fake_client' self._setup_fake_module(class_names=[]) auth_provider = fake_auth_provider.FakeAuthProvider() params = {'k1': 'v1', 'k2': 'v2'} later_params = {'k2': 'v4', 'k3': 'v3'} factory = clients.ClientsFactory( 'fake_path', [], auth_provider, **params) klass_mock = mock.Mock(return_value=expected_fake_client) partial = factory._get_partial_class(klass_mock, auth_provider, params) # Class has not be initialised yet klass_mock.assert_not_called() # Use partial and assert on parameters client = partial(alias=client_alias, **later_params) params.update(later_params) self.assertEqual(expected_fake_client, client) klass_mock.assert_called_once_with(auth_provider=auth_provider, **params) self.assertThat(factory, has_attribute(client_alias)) self.assertEqual(expected_fake_client, getattr(factory, client_alias)) class TestServiceClients(base.TestCase): def setUp(self): super(TestServiceClients, self).setUp() self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.tempest_modules', return_value=set(['fake_service1']))) def test___init___creds_v2_uri(self): # Verify that no API request is made, since no mock # is required to run the test successfully creds = fake_credentials.FakeKeystoneV2Credentials() uri = 'fake_uri' _manager = clients.ServiceClients(creds, identity_uri=uri) self.assertIsInstance(_manager.auth_provider, auth.KeystoneV2AuthProvider) def test___init___creds_v3_uri(self): # Verify that no API request is made, since no mock # is required to run the test successfully creds = fake_credentials.FakeKeystoneV3Credentials() uri = 'fake_uri' _manager = clients.ServiceClients(creds, identity_uri=uri) self.assertIsInstance(_manager.auth_provider, auth.KeystoneV3AuthProvider) def test___init___base_creds_uri(self): creds = fake_credentials.FakeCredentials() uri = 'fake_uri' with testtools.ExpectedException(exceptions.InvalidCredentials): clients.ServiceClients(creds, identity_uri=uri) def test___init___invalid_creds_uri(self): creds = fake_credentials.FakeKeystoneV2Credentials() delattr(creds, 'username') uri = 'fake_uri' with testtools.ExpectedException(exceptions.InvalidCredentials): clients.ServiceClients(creds, identity_uri=uri) def test___init___creds_uri_none(self): creds = fake_credentials.FakeKeystoneV2Credentials() msg = ("Invalid Credentials\nDetails: ServiceClients requires a " "non-empty") with testtools.ExpectedException(exceptions.InvalidCredentials, value_re=msg): clients.ServiceClients(creds, None) def test___init___creds_uri_params(self): creds = fake_credentials.FakeKeystoneV2Credentials() expeted_params = {'fake_param1': 'fake_value1', 'fake_param2': 'fake_value2'} params = {'fake_service1': expeted_params} uri = 'fake_uri' _manager = clients.ServiceClients(creds, identity_uri=uri, client_parameters=params) self.assertIn('fake_service1', _manager.parameters) for _key in expeted_params: self.assertIn(_key, _manager.parameters['fake_service1'].keys()) self.assertEqual(expeted_params[_key], _manager.parameters['fake_service1'].get(_key)) def test___init___creds_uri_params_unknown_services(self): creds = fake_credentials.FakeKeystoneV2Credentials() fake_params = {'fake_param1': 'fake_value1'} params = {'unknown_service1': fake_params, 'unknown_service2': fake_params} uri = 'fake_uri' msg = "(?=.*{0})(?=.*{1})".format(*list(params.keys())) with testtools.ExpectedException( exceptions.UnknownServiceClient, value_re=msg): clients.ServiceClients(creds, identity_uri=uri, client_parameters=params) def test___init___plugin_service_clients_cannot_load(self): creds = fake_credentials.FakeKeystoneV3Credentials() uri = 'fake_uri' fake_service_clients = { 'service1': [{'name': 'client1', 'service_version': 'client1.v1', 'module_path': 'I cannot load this', 'client_names': ['SomeClient1']}], 'service2': [{'name': 'client2', 'service_version': 'client2.v1', 'module_path': 'This neither', 'client_names': ['SomeClient1']}]} msg = "(?=.*{0})(?=.*{1})".format( *[x[1][0]['module_path'] for x in six.iteritems( fake_service_clients)]) self.useFixture(fixtures.MockPatchObject( clients.ClientsRegistry(), 'get_service_clients', return_value=fake_service_clients)) with testtools.ExpectedException( testtools.MultipleExceptions, value_re=msg): clients.ServiceClients(creds, identity_uri=uri) def test___init___plugin_service_clients_name_conflict(self): creds = fake_credentials.FakeKeystoneV3Credentials() uri = 'fake_uri' fake_service_clients = { 'serviceA': [{'name': 'client1', 'service_version': 'client1.v1', 'module_path': 'fake_path_1', 'client_names': ['SomeClient1']}], 'serviceB': [{'name': 'client1', 'service_version': 'client1.v2', 'module_path': 'fake_path_2', 'client_names': ['SomeClient2']}], 'serviceC': [{'name': 'client1', 'service_version': 'client1.v1', 'module_path': 'fake_path_2', 'client_names': ['SomeClient1']}], 'serviceD': [{'name': 'client1', 'service_version': 'client1.v2', 'module_path': 'fake_path_2', 'client_names': ['SomeClient2']}]} msg = "(?=.*{0})(?=.*{1})".format( *[x[1][0]['service_version'] for x in six.iteritems( fake_service_clients)]) self.useFixture(fixtures.MockPatchObject( clients.ClientsRegistry(), 'get_service_clients', return_value=fake_service_clients)) with testtools.ExpectedException( testtools.MultipleExceptions, value_re=msg): clients.ServiceClients(creds, identity_uri=uri) def _get_manager(self, init_region='fake_region'): # Get a manager to invoke _setup_parameters on creds = fake_credentials.FakeKeystoneV2Credentials() return clients.ServiceClients(creds, identity_uri='fake_uri', region=init_region) def test__setup_parameters_none_no_region(self): kwargs = {} _manager = self._get_manager(init_region=None) _params = _manager._setup_parameters(kwargs) self.assertNotIn('region', _params) def test__setup_parameters_none(self): kwargs = {} _manager = self._get_manager() _params = _manager._setup_parameters(kwargs) self.assertIn('region', _params) self.assertEqual('fake_region', _params['region']) def test__setup_parameters_all(self): expected_params = {'region': 'fake_region1', 'catalog_type': 'fake_service2_mod', 'fake_param1': 'fake_value1', 'fake_param2': 'fake_value2'} _manager = self._get_manager() _params = _manager._setup_parameters(expected_params) for _key in _params.keys(): self.assertEqual(expected_params[_key], _params[_key]) def test_register_service_client_module(self): expected_params = {'fake_param1': 'fake_value1', 'fake_param2': 'fake_value2'} _manager = self._get_manager(init_region='fake_region_default') # Mock after the _manager is setup to preserve the call count factory_mock = self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.ClientsFactory')).mock _manager.register_service_client_module( name='fake_module', service_version='fake_service', module_path='fake.path.to.module', client_names=[], **expected_params) self.assertThat(_manager, has_attribute('fake_module')) # Assert called once, without check for exact parameters self.assertTrue(factory_mock.called) self.assertEqual(1, factory_mock.call_count) # Assert expected params are in with their values actual_kwargs = factory_mock.call_args[1] self.assertIn('region', actual_kwargs) self.assertEqual('fake_region_default', actual_kwargs['region']) for param in expected_params: self.assertIn(param, actual_kwargs) self.assertEqual(expected_params[param], actual_kwargs[param]) # Assert the new service is registered self.assertIn('fake_service', _manager._registered_services) def test_register_service_client_module_override_default(self): new_region = 'new_region' expected_params = {'fake_param1': 'fake_value1', 'fake_param2': 'fake_value2', 'region': new_region} _manager = self._get_manager(init_region='fake_region_default') # Mock after the _manager is setup to preserve the call count factory_mock = self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.ClientsFactory')).mock _manager.register_service_client_module( name='fake_module', service_version='fake_service', module_path='fake.path.to.module', client_names=[], **expected_params) self.assertThat(_manager, has_attribute('fake_module')) # Assert called once, without check for exact parameters self.assertTrue(factory_mock.called) self.assertEqual(1, factory_mock.call_count) # Assert expected params are in with their values actual_kwargs = factory_mock.call_args[1] self.assertIn('region', actual_kwargs) self.assertEqual(new_region, actual_kwargs['region']) for param in expected_params: self.assertIn(param, actual_kwargs) self.assertEqual(expected_params[param], actual_kwargs[param]) # Assert the new service is registered self.assertIn('fake_service', _manager._registered_services) def test_register_service_client_module_duplicate_name(self): self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.ClientsFactory')).mock _manager = self._get_manager() name_owner = 'this_is_a_string' setattr(_manager, 'fake_module', name_owner) expected_error = '.*' + name_owner with testtools.ExpectedException( exceptions.ServiceClientRegistrationException, expected_error): _manager.register_service_client_module( name='fake_module', module_path='fake.path.to.module', service_version='fake_service', client_names=[]) def test_register_service_client_module_duplicate_service(self): self.useFixture(fixtures.MockPatch( 'tempest.lib.services.clients.ClientsFactory')).mock _manager = self._get_manager() duplicate_service = 'fake_service1' expected_error = '.*' + duplicate_service _manager._registered_services = [duplicate_service] with testtools.ExpectedException( exceptions.ServiceClientRegistrationException, expected_error): _manager.register_service_client_module( name='fake_module', module_path='fake.path.to.module', service_version=duplicate_service, client_names=[]) tempest-17.2.0/tempest/tests/lib/services/registry_fixture.py0000666000175100017510000000503313207044712024515 0ustar zuulzuul00000000000000# Copyright 2017 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 fixtures from tempest.lib.services import clients class RegistryFixture(fixtures.Fixture): """A fixture to setup a test client registry The clients registry is a singleton. In Tempest it's filled with content from configuration. When testing Tempest lib classes without configuration it's handy to have the registry setup to be able to access service client factories. This fixture sets up the registry using a fake plugin, which includes all services specified at __init__ time. Any other plugin in the registry is removed at setUp time. The fake plugin is removed from the registry on cleanup. """ PLUGIN_NAME = 'fake_plugin_for_test' def __init__(self): """Initialise the registry fixture""" self.services = set(['compute', 'identity.v2', 'identity.v3', 'image.v1', 'image.v2', 'network', 'volume.v1', 'volume.v2', 'volume.v3', 'object-storage']) def _setUp(self): # Cleanup the registry registry = clients.ClientsRegistry() registry._service_clients = {} # Prepare the clients for registration all_clients = [] service_clients = clients.tempest_modules() for sc in self.services: sc_module = service_clients[sc] sc_unversioned = sc.split('.')[0] sc_name = sc.replace('.', '_').replace('-', '_') # Pass the bare minimum params to satisfy the clients interface service_client_data = dict( name=sc_name, service_version=sc, service=sc_unversioned, module_path=sc_module.__name__, client_names=sc_module.__all__) all_clients.append(service_client_data) registry.register_service_client(self.PLUGIN_NAME, all_clients) def _cleanup(): del registry._service_clients[self.PLUGIN_NAME] self.addCleanup(_cleanup) tempest-17.2.0/tempest/tests/lib/services/compute/0000775000175100017510000000000013207045130022171 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/compute/test_agents_client.py0000666000175100017510000000674113207044712026440 0ustar zuulzuul00000000000000# 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 import base class TestAgentsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py0000666000175100017510000000643113207044712030646 0ustar zuulzuul00000000000000# 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 import base class TestFloatingIPsBulkClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_quotas_client.py0000666000175100017510000001267413207044712026475 0ustar zuulzuul00000000000000# 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 import base class TestQuotasClient(base.BaseServiceTest): 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 _get_quota_set(self, detail): if not detail: return self.FAKE_QUOTA_SET fake_quota_set = {"quota_set": {}} for key, val in self.FAKE_QUOTA_SET['quota_set'].items(): fake_quota_set['quota_set'][key] = \ {'limit': val, 'reserved': 0, 'in_use': 0} fake_quota_set['quota_set']['id'] = "8421f7be61064f50b680465c07f334af" return fake_quota_set def _test_show_quota_set(self, bytes_body=False, detail=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._get_quota_set(detail), to_utf=bytes_body, tenant_id=self.project_id, detail=detail, user_id=user_id) else: self.check_service_client_function( self.client.show_quota_set, 'tempest.lib.common.rest_client.RestClient.get', self._get_quota_set(detail), to_utf=bytes_body, tenant_id=self.project_id, detail=detail) 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_quota_set_with_details(self): self._test_show_quota_set(detail=True) 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_security_group_default_rules_client.py0000666000175100017510000000717013207044712033155 0ustar zuulzuul00000000000000# 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 import base class TestSecurityGroupDefaultRulesClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_aggregates_client.py0000666000175100017510000001407513207044712027267 0ustar zuulzuul00000000000000# 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 import base class TestAggregatesClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_hosts_client.py0000666000175100017510000001203113207044712026304 0ustar zuulzuul00000000000000# 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 import base class TestHostsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_quota_classes_client.py0000666000175100017510000000506113207044712030017 0ustar zuulzuul00000000000000# 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 import base class TestQuotaClassesClient(base.BaseServiceTest): 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") tempest-17.2.0/tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py0000666000175100017510000000554213207044712032174 0ustar zuulzuul00000000000000# 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 import base class TestInstanceUsagesAuditLogClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_flavors_client.py0000666000175100017510000002261513207044712026631 0ustar zuulzuul00000000000000# 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 fixtures from oslo_serialization import jsonutils as json from tempest.lib.services.compute import flavors_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_http from tempest.tests.lib.services import base class TestFlavorsClient(base.BaseServiceTest): 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 = fake_http.fake_http_response({}, status=200), body self.useFixture(fixtures.MockPatch( '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) tempest-17.2.0/tempest/tests/lib/services/compute/test_versions_client.py0000666000175100017510000001300013207044712027011 0ustar zuulzuul00000000000000# 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 import fixtures from tempest.lib.services.compute import versions_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestVersionsClient(base.BaseServiceTest): 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(fixtures.MockPatch( "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/') tempest-17.2.0/tempest/tests/lib/services/compute/test_interfaces_client.py0000666000175100017510000000753113207044712027300 0ustar zuulzuul00000000000000# 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 import base class TestInterfacesClient(base.BaseServiceTest): # 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_security_group_rules_client.py0000666000175100017510000000477713207044712031463 0ustar zuulzuul00000000000000# 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 import base class TestSecurityGroupRulesClient(base.BaseServiceTest): 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') tempest-17.2.0/tempest/tests/lib/services/compute/test_server_groups_client.py0000666000175100017510000000741113207044712030057 0ustar zuulzuul00000000000000# 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 fixtures from tempest.lib.services.compute import base_compute_client from tempest.lib.services.compute import server_groups_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_http from tempest.tests.lib.services import base class TestServerGroupsClient(base.BaseServiceTest): 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": self.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 = fake_http.fake_http_response({}, status=204), '' self.useFixture(fixtures.MockPatch( '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": [self.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": self.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) class TestServerGroupsClientMinV213(TestServerGroupsClient): server_group = { "id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9", "name": "test", "policies": ["anti-affinity"], "members": [], "metadata": {}, "project_id": "0beb4bffb7a445eb8eb05fee3ee7660a", "user_id": "86031628064a4f99bb66ec03c507dcd8"} def setUp(self): super(TestServerGroupsClientMinV213, self).setUp() self.patchobject(base_compute_client, 'COMPUTE_MICROVERSION', new='2.13') tempest-17.2.0/tempest/tests/lib/services/compute/test_baremetal_nodes_client.py0000666000175100017510000000554013207044712030277 0ustar zuulzuul00000000000000# 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 import base class TestBareMetalNodesClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_migrations_client.py0000666000175100017510000000374113207044712027330 0ustar zuulzuul00000000000000# 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 import base class TestMigrationsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_images_client.py0000666000175100017510000002330513207044712026417 0ustar zuulzuul00000000000000# 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 import fixtures 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 import base class TestImagesClient(base.BaseServiceTest): # 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'] self.useFixture(fixtures.MockPatch('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(fixtures.MockPatch('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) tempest-17.2.0/tempest/tests/lib/services/compute/test_extensions_client.py0000666000175100017510000000447113207044712027354 0ustar zuulzuul00000000000000# 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 import base class TestExtensionsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_availability_zone_client.py0000666000175100017510000000357113207044712030662 0ustar zuulzuul00000000000000# 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 import base class TestAvailabilityZoneClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_fixedIPs_client.py0000666000175100017510000000443313207044712026666 0ustar zuulzuul00000000000000# 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 import base class TestFixedIPsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/__init__.py0000666000175100017510000000000013207044712024277 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/tests/lib/services/compute/test_floating_ip_pools_client.py0000666000175100017510000000335213207044712030661 0ustar zuulzuul00000000000000# 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 import base class TestFloatingIPPoolsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_services_client.py0000666000175100017510000001173013207044712026774 0ustar zuulzuul00000000000000# 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 import mock from tempest.lib.services.compute import base_compute_client from tempest.lib.services.compute import services_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestServicesClient(base.BaseServiceTest): 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" } } FAKE_UPDATE_FORCED_DOWN = { "service": { "forced_down": True, "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') self.addCleanup(mock.patch.stopall) 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="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="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) def _test_log_reason_disabled_service(self, bytes_body=False): resp_body = copy.deepcopy(self.FAKE_SERVICE) resp_body['service']['disabled_reason'] = 'test reason' self.check_service_client_function( self.client.disable_log_reason, 'tempest.lib.common.rest_client.RestClient.put', resp_body, bytes_body, host="nova-conductor", binary="controller", disabled_reason='test reason') def test_log_reason_disabled_service_with_str_body(self): self._test_log_reason_disabled_service() def test_log_reason_disabled_service_with_bytes_body(self): self._test_log_reason_disabled_service(bytes_body=True) def _test_update_forced_down(self, bytes_body=False): self.check_service_client_function( self.client.update_forced_down, 'tempest.lib.common.rest_client.RestClient.put', self.FAKE_UPDATE_FORCED_DOWN, bytes_body, host="nova-conductor", binary="controller", forced_down=True) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.11')) def test_update_forced_down_with_str_body(self, _): self._test_update_forced_down() @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.11')) def test_update_forced_down_with_bytes_body(self, _): self._test_update_forced_down(bytes_body=True) tempest-17.2.0/tempest/tests/lib/services/compute/test_tenant_usages_client.py0000666000175100017510000000547313207044712030020 0ustar zuulzuul00000000000000# 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 import base class TestTenantUsagesClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_snapshots_client.py0000666000175100017510000000753213207044712027200 0ustar zuulzuul00000000000000# 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 fixtures 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 import base class TestSnapshotsClient(base.BaseServiceTest): 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(fixtures.MockPatch( 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(fixtures.MockPatch( module, return_value={})) self.assertFalse(self.client.is_resource_deleted('fake-id')) tempest-17.2.0/tempest/tests/lib/services/compute/test_tenant_networks_client.py0000666000175100017510000000442413207044712030400 0ustar zuulzuul00000000000000# 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 import base class TestTenantNetworksClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_limits_client.py0000666000175100017510000000464513207044712026461 0ustar zuulzuul00000000000000# 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 import base class TestLimitsClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_keypairs_client.py0000666000175100017510000000642313207044712027003 0ustar zuulzuul00000000000000# 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 import base class TestKeyPairsClient(base.BaseServiceTest): 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') tempest-17.2.0/tempest/tests/lib/services/compute/test_servers_client.py0000666000175100017510000010662713207044712026654 0ustar zuulzuul00000000000000# Copyright 2015 IBM Corp. # Copyright 2017 AT&T 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 mock from tempest.lib.services.compute import base_compute_client from tempest.lib.services.compute import servers_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib.services import base class TestServersClient(base.BaseServiceTest): 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": "2016-10-02T10:00:00-05:00", "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_SERVER_PASSWORD = { "adminPass": "fake-password", } FAKE_INSTANCE_ACTION_EVENTS = { "event": "fake-event", "start_time": "2016-10-02T10:00:00-05:00", "finish_time": "2016-10-02T10:00:00-05:00", "result": "fake-result", "traceback": "fake-trace-back" } FAKE_SECURITY_GROUPS = [{ "description": "default", "id": "3fb26eb3-581b-4420-9963-b0879a026506", "name": "default", "rules": [], "tenant_id": "openstack" }] 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' FAKE_TAGS = ["foo", "bar"] REPLACE_FAKE_TAGS = ["baz", "qux"] 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') self.addCleanup(mock.patch.stopall) 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): 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'], bytes_body, 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', {}, bytes_body, 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', {}, bytes_body, status=202, server_id=self.server_id, backup_type='fake-backup', rotation='fake-rotation', name='fake-name' ) def test_evacuate_server_with_str_body(self): self._test_evacuate_server() def test_evacuate_server_with_bytes_body(self): self._test_evacuate_server(bytes_body=True) def _test_evacuate_server(self, bytes_body=False): kwargs = {'server_id': self.server_id, 'host': 'fake-target-host'} self.check_service_client_function( self.client.evacuate_server, 'tempest.lib.common.rest_client.RestClient.post', self.FAKE_SERVER_PASSWORD, bytes_body, **kwargs) 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', {}, bytes_body, 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'}, bytes_body, server_id=self.server_id ) def test_delete_password(self): 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(self): 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, bytes_body, status=202, server_id=self.server_id, image_ref='fake-image-ref' ) def test_resize_server(self): 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(self): 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(self): 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'}}, bytes_body, 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'}}, bytes_body, 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'}}, bytes_body, 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'}}, bytes_body, 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'}}, bytes_body, server_id=self.server_id, key='fake-key', meta='fake-meta' ) def test_delete_server_metadata(self): 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(self): 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(self): 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}, bytes_body, 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', {}, bytes_body, 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}, bytes_body, 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]}, bytes_body, 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', {}, bytes_body, status=202, server_id=self.server_id, name='fake-name' ) def test_remove_security_group(self): 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', {}, bytes_body, 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', {}, bytes_body, status=202, server_id=self.server_id ) def test_lock_server(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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'}, bytes_body, 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]}, bytes_body, 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'}, bytes_body, server_id=self.server_id ) def test_unrescue_server(self): 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, bytes_body, 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]}, bytes_body, 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}, bytes_body, server_id=self.server_id, request_id='fake-request-id' ) def test_force_delete_server(self): 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(self): 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(self): 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(self): 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}, bytes_body, server_id=self.server_id, type='fake-console-type' ) def test_list_security_groups_by_server_with_str_body(self): self._test_list_security_groups_by_server() def test_list_security_groups_by_server_with_bytes_body(self): self._test_list_security_groups_by_server(True) def _test_list_security_groups_by_server(self, bytes_body=False): self.check_service_client_function( self.client.list_security_groups_by_server, 'tempest.lib.common.rest_client.RestClient.get', {'security_groups': self.FAKE_SECURITY_GROUPS}, bytes_body, server_id=self.server_id ) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_list_tags_str_body(self, _): self._test_list_tags() @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_list_tags_byte_body(self, _): self._test_list_tags(bytes_body=True) def _test_list_tags(self, bytes_body=False): expected = {"tags": self.FAKE_TAGS} self.check_service_client_function( self.client.list_tags, 'tempest.lib.common.rest_client.RestClient.get', expected, server_id=self.server_id, to_utf=bytes_body) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_update_all_tags_str_body(self, _): self._test_update_all_tags() @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_update_all_tags_byte_body(self, _): self._test_update_all_tags(bytes_body=True) def _test_update_all_tags(self, bytes_body=False): expected = {"tags": self.REPLACE_FAKE_TAGS} self.check_service_client_function( self.client.update_all_tags, 'tempest.lib.common.rest_client.RestClient.put', expected, server_id=self.server_id, tags=self.REPLACE_FAKE_TAGS, to_utf=bytes_body) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_delete_all_tags(self, _): self.check_service_client_function( self.client.delete_all_tags, 'tempest.lib.common.rest_client.RestClient.delete', {}, server_id=self.server_id, status=204) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_check_tag_existence_str_body(self, _): self._test_check_tag_existence() @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_check_tag_existence_byte_body(self, _): self._test_check_tag_existence(bytes_body=True) def _test_check_tag_existence(self, bytes_body=False): self.check_service_client_function( self.client.check_tag_existence, 'tempest.lib.common.rest_client.RestClient.get', {}, server_id=self.server_id, tag=self.FAKE_TAGS[0], status=204, to_utf=bytes_body) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_update_tag_str_body(self, _): self._test_update_tag() @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_update_tag_byte_body(self, _): self._test_update_tag(bytes_body=True) def _test_update_tag(self, bytes_body=False): self.check_service_client_function( self.client.update_tag, 'tempest.lib.common.rest_client.RestClient.put', {}, server_id=self.server_id, tag=self.FAKE_TAGS[0], status=201, headers={'location': 'fake_location'}, to_utf=bytes_body) @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', new_callable=mock.PropertyMock(return_value='2.26')) def test_delete_tag(self, _): self.check_service_client_function( self.client.delete_tag, 'tempest.lib.common.rest_client.RestClient.delete', {}, server_id=self.server_id, tag=self.FAKE_TAGS[0], status=204, ) class TestServersClientMinV26(base.BaseServiceTest): def setUp(self): super(TestServersClientMinV26, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = servers_client.ServersClient(fake_auth, 'compute', 'regionOne') base_compute_client.COMPUTE_MICROVERSION = '2.6' self.server_id = "920eaac8-a284-4fd1-9c2c-b30f0181b125" def tearDown(self): super(TestServersClientMinV26, self).tearDown() base_compute_client.COMPUTE_MICROVERSION = None def test_get_remote_consoles(self): self.check_service_client_function( self.client.get_remote_console, 'tempest.lib.common.rest_client.RestClient.post', { 'remote_console': { 'protocol': 'serial', 'type': 'serial', 'url': 'ws://127.0.0.1:6083/?token=IllAllowIt' } }, server_id=self.server_id, console_type='serial', protocol='serial', ) tempest-17.2.0/tempest/tests/lib/services/compute/test_volumes_client.py0000666000175100017510000001013513207044712026641 0ustar zuulzuul00000000000000# 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 import fixtures 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 import base class TestVolumesClient(base.BaseServiceTest): 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(fixtures.MockPatch( 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(fixtures.MockPatch( module, return_value={})) self.assertFalse(self.client.is_resource_deleted('fake-id')) tempest-17.2.0/tempest/tests/lib/services/compute/test_certificates_client.py0000666000175100017510000000444313207044712027621 0ustar zuulzuul00000000000000# 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 import base class TestCertificatesClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_security_groups_client.py0000666000175100017510000001102213207044712030411 0ustar zuulzuul00000000000000# 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 fixtures 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 import base class TestSecurityGroupsClient(base.BaseServiceTest): 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(fixtures.MockPatch(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(fixtures.MockPatch(mod, return_value='success')) self.assertFalse(self.client.is_resource_deleted('fake-id')) tempest-17.2.0/tempest/tests/lib/services/compute/test_base_compute_client.py0000666000175100017510000001700613207044712027621 0ustar zuulzuul00000000000000# 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 from tempest.lib.common import rest_client from tempest.lib import exceptions from tempest.lib.services.compute import base_compute_client from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_http from tempest.tests.lib.services import base class TestMicroversionHeaderCheck(base.BaseServiceTest): def setUp(self): super(TestMicroversionHeaderCheck, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = base_compute_client.BaseComputeClient( fake_auth, 'compute', 'regionOne') base_compute_client.COMPUTE_MICROVERSION = '2.2' def tearDown(self): super(TestMicroversionHeaderCheck, self).tearDown() base_compute_client.COMPUTE_MICROVERSION = None @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_correct_microverion_in_response(self, mock_request): response = fake_http.fake_http_response( headers={self.client.api_microversion_header_name: '2.2'}, ) mock_request.return_value = response, '' self.client.get('fake_url') @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_incorrect_microverion_in_response(self, mock_request): response = fake_http.fake_http_response( headers={self.client.api_microversion_header_name: '2.3'}, ) mock_request.return_value = response, '' self.assertRaises(exceptions.InvalidHTTPResponseHeader, self.client.get, 'fake_url') @mock.patch('tempest.lib.common.http.ClosingHttp.request') def test_no_microverion_header_in_response(self, mock_request): response = fake_http.fake_http_response( headers={}, ) mock_request.return_value = response, '' self.assertRaises(exceptions.InvalidHTTPResponseHeader, self.client.get, 'fake_url') class DummyServiceClient1(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.1', 'schema': 'schemav21'}, {'min': '2.2', 'max': '2.9', 'schema': 'schemav22'}, {'min': '2.10', 'max': None, 'schema': 'schemav210'}] def return_selected_schema(self): return self.get_schema(self.schema_versions_info) class TestSchemaVersionsNone(base.BaseServiceTest): api_microversion = None expected_schema = 'schemav21' def setUp(self): super(TestSchemaVersionsNone, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = DummyServiceClient1(fake_auth, 'compute', 'regionOne') base_compute_client.COMPUTE_MICROVERSION = self.api_microversion def tearDown(self): super(TestSchemaVersionsNone, self).tearDown() base_compute_client.COMPUTE_MICROVERSION = None def test_schema(self): self.assertEqual(self.expected_schema, self.client.return_selected_schema()) class TestSchemaVersionsV21(TestSchemaVersionsNone): api_microversion = '2.1' expected_schema = 'schemav21' class TestSchemaVersionsV22(TestSchemaVersionsNone): api_microversion = '2.2' expected_schema = 'schemav22' class TestSchemaVersionsV25(TestSchemaVersionsNone): api_microversion = '2.5' expected_schema = 'schemav22' class TestSchemaVersionsV29(TestSchemaVersionsNone): api_microversion = '2.9' expected_schema = 'schemav22' class TestSchemaVersionsV210(TestSchemaVersionsNone): api_microversion = '2.10' expected_schema = 'schemav210' class TestSchemaVersionsLatest(TestSchemaVersionsNone): api_microversion = 'latest' expected_schema = 'schemav210' class DummyServiceClient2(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.1', 'schema': 'schemav21'}, {'min': '2.2', 'max': '2.9', 'schema': 'schemav22'}] def return_selected_schema(self): return self.get_schema(self.schema_versions_info) class TestSchemaVersionsNotFound(base.BaseServiceTest): api_microversion = '2.10' expected_schema = 'schemav210' def setUp(self): super(TestSchemaVersionsNotFound, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = DummyServiceClient2(fake_auth, 'compute', 'regionOne') base_compute_client.COMPUTE_MICROVERSION = self.api_microversion def tearDown(self): super(TestSchemaVersionsNotFound, self).tearDown() base_compute_client.COMPUTE_MICROVERSION = None def test_schema(self): self.assertRaises(exceptions.JSONSchemaNotFound, self.client.return_selected_schema) class TestClientWithoutMicroversionHeader(base.BaseServiceTest): 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 (fake_http.fake_http_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.BaseServiceTest): def setUp(self): super(TestClientWithMicroversionHeader, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() self.client = base_compute_client.BaseComputeClient( fake_auth, 'compute', 'regionOne') base_compute_client.COMPUTE_MICROVERSION = '2.2' def tearDown(self): super(TestClientWithMicroversionHeader, self).tearDown() base_compute_client.COMPUTE_MICROVERSION = None 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 (fake_http.fake_http_response( headers={self.client.api_microversion_header_name: '2.2'}, 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') tempest-17.2.0/tempest/tests/lib/services/compute/test_hypervisor_client.py0000666000175100017510000001422413207044712027364 0ustar zuulzuul00000000000000# 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 import base class TestHypervisorClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_networks_client.py0000666000175100017510000000576313207044712027036 0ustar zuulzuul00000000000000# 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 import base class TestNetworksClient(base.BaseServiceTest): 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) tempest-17.2.0/tempest/tests/lib/services/compute/test_floating_ips_client.py0000666000175100017510000001103413207044712027624 0ustar zuulzuul00000000000000# 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 fixtures 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 import base class TestFloatingIpsClient(base.BaseServiceTest): 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(fixtures.MockPatch( '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(fixtures.MockPatch( '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')) tempest-17.2.0/tempest/tests/lib/fake_auth_provider.py0000666000175100017510000000244213207044712023116 0ustar zuulzuul00000000000000# 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" def get_token(self): return "faketoken" class FakeCredentials(object): def __init__(self, creds_dict): for key in creds_dict: setattr(self, key, creds_dict[key]) tempest-17.2.0/tempest/tests/lib/test_auth.py0000666000175100017510000010362313207044712021260 0ustar zuulzuul00000000000000# 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 import fixtures import testtools 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 import base from tempest.tests.lib import fake_credentials 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.patchobject(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(fixtures.MockPatchObject(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.patchobject(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.assertIsNone(body) 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 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_known_name(self): """If name and service is known, return the endpoint.""" self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'name': 'nova' } expected = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][1]) self._test_base_url_helper(expected, self.filters) def test_base_url_with_known_name_and_unknown_servce(self): """Test with Known Name and Unknown service If the name is known but the service is unknown, raise an exception. """ self.filters = { 'service': 'AintNoBodyKnowThatService', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'name': 'AintNoBodyKnowThatName' } self.assertRaises(exceptions.EndpointNotFound, self._test_base_url_helper, None, self.filters) def test_base_url_with_unknown_name_and_known_service(self): """Test with Unknown Name and Known Service If the name is unknown, raise an exception. Note that filtering by name is only successful service exists. """ self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'name': 'AintNoBodyKnowThatName' } self.assertRaises(exceptions.EndpointNotFound, self._test_base_url_helper, None, self.filters) def test_base_url_without_name(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_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_base_url_with_extra_path_endpoint(self): auth_data = { 'serviceCatalog': [ { 'type': 'compute', 'endpoints': [ { 'region': 'FakeRegion', 'publicURL': 'http://fake_url/some_path/v2.0' } ] } ] } filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'api_version': 'v2.0' } expected = 'http://fake_url/some_path/v2.0' self._test_base_url_helper(expected, filters, ('token', auth_data)) def test_base_url_with_unversioned_extra_path_endpoint(self): auth_data = { 'serviceCatalog': [ { 'type': 'compute', 'endpoints': [ { 'region': 'FakeRegion', 'publicURL': 'http://fake_url/some_path' } ] } ] } filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'api_version': 'v2.0' } expected = 'http://fake_url/some_path/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) def test_set_scope_all_valid(self): for scope in self.auth_provider.SCOPES: self.auth_provider.scope = scope self.assertEqual(scope, self.auth_provider.scope) def test_set_scope_invalid(self): with testtools.ExpectedException(exceptions.InvalidScope, '.* invalid_scope .*'): self.auth_provider.scope = 'invalid_scope' 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.patchobject(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)) def test_base_url_with_extra_path_endpoint(self): auth_data = { 'catalog': [ { 'type': 'compute', 'endpoints': [ { 'region': 'FakeRegion', 'url': 'http://fake_url/some_path/v2.0', 'interface': 'public' } ] } ] } filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'api_version': 'v2.0' } expected = 'http://fake_url/some_path/v2.0' self._test_base_url_helper(expected, filters, ('token', auth_data)) def test_base_url_with_unversioned_extra_path_endpoint(self): auth_data = { 'catalog': [ { 'type': 'compute', 'endpoints': [ { 'region': 'FakeRegion', 'url': 'http://fake_url/some_path', 'interface': 'public' } ] } ] } filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'api_version': 'v2.0' } expected = 'http://fake_url/some_path/v2.0' self._test_base_url_helper(expected, filters, ('token', auth_data)) # Base URL test with scope only for V3 def test_base_url_scope_project(self): self.auth_provider.scope = 'project' 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) # Base URL test with scope only for V3 def test_base_url_unscoped_identity(self): self.auth_provider.scope = 'unscoped' self.patchobject(v3_client.V3TokenClient, 'raw_request', fake_identity._fake_v3_response_no_scope) self.filters = { 'service': 'identity', 'endpoint_type': 'publicURL', 'region': 'FakeRegion' } expected = fake_identity.FAKE_AUTH_URL self._test_base_url_helper(expected, self.filters) # Base URL test with scope only for V3 def test_base_url_unscoped_other(self): self.auth_provider.scope = 'unscoped' self.patchobject(v3_client.V3TokenClient, 'raw_request', fake_identity._fake_v3_response_no_scope) self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion' } self.assertRaises(exceptions.EndpointNotFound, self.auth_provider.base_url, auth_data=self.auth_provider.auth_data, filters=self.filters) def test_auth_parameters_with_scope_unset(self): # No scope defaults to 'project' all_creds = fake_credentials.FakeKeystoneV3AllCredentials() self.auth_provider.credentials = all_creds auth_params = self.auth_provider._auth_params() self.assertNotIn('scope', auth_params.keys()) for attr in all_creds.get_init_attributes(): if attr.startswith('domain_'): self.assertNotIn(attr, auth_params.keys()) else: self.assertIn(attr, auth_params.keys()) self.assertEqual(getattr(all_creds, attr), auth_params[attr]) def test_auth_parameters_with_project_scope(self): all_creds = fake_credentials.FakeKeystoneV3AllCredentials() self.auth_provider.credentials = all_creds self.auth_provider.scope = 'project' auth_params = self.auth_provider._auth_params() self.assertNotIn('scope', auth_params.keys()) for attr in all_creds.get_init_attributes(): if attr.startswith('domain_'): self.assertNotIn(attr, auth_params.keys()) else: self.assertIn(attr, auth_params.keys()) self.assertEqual(getattr(all_creds, attr), auth_params[attr]) def test_auth_parameters_with_domain_scope(self): all_creds = fake_credentials.FakeKeystoneV3AllCredentials() self.auth_provider.credentials = all_creds self.auth_provider.scope = 'domain' auth_params = self.auth_provider._auth_params() self.assertNotIn('scope', auth_params.keys()) for attr in all_creds.get_init_attributes(): if attr.startswith('project_'): self.assertNotIn(attr, auth_params.keys()) else: self.assertIn(attr, auth_params.keys()) self.assertEqual(getattr(all_creds, attr), auth_params[attr]) def test_auth_parameters_unscoped(self): all_creds = fake_credentials.FakeKeystoneV3AllCredentials() self.auth_provider.credentials = all_creds self.auth_provider.scope = 'unscoped' auth_params = self.auth_provider._auth_params() self.assertNotIn('scope', auth_params.keys()) for attr in all_creds.get_init_attributes(): if attr.startswith('project_') or attr.startswith('domain_'): self.assertNotIn(attr, auth_params.keys()) else: self.assertIn(attr, auth_params.keys()) self.assertEqual(getattr(all_creds, attr), auth_params[attr]) class TestKeystoneV3Credentials(base.TestCase): def testSetAttrUserDomain(self): creds = auth.KeystoneV3Credentials() creds.user_domain_name = 'user_domain' creds.domain_name = 'domain' self.assertEqual('user_domain', creds.user_domain_name) creds = auth.KeystoneV3Credentials() creds.domain_name = 'domain' creds.user_domain_name = 'user_domain' self.assertEqual('user_domain', creds.user_domain_name) def testSetAttrProjectDomain(self): creds = auth.KeystoneV3Credentials() creds.project_domain_name = 'project_domain' creds.domain_name = 'domain' self.assertEqual('project_domain', creds.user_domain_name) creds = auth.KeystoneV3Credentials() creds.domain_name = 'domain' creds.project_domain_name = 'project_domain' self.assertEqual('project_domain', creds.project_domain_name) def testProjectTenantNoCollision(self): creds = auth.KeystoneV3Credentials(tenant_id='tenant') self.assertEqual('tenant', creds.project_id) creds = auth.KeystoneV3Credentials(project_id='project') self.assertEqual('project', creds.tenant_id) creds = auth.KeystoneV3Credentials(tenant_name='tenant') self.assertEqual('tenant', creds.project_name) creds = auth.KeystoneV3Credentials(project_name='project') self.assertEqual('project', creds.tenant_name) def testProjectTenantCollision(self): attrs = {'tenant_id': 'tenant', 'project_id': 'project'} self.assertRaises( exceptions.InvalidCredentials, auth.KeystoneV3Credentials, **attrs) attrs = {'tenant_name': 'tenant', 'project_name': 'project'} self.assertRaises( exceptions.InvalidCredentials, auth.KeystoneV3Credentials, **attrs) class TestReplaceVersion(base.TestCase): def test_version_no_trailing_path(self): self.assertEqual( 'http://localhost:35357/v2.0', auth.replace_version('http://localhost:35357/v3', 'v2.0')) def test_version_no_trailing_path_solidus(self): self.assertEqual( 'http://localhost:35357/v2.0/', auth.replace_version('http://localhost:35357/v3/', 'v2.0')) def test_version_trailing_path(self): self.assertEqual( 'http://localhost:35357/v2.0/uuid', auth.replace_version('http://localhost:35357/v3/uuid', 'v2.0')) def test_version_trailing_path_solidus(self): self.assertEqual( 'http://localhost:35357/v2.0/uuid/', auth.replace_version('http://localhost:35357/v3/uuid/', 'v2.0')) def test_no_version_base(self): self.assertEqual( 'http://localhost:35357/v2.0', auth.replace_version('http://localhost:35357', 'v2.0')) def test_no_version_base_solidus(self): self.assertEqual( 'http://localhost:35357/v2.0', auth.replace_version('http://localhost:35357/', 'v2.0')) def test_no_version_path(self): self.assertEqual( 'http://localhost/identity/v2.0', auth.replace_version('http://localhost/identity', 'v2.0')) def test_no_version_path_solidus(self): self.assertEqual( 'http://localhost/identity/v2.0', auth.replace_version('http://localhost/identity/', 'v2.0')) def test_path_version(self): self.assertEqual( 'http://localhost/identity/v2.0', auth.replace_version('http://localhost/identity/v3', 'v2.0')) def test_path_version_solidus(self): self.assertEqual( 'http://localhost/identity/v2.0/', auth.replace_version('http://localhost/identity/v3/', 'v2.0')) def test_path_version_trailing_path(self): self.assertEqual( 'http://localhost/identity/v2.0/uuid', auth.replace_version('http://localhost/identity/v3/uuid', 'v2.0')) def test_path_version_trailing_path_solidus(self): self.assertEqual( 'http://localhost/identity/v2.0/uuid/', auth.replace_version('http://localhost/identity/v3/uuid/', 'v2.0')) class TestKeystoneV3AuthProvider_DomainScope(BaseAuthTestsSetUp): _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog'] _auth_provider_class = auth.KeystoneV3AuthProvider credentials = fake_credentials.FakeKeystoneV3Credentials() def setUp(self): super(TestKeystoneV3AuthProvider_DomainScope, self).setUp() self.patchobject(v3_client.V3TokenClient, 'raw_request', fake_identity._fake_v3_response_domain_scope) def test_get_auth_with_domain_scope(self): self.auth_provider.scope = 'domain' _, auth_data = self.auth_provider.get_auth() self.assertIn('domain', auth_data) self.assertNotIn('project', auth_data) class TestGetCredentials(base.TestCase): def test_invalid_identity_version(self): with testtools.ExpectedException(exceptions.InvalidIdentityVersion, '.* v1 .*'): auth.get_credentials('http://localhost/identity/v3', identity_version='v1') tempest-17.2.0/tempest/tests/lib/fake_credentials.py0000666000175100017510000000466013207044712022544 0ustar zuulzuul00000000000000# 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) class FakeKeystoneV3AllCredentials(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', project_name='fake_tenant_name', project_domain_name='fake_domain_name', domain_name='fake_domain_name' ) super(FakeKeystoneV3AllCredentials, self).__init__(**creds) tempest-17.2.0/tempest/tests/test_list_tests.py0000666000175100017510000000325213207044712021743 0ustar zuulzuul00000000000000# 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 os import re import subprocess import six from tempest.tests import base class TestTestList(base.TestCase): def test_stestr_list_no_errors(self): test_env = os.environ.copy() import_failures = [] p = subprocess.Popen(['stestr', 'list'], stdout=subprocess.PIPE, env=test_env) ids, err = p.communicate() self.assertEqual(0, p.returncode, "test discovery failed, one or more files cause an " "error on import %s" % ids) ids = six.text_type(ids).split('\n') for test_id in ids: if re.match('(\w+\.){3}\w+', test_id): if not test_id.startswith('tempest.'): parts = test_id.partition('tempest') fail_id = parts[1] + parts[2] import_failures.append(fail_id) error_message = ("The following tests have import failures and aren't" " being run with test filters %s" % import_failures) self.assertFalse(import_failures, error_message) tempest-17.2.0/tempest/README.rst0000666000175100017510000000377613207044712016475 0ustar zuulzuul00000000000000============================ Tempest Field Guide Overview ============================ Tempest is designed to be useful for a large number of different environments. This includes being useful for gating commits to OpenStack core projects, being used to validate OpenStack cloud implementations for both correctness, as well as a burn in tool for OpenStack clouds. As such Tempest tests come in many flavors, each with their own rules and guidelines. Below is the overview of the Tempest respository structure to make this clear. | tempest/ | api/ - API tests | scenario/ - complex scenario tests | tests/ - unit tests for Tempest internals Each of these directories contains different types of tests. What belongs in each directory, the rules and examples for good tests, are documented in a README.rst file in the directory. :ref:`api_field_guide` ---------------------- API tests are validation tests for the OpenStack API. They should not use the existing Python clients for OpenStack, but should instead use the Tempest implementations of clients. Having raw clients let us pass invalid JSON to the APIs and see the results, something we could not get with the native clients. When it makes sense, API testing should be moved closer to the projects themselves, possibly as functional tests in their unit test frameworks. :ref:`scenario_field_guide` --------------------------- Scenario tests are complex "through path" tests for OpenStack functionality. They are typically a series of steps where complicated state requiring multiple services is set up exercised, and torn down. Scenario tests should not use the existing Python clients for OpenStack, but should instead use the Tempest implementations of clients. :ref:`unit_tests_field_guide` ----------------------------- Unit tests are the self checks for Tempest. They provide functional verification and regression checking for the internal components of Tempest. They should be used to just verify that the individual pieces of Tempest are working as expected. tempest-17.2.0/tempest/test.py0000666000175100017510000011227713207044712016334 0ustar zuulzuul00000000000000# 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 atexit import os import sys import debtcollector.moves import fixtures from oslo_log import log as logging import six import testtools from tempest import clients from tempest.common import credentials_factory as credentials from tempest.common import utils from tempest import config from tempest.lib.common import fixed_network from tempest.lib.common import validation_resources as vr from tempest.lib import decorators from tempest.lib import exceptions as lib_exc LOG = logging.getLogger(__name__) CONF = config.CONF # TODO(oomichi): This test.idempotent_id should be removed after all projects # switch to use decorators.idempotent_id. idempotent_id = debtcollector.moves.moved_function( decorators.idempotent_id, 'idempotent_id', __name__, version='Mitaka', removal_version='?') attr = debtcollector.moves.moved_function( decorators.attr, 'attr', __name__, version='Pike', removal_version='?') services = debtcollector.moves.moved_function( utils.services, 'services', __name__, version='Pike', removal_version='?') requires_ext = debtcollector.moves.moved_function( utils.requires_ext, 'requires_ext', __name__, version='Pike', removal_version='?') is_extension_enabled = debtcollector.moves.moved_function( utils.is_extension_enabled, 'is_extension_enabled', __name__, version='Pike', removal_version='?') at_exit_set = set() def validate_tearDownClass(): if at_exit_set: LOG.error( "tearDownClass does not call the super's " "tearDownClass in these classes: \n" + str(at_exit_set)) atexit.register(validate_tearDownClass) class BaseTestCase(testtools.testcase.WithAttributes, testtools.TestCase): """The test base class defines Tempest framework for class level fixtures. `setUpClass` and `tearDownClass` are defined here and cannot be overwritten by subclasses (enforced via hacking rule T105). Set-up is split in a series of steps (setup stages), which can be overwritten by test classes. Set-up stages are: - skip_checks - setup_credentials - setup_clients - resource_setup Tear-down is also split in a series of steps (teardown stages), which are stacked for execution only if the corresponding setup stage had been reached during the setup phase. Tear-down stages are: - clear_credentials (defined in the base test class) - resource_cleanup """ # NOTE(andreaf) credentials holds a list of the credentials to be allocated # at class setup time. Credential types can be 'primary', 'alt', 'admin' or # a list of roles - the first element of the list being a label, and the # rest the actual roles credentials = [] # Track if setUpClass was invoked __setupclass_called = False # Network resources to be provisioned for the requested test credentials. # Only used with the dynamic credentials provider. _network_resources = {} # Stack of resource cleanups _class_cleanups = [] # Resources required to validate a server using ssh _validation_resources = {} # 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') # 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 _reset_class(cls): cls.__setup_credentials_called = False cls.__resource_cleanup_called = False cls.__skip_checks_called = False # Stack of callable to be invoked in reverse order cls._class_cleanups = [] # Stack of (name, callable) to be invoked in reverse order at teardown cls._teardowns = [] @classmethod def setUpClass(cls): cls.__setupclass_called = True # Reset state cls._reset_class() # It should never be overridden by descendants if hasattr(super(BaseTestCase, cls), 'setUpClass'): super(BaseTestCase, cls).setUpClass() # All the configuration checks that may generate a skip cls.skip_checks() if not cls.__skip_checks_called: raise RuntimeError("skip_checks for %s did not call the super's " "skip_checks" % cls.__name__) try: # Allocation of all required credentials and client managers cls._teardowns.append(('credentials', cls.clear_credentials)) cls.setup_credentials() if not cls.__setup_credentials_called: raise RuntimeError("setup_credentials for %s did not call the " "super's setup_credentials" % cls.__name__) # Shortcuts to clients cls.setup_clients() # Additional class-wide test resources cls._teardowns.append(('resources', cls.resource_cleanup)) cls.resource_setup() except Exception: etype, value, trace = sys.exc_info() LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass.", etype, cls.__name__) cls.tearDownClass() try: six.reraise(etype, value, trace) finally: del trace # to avoid circular refs @classmethod def tearDownClass(cls): # insert pdb breakpoint when pause_teardown is enabled if CONF.pause_teardown: cls.insert_pdb_breakpoint() at_exit_set.discard(cls) # It should never be overridden by descendants if hasattr(super(BaseTestCase, cls), 'tearDownClass'): super(BaseTestCase, cls).tearDownClass() # Save any existing exception, we always want to re-raise the original # exception only etype, value, trace = sys.exc_info() # If there was no exception during setup we shall re-raise the first # exception in teardown re_raise = (etype is None) while cls._teardowns: name, teardown = cls._teardowns.pop() # Catch any exception in tearDown so we can re-raise the original # exception at the end try: teardown() if name == 'resources': if not cls.__resource_cleanup_called: raise RuntimeError( "resource_cleanup for %s did not call the " "super's resource_cleanup" % cls.__name__) except Exception as te: sys_exec_info = sys.exc_info() tetype = sys_exec_info[0] # TODO(andreaf): Resource cleanup is often implemented by # storing an array of resources at class level, and cleaning # them up during `resource_cleanup`. # In case of failure during setup, some resource arrays might # not be defined at all, in which case the cleanup code might # trigger an AttributeError. In such cases we log # AttributeError as info instead of exception. Once all # cleanups are migrated to addClassResourceCleanup we can # remove this. if tetype is AttributeError and name == 'resources': LOG.info("tearDownClass of %s failed: %s", name, te) else: LOG.exception("teardown of %s failed: %s", name, te) if not etype: etype, value, trace = sys_exec_info # If exceptions were raised during teardown, and not before, re-raise # the first one if re_raise and etype is not None: try: six.reraise(etype, value, trace) finally: del trace # to avoid circular refs def tearDown(self): super(BaseTestCase, self).tearDown() # insert pdb breakpoint when pause_teardown is enabled if CONF.pause_teardown: BaseTestCase.insert_pdb_breakpoint() @classmethod def insert_pdb_breakpoint(cls): """Add pdb breakpoint. This can help in debugging process, cleaning of resources is paused, so they can be examined. """ import pdb pdb.set_trace() @classmethod def skip_checks(cls): """Class level skip checks. Subclasses verify in here all conditions that might prevent the execution of the entire test class. Skipping here prevents any other class fixture from being executed i.e. no credentials or other resource allocation will happen. Tests defined in the test class will no longer appear in test results. The `setUpClass` for the entire test class will be marked as SKIPPED instead. At this stage no test credentials are available, so skip checks should rely on configuration alone. This is deliberate since skips based on the result of an API call are discouraged. The following checks are implemented in `test.py` already: - check that alt credentials are available when requested by the test - check that admin credentials are available when requested by the test - check that the identity version specified by the test is marked as enabled in the configuration Overriders of skip_checks must always invoke skip_check on `super` first. Example:: @classmethod def skip_checks(cls): super(Example, cls).skip_checks() if not CONF.service_available.my_service: skip_msg = ("%s skipped as my_service is not available") raise cls.skipException(skip_msg % cls.__name__) """ cls.__skip_checks_called = True identity_version = cls.get_identity_version() # setting force_tenant_isolation to True also needs admin credentials. if ('admin' in cls.credentials or getattr(cls, 'force_tenant_isolation', False)): if not credentials.is_admin_available( identity_version=identity_version): raise cls.skipException( "Missing Identity Admin API credentials in configuration.") if 'alt' in cls.credentials and not credentials.is_alt_available( identity_version=identity_version): msg = "Missing a 2nd set of API credentials in configuration." raise cls.skipException(msg) if hasattr(cls, 'identity_version'): if cls.identity_version == 'v2': if not CONF.identity_feature_enabled.api_v2: raise cls.skipException("Identity api v2 is not enabled") elif cls.identity_version == 'v3': if not CONF.identity_feature_enabled.api_v3: raise cls.skipException("Identity api v3 is not enabled") @classmethod def setup_credentials(cls): """Allocate credentials and create the client managers from them. `setup_credentials` looks for the content of the `credentials` attribute in the test class. If the value is a non-empty collection, a credentials provider is setup, and credentials are provisioned or allocated based on the content of the collection. Every set of credentials is associated to an object of type `cls.client_manager`. The client manager is accessible by tests via class attribute `os_[type]`: Valid values in `credentials` are: - 'primary': A normal user is provisioned. It can be used only once. Multiple entries will be ignored. Clients are available at os_primary. - 'alt': A normal user other than 'primary' is provisioned. It can be used only once. Multiple entries will be ignored. Clients are available at os_alt. - 'admin': An admin user is provisioned. It can be used only once. Multiple entries will be ignored. Clients are available at os_admin. - A list in the format ['any_label', 'role1', ... , 'roleN']: A client with roles [1:] is provisioned. It can be used multiple times, with unique labels. Clients are available at os_roles_[0]. By default network resources are allocated (in case of dynamic credentials). Tests that do not need network or that require a custom network setup must specify which network resources shall be provisioned using the `set_network_resources()` method (note that it must be invoked before the `setup_credentials` is invoked on super). Example:: class TestWithCredentials(test.BaseTestCase): credentials = ['primary', 'admin', ['special', 'special_role1']] @classmethod def setup_credentials(cls): # set_network_resources must be called first cls.set_network_resources(network=True) super(TestWithCredentials, cls).setup_credentials() @classmethod def setup_clients(cls): cls.servers = cls.os_primary.compute.ServersClient() cls.admin_servers = cls.os_admin.compute.ServersClient() # certain API calls may require a user with a specific # role assigned. In this example `special_role1` is # assigned to the user in `cls.os_roles_special`. cls.special_servers = ( cls.os_roles_special.compute.ServersClient()) def test_special_servers(self): # Do something with servers pass """ cls.__setup_credentials_called = True for credentials_type in cls.credentials: # This may raise an exception in case credentials are not available # In that case we want to let the exception through and the test # fail accordingly if isinstance(credentials_type, six.string_types): manager = cls.get_client_manager( credential_type=credentials_type) setattr(cls, 'os_%s' % credentials_type, manager) # NOTE(jordanP): Tempest should use os_primary, os_admin # and os_alt throughout its code base but we keep the aliases # around for a while for Tempest plugins. Aliases should be # removed eventually. # Setup some common aliases if credentials_type == 'primary': cls.os = debtcollector.moves.moved_read_only_property( 'os', 'os_primary', version='Pike', removal_version='Queens') cls.manager =\ debtcollector.moves.moved_read_only_property( 'manager', 'os_primary', version='Pike', removal_version='Queens') if credentials_type == 'admin': cls.os_adm = debtcollector.moves.moved_read_only_property( 'os_adm', 'os_admin', version='Pike', removal_version='Queens') cls.admin_manager =\ debtcollector.moves.moved_read_only_property( 'admin_manager', 'os_admin', version='Pike', removal_version='Queens') if credentials_type == 'alt': cls.alt_manager =\ debtcollector.moves.moved_read_only_property( 'alt_manager', 'os_alt', version='Pike', removal_version='Queens') elif isinstance(credentials_type, list): manager = cls.get_client_manager(roles=credentials_type[1:], force_new=True) setattr(cls, 'os_roles_%s' % credentials_type[0], manager) @classmethod def setup_clients(cls): """Create aliases to the clients in the client managers. `setup_clients` is invoked after the credential provisioning step. Client manager objects are available to tests already. The purpose of this helper is to setup shortcuts to specific clients that are useful for the tests implemented in the test class. Its purpose is mostly for code readability, however it should be used carefully to avoid doing exactly the opposite, i.e. making the code unreadable and hard to debug. If aliases are defined in a super class it won't be obvious what they refer to, so it's good practice to define all aliases used in the class. Aliases are meant to be shortcuts to be used in tests, not shortcuts to avoid helper method attributes. If an helper method starts relying on a client alias and a subclass overrides that alias, it will become rather difficult to understand what the helper method actually does. Example:: class TestDoneItRight(test.BaseTestCase): credentials = ['primary', 'alt'] @classmethod def setup_clients(cls): super(TestDoneItRight, cls).setup_clients() cls.servers = cls.os_primary.ServersClient() cls.servers_alt = cls.os_alt.ServersClient() def _a_good_helper(self, clients): # Some complex logic we're going to use many times servers = clients.ServersClient() vm = servers.create_server(...) def delete_server(): test_utils.call_and_ignore_notfound_exc( servers.delete_server, vm['id']) self.addCleanup(self.delete_server) return vm def test_with_servers(self): vm = self._a_good_helper(os.primary) vm_alt = self._a_good_helper(os.alt) cls.servers.show_server(vm['id']) cls.servers_alt.show_server(vm_alt['id']) """ pass @classmethod def resource_setup(cls): """Class level resource setup for test cases. `resource_setup` is invoked once all credentials (and related network resources have been provisioned and after client aliases - if any - have been defined. The use case for `resource_setup` is test optimization: provisioning of project-specific "expensive" resources that are not dirtied by tests and can thus safely be re-used by multiple tests. System wide resources shared by all tests could instead be provisioned only once, before the test run. Resources provisioned here must be cleaned up during `resource_cleanup`. This is best achieved by scheduling a cleanup via `addClassResourceCleanup`. Some test resources have an asynchronous delete process. It's best practice for them to schedule a wait for delete via `addClassResourceCleanup` to avoid having resources in process of deletion when we reach the credentials cleanup step. Example:: @classmethod def resource_setup(cls): super(MyTest, cls).resource_setup() servers = cls.os_primary.compute.ServersClient() # Schedule delete and wait so that we can first delete the # two servers and then wait for both to delete # Create server 1 cls.shared_server = servers.create_server() # Create server 2. If something goes wrong we schedule cleanup # of server 1 anyways. try: cls.shared_server2 = servers.create_server() # Wait server 2 cls.addClassResourceCleanup( waiters.wait_for_server_termination, servers, cls.shared_server2['id'], ignore_error=False) finally: # Wait server 1 cls.addClassResourceCleanup( waiters.wait_for_server_termination, servers, cls.shared_server['id'], ignore_error=False) # Delete server 1 cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, servers.delete_server, cls.shared_server['id']) # Delete server 2 (if it was created) if hasattr(cls, 'shared_server2'): cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, servers.delete_server, cls.shared_server2['id']) """ pass @classmethod def resource_cleanup(cls): """Class level resource cleanup for test cases. Resource cleanup processes the stack of cleanups produced by `addClassResourceCleanup` and then cleans up validation resources if any were provisioned. All cleanups are processed whatever the outcome. Exceptions are accumulated and re-raised as a `MultipleExceptions` at the end. In most cases test cases won't need to override `resource_cleanup`, but if they do they must invoke `resource_cleanup` on super. Example:: class TestWithReallyComplexCleanup(test.BaseTestCase): @classmethod def resource_setup(cls): # provision resource A cls.addClassResourceCleanup(delete_resource, A) # provision resource B cls.addClassResourceCleanup(delete_resource, B) @classmethod def resource_cleanup(cls): # It's possible to override resource_cleanup but in most # cases it shouldn't be required. Nothing that may fail # should be executed before the call to super since it # might cause resource leak in case of error. super(TestWithReallyComplexCleanup, cls).resource_cleanup() # At this point test credentials are still available but # anything from the cleanup stack has been already deleted. """ cls.__resource_cleanup_called = True cleanup_errors = [] while cls._class_cleanups: try: fn, args, kwargs = cls._class_cleanups.pop() fn(*args, **kwargs) except Exception: cleanup_errors.append(sys.exc_info()) if cleanup_errors: raise testtools.MultipleExceptions(*cleanup_errors) @classmethod def addClassResourceCleanup(cls, fn, *arguments, **keywordArguments): """Add a cleanup function to be called during resource_cleanup. Functions added with addClassResourceCleanup will be called in reverse order of adding at the beginning of resource_cleanup, before any credential, networking or validation resources cleanup is processed. If a function added with addClassResourceCleanup raises an exception, the error will be recorded as a test error, and the next cleanup will then be run. Cleanup functions are always called during the test class tearDown fixture, even if an exception occured during setUp or tearDown. """ cls._class_cleanups.append((fn, arguments, keywordArguments)) def setUp(self): super(BaseTestCase, self).setUp() if not self.__setupclass_called: raise RuntimeError("setUpClass does not calls the super's" "setUpClass in the " + self.__class__.__name__) at_exit_set.add(self.__class__) test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) try: test_timeout = int(test_timeout) * self.TIMEOUT_SCALING_FACTOR 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)) @property def credentials_provider(self): return self._get_credentials_provider() @classmethod def get_identity_version(cls): """Returns the identity version used by the test class""" identity_version = getattr(cls, 'identity_version', None) return identity_version or CONF.identity.auth_version @classmethod def _get_credentials_provider(cls): """Returns a credentials provider If no credential provider exists yet creates one. It always use the configuration value from identity.auth_version, since we always want to provision accounts with the current version of the identity API. """ if (not hasattr(cls, '_creds_provider') or not cls._creds_provider or not cls._creds_provider.name == cls.__name__): force_tenant_isolation = getattr(cls, 'force_tenant_isolation', False) cls._creds_provider = credentials.get_credentials_provider( name=cls.__name__, network_resources=cls._network_resources, force_tenant_isolation=force_tenant_isolation) return cls._creds_provider @classmethod def get_client_manager(cls, credential_type=None, roles=None, force_new=None): """Returns an OpenStack client manager Returns an OpenStack client manager based on either credential_type or a list of roles. If neither is specified, it defaults to credential_type 'primary' :param credential_type: string - primary, alt or admin :param roles: list of roles :returns: the created client manager :raises skipException: if the requested credentials are not available """ if all([roles, credential_type]): msg = "Cannot get credentials by type and roles at the same time" raise ValueError(msg) if not any([roles, credential_type]): credential_type = 'primary' cred_provider = cls._get_credentials_provider() if roles: for role in roles: if not cred_provider.is_role_available(role): skip_msg = ( "%s skipped because the configured credential provider" " is not able to provide credentials with the %s role " "assigned." % (cls.__name__, role)) raise cls.skipException(skip_msg) params = dict(roles=roles) if force_new is not None: params.update(force_new=force_new) creds = cred_provider.get_creds_by_roles(**params) else: credentials_method = 'get_%s_creds' % credential_type if hasattr(cred_provider, credentials_method): creds = getattr(cred_provider, credentials_method)() else: raise lib_exc.InvalidCredentials( "Invalid credentials type %s" % credential_type) manager = cls.client_manager(credentials=creds.credentials) # NOTE(andreaf) Ensure credentials have user and project id fields. # It may not be the case when using pre-provisioned credentials. manager.auth_provider.set_auth() return manager @classmethod def clear_credentials(cls): """Clears creds if set""" if hasattr(cls, '_creds_provider'): cls._creds_provider.clear_creds() @staticmethod def _validation_resources_params_from_conf(): return dict( keypair=(CONF.validation.auth_method.lower() == "keypair"), floating_ip=(CONF.validation.connect_method.lower() == "floating"), security_group=CONF.validation.security_group, security_group_rules=CONF.validation.security_group_rules, use_neutron=CONF.service_available.neutron, ethertype='IPv' + str(CONF.validation.ip_version_for_ssh), floating_network_id=CONF.network.public_network_id, floating_network_name=CONF.network.floating_network_name) @classmethod def get_class_validation_resources(cls, os_clients): """Provision validation resources according to configuration This is a wrapper around `create_validation_resources` from `tempest.common.validation_resources` that passes parameters from Tempest configuration. Only one instance of class level validation resources is managed by the helper, so If resources were already provisioned before, existing ones will be returned. Resources are returned as a dictionary. They are also scheduled for automatic cleanup during class teardown using `addClassResourcesCleanup`. If `CONF.validation.run_validation` is False no resource will be provisioned at all. @param os_clients: Clients to be used to provision the resources. """ if not CONF.validation.run_validation: return if os_clients in cls._validation_resources: return cls._validation_resources[os_clients] if (CONF.validation.ip_version_for_ssh not in (4, 6) and CONF.service_available.neutron): msg = "Invalid IP version %s in ip_version_for_ssh. Use 4 or 6" raise lib_exc.InvalidConfiguration( msg % CONF.validation.ip_version_for_ssh) resources = vr.create_validation_resources( os_clients, **cls._validation_resources_params_from_conf()) cls.addClassResourceCleanup( vr.clear_validation_resources, os_clients, use_neutron=CONF.service_available.neutron, **resources) cls._validation_resources[os_clients] = resources return resources def get_test_validation_resources(self, os_clients): """Returns a dict of validation resources according to configuration Initialise a validation resources fixture based on configuration. Start the fixture and returns the validation resources. If `CONF.validation.run_validation` is False no resource will be provisioned at all. @param os_clients: Clients to be used to provision the resources. """ params = {} # Test will try to use the fixture, so for this to be useful # we must return a fixture. If validation is disabled though # we don't need to provision anything, which is the default # behavior for the fixture. if CONF.validation.run_validation: params = self._validation_resources_params_from_conf() validation = self.useFixture( vr.ValidationResourcesFixture(os_clients, **params)) return validation.resources @classmethod def set_network_resources(cls, network=False, router=False, subnet=False, dhcp=False): """Specify which network resources should be created The dynamic credentials provider by default provisions network resources for each user/project that is provisioned. This behavior can be altered using this method, which allows tests to define which specific network resources to be provisioned - none if no parameter is specified. This method is designed so that only the network resources set on the leaf class are honoured. Credentials are provisioned as part of the class setup fixture, during the `setup_credentials` step. For this to be effective this helper must be invoked before super's `setup_credentials` is executed. @param network @param router @param subnet @param dhcp Example:: @classmethod def setup_credentials(cls): # Do not setup network resources for this test cls.set_network_resources() super(MyTest, cls).setup_credentials() """ # If this is invoked after the credentials are setup, it won't take # any effect. To avoid this situation, fail the test in case this was # invoked too late in the test lifecycle. if cls.__setup_credentials_called: raise RuntimeError( "set_network_resources invoked after setup_credentials on the " "super class has been already invoked. For " "set_network_resources to have effect please invoke it before " "the call to super().setup_credentials") # Network resources should be set only once from callers # in order to ensure that even if it's called multiple times in # a chain of overloaded methods, the attribute is set only # in the leaf class. if not cls._network_resources: cls._network_resources = { 'network': network, 'router': router, 'subnet': subnet, 'dhcp': dhcp} @classmethod def get_tenant_network(cls, credentials_type='primary'): """Get the network to be used in testing :param credentials_type: The type of credentials for which to get the tenant network :return: network dict including 'id' and 'name' """ # Get a manager for the given credentials_type, but at least # always fall back on getting the manager for primary credentials if isinstance(credentials_type, six.string_types): manager = cls.get_client_manager(credential_type=credentials_type) elif isinstance(credentials_type, list): manager = cls.get_client_manager(roles=credentials_type[1:]) else: manager = cls.get_client_manager() # Make sure cred_provider exists and get a network client networks_client = manager.compute_networks_client cred_provider = cls._get_credentials_provider() # In case of nova network, isolated tenants are not able to list the # network configured in fixed_network_name, even if they can use it # for their servers, so using an admin network client to validate # the network name if (not CONF.service_available.neutron and credentials.is_admin_available( identity_version=cls.get_identity_version())): admin_creds = cred_provider.get_admin_creds() admin_manager = clients.Manager(admin_creds.credentials) networks_client = admin_manager.compute_networks_client return fixed_network.get_tenant_network( cred_provider, networks_client, CONF.compute.fixed_network_name) def assertEmpty(self, items, msg=None): """Asserts whether a sequence or collection is empty :param items: sequence or collection to be tested :param msg: message to be passed to the AssertionError :raises AssertionError: when items is not empty """ if msg is None: msg = "sequence or collection is not empty: %s" % items self.assertFalse(items, msg) def assertNotEmpty(self, items, msg=None): """Asserts whether a sequence or collection is not empty :param items: sequence or collection to be tested :param msg: message to be passed to the AssertionError :raises AssertionError: when items is empty """ if msg is None: msg = "sequence or collection is empty." self.assertTrue(items, msg) tempest-17.2.0/tempest/clients.py0000666000175100017510000003737313207044712017021 0ustar zuulzuul00000000000000# 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 tempest import config from tempest.lib import auth from tempest.lib import exceptions as lib_exc from tempest.lib.services import clients CONF = config.CONF class Manager(clients.ServiceClients): """Top level manager for OpenStack tempest clients""" def __init__(self, credentials, scope='project'): """Initialization of Manager class. Setup all services clients and make them available for tests cases. :param credentials: type Credentials or TestResources :param scope: default scope for tokens produced by the auth provider """ _, identity_uri = get_auth_provider_class(credentials) super(Manager, self).__init__( credentials=credentials, identity_uri=identity_uri, scope=scope, region=CONF.identity.region) # TODO(andreaf) When clients are initialised without the right # parameters available, the calls below will trigger a KeyError. # We should catch that and raise a better error. self._set_compute_clients() self._set_identity_clients() self._set_volume_clients() self._set_object_storage_clients() self._set_image_clients() self._set_network_clients() # TODO(andreaf) This is maintained for backward compatibility # with plugins, but it should removed eventually, since it was # never a stable interface and it's not useful anyways self.default_params = config.service_client_config() def _set_network_clients(self): self.network_agents_client = self.network.AgentsClient() self.network_extensions_client = self.network.ExtensionsClient() self.networks_client = self.network.NetworksClient() self.subnetpools_client = self.network.SubnetpoolsClient() self.subnets_client = self.network.SubnetsClient() self.ports_client = self.network.PortsClient() self.network_quotas_client = self.network.QuotasClient() self.floating_ips_client = self.network.FloatingIPsClient() self.metering_labels_client = self.network.MeteringLabelsClient() self.metering_label_rules_client = ( self.network.MeteringLabelRulesClient()) self.routers_client = self.network.RoutersClient() self.security_group_rules_client = ( self.network.SecurityGroupRulesClient()) self.security_groups_client = self.network.SecurityGroupsClient() self.network_versions_client = self.network.NetworkVersionsClient() self.service_providers_client = self.network.ServiceProvidersClient() self.tags_client = self.network.TagsClient() def _set_image_clients(self): if CONF.service_available.glance: self.image_client = self.image_v1.ImagesClient() self.image_member_client = self.image_v1.ImageMembersClient() self.image_client_v2 = self.image_v2.ImagesClient() self.image_member_client_v2 = self.image_v2.ImageMembersClient() self.namespaces_client = self.image_v2.NamespacesClient() self.resource_types_client = self.image_v2.ResourceTypesClient() self.namespace_objects_client = \ self.image_v2.NamespaceObjectsClient() self.schemas_client = self.image_v2.SchemasClient() self.namespace_properties_client = \ self.image_v2.NamespacePropertiesClient() self.namespace_tags_client = \ self.image_v2.NamespaceTagsClient() self.image_versions_client = \ self.image_v2.VersionsClient() def _set_compute_clients(self): self.agents_client = self.compute.AgentsClient() self.compute_networks_client = self.compute.NetworksClient() self.migrations_client = self.compute.MigrationsClient() self.security_group_default_rules_client = ( self.compute.SecurityGroupDefaultRulesClient()) self.certificates_client = self.compute.CertificatesClient() eip = CONF.compute_feature_enabled.enable_instance_password self.servers_client = self.compute.ServersClient( enable_instance_password=eip) self.server_groups_client = self.compute.ServerGroupsClient() self.limits_client = self.compute.LimitsClient() self.compute_images_client = self.compute.ImagesClient() self.keypairs_client = self.compute.KeyPairsClient() self.quotas_client = self.compute.QuotasClient() self.quota_classes_client = self.compute.QuotaClassesClient() self.flavors_client = self.compute.FlavorsClient() self.extensions_client = self.compute.ExtensionsClient() self.floating_ip_pools_client = self.compute.FloatingIPPoolsClient() self.floating_ips_bulk_client = self.compute.FloatingIPsBulkClient() self.compute_floating_ips_client = self.compute.FloatingIPsClient() self.compute_security_group_rules_client = ( self.compute.SecurityGroupRulesClient()) self.compute_security_groups_client = ( self.compute.SecurityGroupsClient()) self.interfaces_client = self.compute.InterfacesClient() self.fixed_ips_client = self.compute.FixedIPsClient() self.availability_zone_client = self.compute.AvailabilityZoneClient() self.aggregates_client = self.compute.AggregatesClient() self.services_client = self.compute.ServicesClient() self.tenant_usages_client = self.compute.TenantUsagesClient() self.hosts_client = self.compute.HostsClient() self.hypervisor_client = self.compute.HypervisorClient() self.instance_usages_audit_log_client = ( self.compute.InstanceUsagesAuditLogClient()) self.tenant_networks_client = self.compute.TenantNetworksClient() # NOTE: The following client needs special timeout values because # the API is a proxy for the other component. params_volume = { 'build_interval': CONF.volume.build_interval, 'build_timeout': CONF.volume.build_timeout } self.volumes_extensions_client = self.compute.VolumesClient( **params_volume) self.compute_versions_client = self.compute.VersionsClient( **params_volume) self.snapshots_extensions_client = self.compute.SnapshotsClient( **params_volume) def _set_identity_clients(self): # Clients below use the admin endpoint type of Keystone API v2 params_v2_admin = { 'endpoint_type': CONF.identity.v2_admin_endpoint_type} self.endpoints_client = self.identity_v2.EndpointsClient( **params_v2_admin) self.identity_client = self.identity_v2.IdentityClient( **params_v2_admin) self.tenants_client = self.identity_v2.TenantsClient( **params_v2_admin) self.roles_client = self.identity_v2.RolesClient(**params_v2_admin) self.users_client = self.identity_v2.UsersClient(**params_v2_admin) self.identity_services_client = self.identity_v2.ServicesClient( **params_v2_admin) # Clients below use the public endpoint type of Keystone API v2 params_v2_public = { 'endpoint_type': CONF.identity.v2_public_endpoint_type} self.identity_public_client = self.identity_v2.IdentityClient( **params_v2_public) self.tenants_public_client = self.identity_v2.TenantsClient( **params_v2_public) self.users_public_client = self.identity_v2.UsersClient( **params_v2_public) # Clients below use the endpoint type of Keystone API v3, which is set # in endpoint_type params_v3 = {'endpoint_type': CONF.identity.v3_endpoint_type} self.domains_client = self.identity_v3.DomainsClient(**params_v3) self.identity_v3_client = self.identity_v3.IdentityClient(**params_v3) self.trusts_client = self.identity_v3.TrustsClient(**params_v3) self.users_v3_client = self.identity_v3.UsersClient(**params_v3) self.endpoints_v3_client = self.identity_v3.EndPointsClient( **params_v3) self.roles_v3_client = self.identity_v3.RolesClient(**params_v3) self.inherited_roles_client = self.identity_v3.InheritedRolesClient( **params_v3) self.role_assignments_client = self.identity_v3.RoleAssignmentsClient( **params_v3) self.identity_services_v3_client = self.identity_v3.ServicesClient( **params_v3) self.policies_client = self.identity_v3.PoliciesClient(**params_v3) self.projects_client = self.identity_v3.ProjectsClient(**params_v3) self.regions_client = self.identity_v3.RegionsClient(**params_v3) self.credentials_client = self.identity_v3.CredentialsClient( **params_v3) self.groups_client = self.identity_v3.GroupsClient(**params_v3) self.identity_versions_v3_client = self.identity_v3.VersionsClient( **params_v3) self.oauth_consumers_client = self.identity_v3.OAUTHConsumerClient( **params_v3) self.oauth_token_client = self.identity_v3.OAUTHTokenClient( **params_v3) self.domain_config_client = self.identity_v3.DomainConfigurationClient( **params_v3) self.endpoint_filter_client = \ self.identity_v3.EndPointsFilterClient(**params_v3) self.endpoint_groups_client = self.identity_v3.EndPointGroupsClient( **params_v3) self.catalog_client = self.identity_v3.CatalogClient(**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 # API version is marked as enabled if CONF.identity_feature_enabled.api_v2: if CONF.identity.uri: self.token_client = self.identity_v2.TokenClient( auth_url=CONF.identity.uri) else: msg = 'Identity v2 API enabled, but no identity.uri set' raise lib_exc.InvalidConfiguration(msg) if CONF.identity_feature_enabled.api_v3: if CONF.identity.uri_v3: self.token_v3_client = self.identity_v3.V3TokenClient( auth_url=CONF.identity.uri_v3) else: msg = 'Identity v3 API enabled, but no identity.uri_v3 set' raise lib_exc.InvalidConfiguration(msg) def _set_volume_clients(self): if CONF.volume_feature_enabled.api_v1: self.backups_client = self.volume_v1.BackupsClient() self.encryption_types_client = \ self.volume_v1.EncryptionTypesClient() self.snapshots_client = self.volume_v1.SnapshotsClient() self.volume_availability_zone_client = \ self.volume_v1.AvailabilityZoneClient() self.volume_hosts_client = self.volume_v1.HostsClient() self.volume_limits_client = self.volume_v1.LimitsClient() self.volume_qos_client = self.volume_v1.QosSpecsClient() self.volume_quotas_client = self.volume_v1.QuotasClient() self.volume_services_client = self.volume_v1.ServicesClient() self.volume_types_client = self.volume_v1.TypesClient() self.volumes_client = self.volume_v1.VolumesClient() self.volumes_extension_client = self.volume_v1.ExtensionsClient() if CONF.volume_feature_enabled.api_v2: self.backups_v2_client = self.volume_v2.BackupsClient() self.encryption_types_v2_client = \ self.volume_v2.EncryptionTypesClient() self.snapshot_manage_v2_client = \ self.volume_v2.SnapshotManageClient() self.snapshots_v2_client = self.volume_v2.SnapshotsClient() self.volume_capabilities_v2_client = \ self.volume_v2.CapabilitiesClient() self.volume_manage_v2_client = self.volume_v2.VolumeManageClient() self.volume_qos_v2_client = self.volume_v2.QosSpecsClient() self.volume_services_v2_client = self.volume_v2.ServicesClient() self.volume_types_v2_client = self.volume_v2.TypesClient() self.volume_hosts_v2_client = self.volume_v2.HostsClient() self.volume_quotas_v2_client = self.volume_v2.QuotasClient() self.volume_quota_classes_v2_client = \ self.volume_v2.QuotaClassesClient() self.volume_scheduler_stats_v2_client = \ self.volume_v2.SchedulerStatsClient() self.volume_transfers_v2_client = \ self.volume_v2.TransfersClient() self.volume_v2_availability_zone_client = \ self.volume_v2.AvailabilityZoneClient() self.volume_v2_limits_client = self.volume_v2.LimitsClient() self.volumes_v2_client = self.volume_v2.VolumesClient() self.volumes_v2_extension_client = \ self.volume_v2.ExtensionsClient() # Set default client for users that don't need explicit version self.volumes_client_latest = self.volumes_v2_client self.snapshots_client_latest = self.snapshots_v2_client if CONF.volume_feature_enabled.api_v3: self.backups_v3_client = self.volume_v3.BackupsClient() self.group_types_v3_client = self.volume_v3.GroupTypesClient() self.groups_v3_client = self.volume_v3.GroupsClient() self.group_snapshots_v3_client = \ self.volume_v3.GroupSnapshotsClient() self.snapshots_v3_client = self.volume_v3.SnapshotsClient() self.volume_v3_messages_client = self.volume_v3.MessagesClient() self.volume_v3_versions_client = self.volume_v3.VersionsClient() self.volumes_v3_client = self.volume_v3.VolumesClient() # Set default client for users that don't need explicit version self.volumes_client_latest = self.volumes_v3_client self.snapshots_client_latest = self.snapshots_v3_client def _set_object_storage_clients(self): self.account_client = self.object_storage.AccountClient() self.bulk_client = self.object_storage.BulkMiddlewareClient() self.capabilities_client = self.object_storage.CapabilitiesClient() self.container_client = self.object_storage.ContainerClient() self.object_client = self.object_storage.ObjectClient() def get_auth_provider_class(credentials): if isinstance(credentials, auth.KeystoneV3Credentials): return auth.KeystoneV3AuthProvider, CONF.identity.uri_v3 else: return auth.KeystoneV2AuthProvider, CONF.identity.uri def get_auth_provider(credentials, pre_auth=False, scope='project'): # kwargs for auth provider match the common ones used by service clients default_params = config.service_client_config() if credentials is None: raise lib_exc.InvalidCredentials( 'Credentials must be specified') auth_provider_class, auth_url = get_auth_provider_class( credentials) _auth_provider = auth_provider_class(credentials, auth_url, scope=scope, **default_params) if pre_auth: _auth_provider.set_auth() return _auth_provider tempest-17.2.0/tempest/test_discover/0000775000175100017510000000000013207045130017637 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/test_discover/plugins.py0000666000175100017510000002044513207044712021706 0ustar zuulzuul00000000000000# 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 abc from oslo_log import log as logging import six import stevedore from tempest.lib.common.utils import misc from tempest.lib.services import clients LOG = logging.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class TempestPlugin(object): """Provide basic hooks for an external plugin To provide tempest the necessary information to run the plugin. """ @abc.abstractmethod def load_tests(self): """Return the information necessary to load the tests in the plugin. :return: a tuple with the first value being the test_dir and the second being the top_level :rtype: tuple """ return @abc.abstractmethod def register_opts(self, conf): """Add additional configuration options to tempest. This method will be run for the plugin during the register_opts() function in tempest.config. :param ConfigOpts conf: The conf object that can be used to register additional options on. Example:: # Config options are defined in a config.py module service_option = cfg.BoolOpt( "my_service", default=True, help="Whether or not my service is available") # Note: as long as the group is listed in get_opt_lists, # it will be possible to access its optins in the plugin code # via ("-" in the group name are replaces with "_"): # CONF.my_service. my_service_group = cfg.OptGroup(name="my-service", title="My service options") MyServiceGroup = [] # (...) More groups and options... # Plugin is implemented in a plugin.py module from my_plugin import config as my_config def register_opts(self, conf): conf.register_opt(my_config.service_option, group='service_available') conf.register_group(my_config.my_service_group) conf.register_opts(my_config.MyServiceGroup, my_config.my_service_group) conf.register_group(my_config.my_service_feature_group) conf.register_opts(my_config.MyServiceFeaturesGroup, my_config.my_service_feature_group) """ return @abc.abstractmethod def get_opt_lists(self): """Get a list of options for sample config generation :return option_list: A list of tuples with the group name and options in that group. :rtype: list Example:: # Config options are defined in a config.py module service_option = cfg.BoolOpt( "my_service", default=True, help="Whether or not my service is available") my_service_group = cfg.OptGroup(name="my-service", title="My service options") my_service_features_group = cfg.OptGroup( name="my-service-features", title="My service available features") MyServiceGroup = [] MyServiceFeaturesGroup = [] # Plugin is implemented in a plugin.py module from my_plugin import config as my_config def get_opt_lists(self, conf): return [ (my_service_group.name, MyServiceGroup), (my_service_features_group.name, MyServiceFeaturesGroup) ] """ return [] def get_service_clients(self): """Get a list of the service clients for registration If the plugin implements service clients for one or more APIs, it may return their details by this method for automatic registration in any ServiceClients object instantiated by tests. The default implementation returns an empty list. :returns: Each element of the list represents the service client for an API. Each dict must define all parameters required for the invocation of `service_clients.ServiceClients.register_service_client_module`. :rtype: list of dictionaries Example implementation with one service client:: def get_service_clients(self): # Example implementation with one service client myservice_config = config.service_client_config('myservice') params = { 'name': 'myservice', 'service_version': 'myservice', 'module_path': 'myservice_tempest_tests.services', 'client_names': ['API1Client', 'API2Client'], } params.update(myservice_config) return [params] Example implementation with two service clients:: def get_service_clients(self): # Example implementation with two service clients foo1_config = config.service_client_config('foo') params_foo1 = { 'name': 'foo_v1', 'service_version': 'foo.v1', 'module_path': 'bar_tempest_tests.services.foo.v1', 'client_names': ['API1Client', 'API2Client'], } params_foo1.update(foo_config) foo2_config = config.service_client_config('foo') params_foo2 = { 'name': 'foo_v2', 'service_version': 'foo.v2', 'module_path': 'bar_tempest_tests.services.foo.v2', 'client_names': ['API1Client', 'API2Client'], } params_foo2.update(foo2_config) return [params_foo1, params_foo2] """ return [] @misc.singleton class TempestTestPluginManager(object): """Tempest test plugin manager class This class is used to manage the lifecycle of external tempest test plugins. It provides functions for getting set """ def __init__(self): self.ext_plugins = stevedore.ExtensionManager( 'tempest.test_plugins', invoke_on_load=True, propagate_map_exceptions=True, on_load_failure_callback=self.failure_hook) @staticmethod def failure_hook(_, ep, err): LOG.error('Could not load %r: %s', ep.name, err) raise err def get_plugin_load_tests_tuple(self): load_tests_dict = {} for plug in self.ext_plugins: load_tests_dict[plug.name] = plug.obj.load_tests() return load_tests_dict def register_plugin_opts(self, conf): for plug in self.ext_plugins: try: plug.obj.register_opts(conf) except Exception: LOG.exception('Plugin %s raised an exception trying to run ' 'register_opts', plug.name) def get_plugin_options_list(self): plugin_options = [] for plug in self.ext_plugins: opt_list = plug.obj.get_opt_lists() if opt_list: plugin_options.extend(opt_list) return plugin_options def _register_service_clients(self): registry = clients.ClientsRegistry() for plug in self.ext_plugins: try: service_clients = plug.obj.get_service_clients() if service_clients: registry.register_service_client( plug.name, service_clients) except Exception: LOG.exception('Plugin %s raised an exception trying to run ' 'get_service_clients', plug.name) tempest-17.2.0/tempest/test_discover/test_discover.py0000666000175100017510000000365613207044712023107 0ustar zuulzuul00000000000000# 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 os import sys from tempest.test_discover import plugins if sys.version_info >= (2, 7): import unittest else: import unittest2 as unittest def load_tests(loader, tests, pattern): ext_plugins = plugins.TempestTestPluginManager() suite = unittest.TestSuite() base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0] base_path = os.path.split(base_path)[0] # Load local tempest tests for test_dir in ['tempest/api', 'tempest/scenario']: full_test_dir = os.path.join(base_path, test_dir) if not pattern: suite.addTests(loader.discover(full_test_dir, top_level_dir=base_path)) else: suite.addTests(loader.discover(full_test_dir, pattern=pattern, top_level_dir=base_path)) plugin_load_tests = ext_plugins.get_plugin_load_tests_tuple() if not plugin_load_tests: return suite # Load any installed plugin tests for plugin in plugin_load_tests: test_dir, top_path = plugin_load_tests[plugin] if not pattern: suite.addTests(loader.discover(test_dir, top_level_dir=top_path)) else: suite.addTests(loader.discover(test_dir, pattern=pattern, top_level_dir=top_path)) return suite tempest-17.2.0/tempest/test_discover/__init__.py0000666000175100017510000000000013207044712021745 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/common/0000775000175100017510000000000013207045130016252 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/common/compute.py0000666000175100017510000004255213207044712020317 0ustar zuulzuul00000000000000# Copyright (c) 2015 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 base64 import socket import ssl import struct import textwrap import six from six.moves.urllib import parse as urlparse from oslo_log import log as logging from oslo_utils import excutils from tempest.common import waiters from tempest import config from tempest.lib.common import fixed_network from tempest.lib.common import rest_client from tempest.lib.common.utils import data_utils if six.PY2: ord_func = ord else: ord_func = int CONF = config.CONF LOG = logging.getLogger(__name__) def is_scheduler_filter_enabled(filter_name): """Check the list of enabled compute scheduler filters from config. This function checks whether the given compute scheduler filter is available and configured in the config file. If the scheduler_available_filters option is set to 'all' (Default value. which means default filters are configured in nova) in tempest.conf then, this function returns True with assumption that requested filter 'filter_name' is one of available filter in nova ("nova.scheduler.filters.all_filters"). """ filters = CONF.compute_feature_enabled.scheduler_available_filters if not filters: return False if 'all' in filters: return True if filter_name in filters: return True return False def create_test_server(clients, validatable=False, validation_resources=None, tenant_network=None, wait_until=None, volume_backed=False, name=None, flavor=None, image_id=None, **kwargs): """Common wrapper utility returning a test server. This method is a common wrapper returning a test server that can be pingable or sshable. :param clients: Client manager which provides OpenStack Tempest clients. :param validatable: Whether the server will be pingable or sshable. :param validation_resources: Resources created for the connection to the server. Include a keypair, a security group and an IP. :param tenant_network: Tenant network to be used for creating a server. :param wait_until: Server status to wait for the server to reach after its creation. :param volume_backed: Whether the server is volume backed or not. If this is true, a volume will be created and create server will be requested with 'block_device_mapping_v2' populated with below values: -------------------------------------------- bd_map_v2 = [{ 'uuid': volume['volume']['id'], 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': True}] kwargs['block_device_mapping_v2'] = bd_map_v2 --------------------------------------------- If server needs to be booted from volume with other combination of bdm inputs than mentioned above, then pass the bdm inputs explicitly as kwargs and image_id as empty string (''). :param name: Name of the server to be provisioned. If not defined a random string ending with '-instance' will be generated. :param flavor: Flavor of the server to be provisioned. If not defined, CONF.compute.flavor_ref will be used instead. :param image_id: ID of the image to be used to provision the server. If not defined, CONF.compute.image_ref will be used instead. :returns: a tuple """ # TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE if name is None: name = data_utils.rand_name(__name__ + "-instance") if flavor is None: flavor = CONF.compute.flavor_ref if image_id is None: image_id = CONF.compute.image_ref kwargs = fixed_network.set_networks_kwarg( tenant_network, kwargs) or {} multiple_create_request = (max(kwargs.get('min_count', 0), kwargs.get('max_count', 0)) > 1) if CONF.validation.run_validation and validatable: # As a first implementation, multiple pingable or sshable servers will # not be supported if multiple_create_request: msg = ("Multiple pingable or sshable servers not supported at " "this stage.") raise ValueError(msg) LOG.debug("Provisioning test server with validation resources %s", validation_resources) if 'security_groups' in kwargs: kwargs['security_groups'].append( {'name': validation_resources['security_group']['name']}) else: try: kwargs['security_groups'] = [ {'name': validation_resources['security_group']['name']}] except KeyError: LOG.debug("No security group provided.") if 'key_name' not in kwargs: try: kwargs['key_name'] = validation_resources['keypair']['name'] except KeyError: LOG.debug("No key provided.") if CONF.validation.connect_method == 'floating': if wait_until is None: wait_until = 'ACTIVE' if 'user_data' not in kwargs: # If nothing overrides the default user data script then run # a simple script on the host to print networking info. This is # to aid in debugging ssh failures. script = ''' #!/bin/sh echo "Printing {user} user authorized keys" cat ~{user}/.ssh/authorized_keys || true '''.format(user=CONF.validation.image_ssh_user) script_clean = textwrap.dedent(script).lstrip().encode('utf8') script_b64 = base64.b64encode(script_clean) kwargs['user_data'] = script_b64 if volume_backed: volume_name = data_utils.rand_name(__name__ + '-volume') volumes_client = clients.volumes_v2_client params = {'name': volume_name, 'imageRef': image_id, 'size': CONF.volume.volume_size} volume = volumes_client.create_volume(**params) waiters.wait_for_volume_resource_status(volumes_client, volume['volume']['id'], 'available') bd_map_v2 = [{ 'uuid': volume['volume']['id'], 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': True}] kwargs['block_device_mapping_v2'] = bd_map_v2 # Since this is boot from volume an image does not need # to be specified. image_id = '' body = clients.servers_client.create_server(name=name, imageRef=image_id, flavorRef=flavor, **kwargs) # handle the case of multiple servers if multiple_create_request: # Get servers created which name match with name param. body_servers = clients.servers_client.list_servers() servers = \ [s for s in body_servers['servers'] if s['name'].startswith(name)] else: body = rest_client.ResponseBody(body.response, body['server']) servers = [body] def _setup_validation_fip(): if CONF.service_available.neutron: ifaces = clients.interfaces_client.list_interfaces(server['id']) validation_port = None for iface in ifaces['interfaceAttachments']: if iface['net_id'] == tenant_network['id']: validation_port = iface['port_id'] break if not validation_port: # NOTE(artom) This will get caught by the catch-all clause in # the wait_until loop below raise ValueError('Unable to setup floating IP for validation: ' 'port not found on tenant network') clients.floating_ips_client.update_floatingip( validation_resources['floating_ip']['id'], port_id=validation_port) else: fip_client = clients.compute_floating_ips_client fip_client.associate_floating_ip_to_server( floating_ip=validation_resources['floating_ip']['ip'], server_id=servers[0]['id']) if wait_until: for server in servers: try: waiters.wait_for_server_status( clients.servers_client, server['id'], wait_until) # Multiple validatable servers are not supported for now. Their # creation will fail with the condition above (l.58). if CONF.validation.run_validation and validatable: if CONF.validation.connect_method == 'floating': _setup_validation_fip() except Exception: with excutils.save_and_reraise_exception(): for server in servers: try: clients.servers_client.delete_server( server['id']) except Exception: LOG.exception('Deleting server %s failed', server['id']) for server in servers: # NOTE(artom) If the servers were booted with volumes # and with delete_on_termination=False we need to wait # for the servers to go away before proceeding with # cleanup, otherwise we'll attempt to delete the # volumes while they're still attached to servers that # are in the process of being deleted. try: waiters.wait_for_server_termination( clients.servers_client, server['id']) except Exception: LOG.exception('Server %s failed to delete in time', server['id']) return body, servers def shelve_server(servers_client, server_id, force_shelve_offload=False): """Common wrapper utility to shelve server. This method is a common wrapper to make server in 'SHELVED' or 'SHELVED_OFFLOADED' state. :param servers_clients: Compute servers client instance. :param server_id: Server to make in shelve state :param force_shelve_offload: Forcefully offload shelve server if it is configured not to offload server automatically after offload time. """ servers_client.shelve_server(server_id) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: waiters.wait_for_server_status(servers_client, server_id, 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: waiters.wait_for_server_status(servers_client, server_id, 'SHELVED') if force_shelve_offload: servers_client.shelve_offload_server(server_id) waiters.wait_for_server_status(servers_client, server_id, 'SHELVED_OFFLOADED') def create_websocket(url): url = urlparse.urlparse(url) if url.scheme == 'https': client_socket = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) else: client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) client_socket.connect((url.hostname, url.port)) # Turn the Socket into a WebSocket to do the communication return _WebSocket(client_socket, url) class _WebSocket(object): def __init__(self, client_socket, url): """Contructor for the WebSocket wrapper to the socket.""" self._socket = client_socket # cached stream for early frames. self.cached_stream = b'' # Upgrade the HTTP connection to a WebSocket self._upgrade(url) def _recv(self, recv_size): """Wrapper to receive data from the cached stream or socket.""" if recv_size <= 0: return None data_from_cached = b'' data_from_socket = b'' if len(self.cached_stream) > 0: read_from_cached = min(len(self.cached_stream), recv_size) data_from_cached += self.cached_stream[:read_from_cached] self.cached_stream = self.cached_stream[read_from_cached:] recv_size -= read_from_cached if recv_size > 0: data_from_socket = self._socket.recv(recv_size) return data_from_cached + data_from_socket def receive_frame(self): """Wrapper for receiving data to parse the WebSocket frame format""" # We need to loop until we either get some bytes back in the frame # or no data was received (meaning the socket was closed). This is # done to handle the case where we get back some empty frames while True: header = self._recv(2) # If we didn't receive any data, just return None if not header: return None # We will make the assumption that we are only dealing with # frames less than 125 bytes here (for the negotiation) and # that only the 2nd byte contains the length, and since the # server doesn't do masking, we can just read the data length if ord_func(header[1]) & 127 > 0: return self._recv(ord_func(header[1]) & 127) def send_frame(self, data): """Wrapper for sending data to add in the WebSocket frame format.""" frame_bytes = list() # For the first byte, want to say we are sending binary data (130) frame_bytes.append(130) # Only sending negotiation data so don't need to worry about > 125 # We do need to add the bit that says we are masking the data frame_bytes.append(len(data) | 128) # We don't really care about providing a random mask for security # So we will just hard-code a value since a test program mask = [7, 2, 1, 9] for i in range(len(mask)): frame_bytes.append(mask[i]) # Mask each of the actual data bytes that we are going to send for i in range(len(data)): frame_bytes.append(ord_func(data[i]) ^ mask[i % 4]) # Convert our integer list to a binary array of bytes frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes) self._socket.sendall(frame_bytes) def close(self): """Helper method to close the connection.""" # Close down the real socket connection and exit the test program if self._socket is not None: self._socket.shutdown(1) self._socket.close() self._socket = None def _upgrade(self, url): """Upgrade the HTTP connection to a WebSocket and verify.""" # The real request goes to the /websockify URI always reqdata = 'GET /websockify HTTP/1.1\r\n' reqdata += 'Host: %s:%s\r\n' % (url.hostname, url.port) # Tell the HTTP Server to Upgrade the connection to a WebSocket reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n' # The token=xxx is sent as a Cookie not in the URI reqdata += 'Cookie: %s\r\n' % url.query # Use a hard-coded WebSocket key since a test program reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n' reqdata += 'Sec-WebSocket-Version: 13\r\n' # We are choosing to use binary even though browser may do Base64 reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n' # Send the HTTP GET request and get the response back self._socket.sendall(reqdata.encode('utf8')) self.response = data = self._socket.recv(4096) # Loop through & concatenate all of the data in the response body end_loc = self.response.find(b'\r\n\r\n') while data and end_loc < 0: data = self._socket.recv(4096) self.response += data end_loc = self.response.find(b'\r\n\r\n') if len(self.response) > end_loc + 4: # In case some frames (e.g. the first RFP negotiation) have # arrived, cache it for next reading. self.cached_stream = self.response[end_loc + 4:] # ensure response ends with '\r\n\r\n'. self.response = self.response[:end_loc + 4] tempest-17.2.0/tempest/common/utils/0000775000175100017510000000000013207045130017412 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/common/utils/net_info.py0000666000175100017510000000162713207044712021602 0ustar zuulzuul00000000000000# 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 re RE_OWNER = re.compile('^network:.*router_.*interface.*') def _is_owner_router_interface(owner): return bool(RE_OWNER.match(owner)) def is_router_interface_port(port): """Based on the port attributes determines is it a router interface.""" return _is_owner_router_interface(port['device_owner']) tempest-17.2.0/tempest/common/utils/net_utils.py0000666000175100017510000000466013207044712022007 0ustar zuulzuul00000000000000# Copyright 2016 Hewlett Packard Enterprise Development Company # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import exceptions as lib_exc def get_unused_ip_addresses(ports_client, subnets_client, network_id, subnet_id, count): """Return a list with the specified number of unused IP addresses This method uses the given ports_client to find the specified number of unused IP addresses on the given subnet using the supplied subnets_client """ ports = ports_client.list_ports(network_id=network_id)['ports'] subnet = subnets_client.show_subnet(subnet_id) ip_net = netaddr.IPNetwork(subnet['subnet']['cidr']) subnet_set = netaddr.IPSet(ip_net.iter_hosts()) alloc_set = netaddr.IPSet() # prune out any addresses already allocated to existing ports for port in ports: for fixed_ip in port.get('fixed_ips'): alloc_set.add(fixed_ip['ip_address']) # exclude gateway_ip of subnet gateway_ip = subnet['subnet']['gateway_ip'] if gateway_ip: alloc_set.add(gateway_ip) av_set = subnet_set - alloc_set addrs = [] for cidr in reversed(av_set.iter_cidrs()): for ip in reversed(cidr): addrs.append(str(ip)) if len(addrs) == count: return addrs msg = "Insufficient IP addresses available" raise lib_exc.BadRequest(message=msg) def get_ping_payload_size(mtu, ip_version): """Return the maximum size of ping payload that will fit into MTU.""" if not mtu: return None if ip_version == 4: ip_header = 20 icmp_header = 8 else: ip_header = 40 icmp_header = 4 res = mtu - ip_header - icmp_header if res < 0: raise lib_exc.BadRequest( message='MTU = %(mtu)d is too low for IPv%(ip_version)d' % { 'mtu': mtu, 'ip_version': ip_version, }) return res tempest-17.2.0/tempest/common/utils/__init__.py0000666000175100017510000001070613207044712021536 0ustar zuulzuul00000000000000# 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 functools from functools import partial import testtools from tempest import config from tempest.exceptions import InvalidServiceTag from tempest.lib.common.utils import data_utils as lib_data_utils from tempest.lib import decorators CONF = config.CONF class DataUtils(object): def __getattr__(self, attr): if attr == 'rand_name': # NOTE(flwang): This is a proxy to generate a random name that # includes a random number and a prefix if one is configured in # CONF.resources_prefix attr_obj = partial(lib_data_utils.rand_name, prefix=CONF.resources_prefix) else: attr_obj = getattr(lib_data_utils, attr) self.__dict__[attr] = attr_obj return attr_obj data_utils = DataUtils() def get_service_list(): service_list = { 'compute': CONF.service_available.nova, 'image': CONF.service_available.glance, 'volume': CONF.service_available.cinder, # NOTE(masayukig): We have two network services which are neutron and # nova-network. And we have no way to know whether nova-network is # available or not. After the pending removal of nova-network from # nova, we can treat the network/neutron case in the same manner as # the other services. 'network': True, # NOTE(masayukig): Tempest tests always require the identity service. # So we should set this True here. 'identity': True, 'object_storage': CONF.service_available.swift, } return service_list def services(*args): """A decorator used to set an attr for each service used in a test case This decorator applies a testtools attr for each service that gets exercised by a test case. """ def decorator(f): known_services = get_service_list() for service in args: if service not in known_services: raise InvalidServiceTag('%s is not a valid service' % service) decorators.attr(type=list(args))(f) @functools.wraps(f) def wrapper(self, *func_args, **func_kwargs): service_list = get_service_list() for service in args: if not service_list[service]: msg = 'Skipped because the %s service is not available' % ( service) raise testtools.TestCase.skipException(msg) return f(self, *func_args, **func_kwargs) return wrapper return decorator def requires_ext(**kwargs): """A decorator to skip tests if an extension is not enabled @param extension @param service """ def decorator(func): @functools.wraps(func) def wrapper(*func_args, **func_kwargs): if not is_extension_enabled(kwargs['extension'], kwargs['service']): msg = "Skipped because %s extension: %s is not enabled" % ( kwargs['service'], kwargs['extension']) raise testtools.TestCase.skipException(msg) return func(*func_args, **func_kwargs) return wrapper return decorator def is_extension_enabled(extension_name, service): """A function that will check the list of enabled extensions from config """ config_dict = { 'compute': CONF.compute_feature_enabled.api_extensions, 'volume': CONF.volume_feature_enabled.api_extensions, 'network': CONF.network_feature_enabled.api_extensions, 'object': CONF.object_storage_feature_enabled.discoverable_apis, 'identity': CONF.identity_feature_enabled.api_extensions } if not config_dict[service]: return False if config_dict[service][0] == 'all': return True if extension_name in config_dict[service]: return True return False tempest-17.2.0/tempest/common/utils/linux/0000775000175100017510000000000013207045130020551 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/common/utils/linux/remote_client.py0000666000175100017510000001434713207044712023774 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import time from oslo_log import log as logging from tempest import config from tempest.lib.common.utils.linux import remote_client import tempest.lib.exceptions CONF = config.CONF LOG = logging.getLogger(__name__) class RemoteClient(remote_client.RemoteClient): # TODO(oomichi): Make this class deprecated after migrating # necessary methods to tempest.lib and cleaning # unnecessary methods up from this class. def __init__(self, ip_address, username, password=None, pkey=None, server=None, servers_client=None): """Executes commands in a VM over ssh :param ip_address: IP address to ssh to :param username: ssh username :param password: ssh password (optional) :param pkey: ssh public key (optional) :param server: server dict, used for debugging purposes :param servers_client: servers client, used for debugging purposes """ super(RemoteClient, self).__init__( ip_address, username, password=password, pkey=pkey, server=server, servers_client=servers_client, ssh_timeout=CONF.validation.ssh_timeout, connect_timeout=CONF.validation.connect_timeout, console_output_enabled=CONF.compute_feature_enabled.console_output, ssh_shell_prologue=CONF.validation.ssh_shell_prologue, ping_count=CONF.validation.ping_count, ping_size=CONF.validation.ping_size) # Note that this method will not work on SLES11 guests, as they do # not support the TYPE column on lsblk def get_disks(self): # Select root disk devices as shown by lsblk command = 'lsblk -lb --nodeps' output = self.exec_command(command) selected = [] pos = None for l in output.splitlines(): if pos is None and l.find("TYPE") > 0: pos = l.find("TYPE") # Show header line too selected.append(l) # lsblk lists disk type in a column right-aligned with TYPE elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk": selected.append(l) if selected: return "\n".join(selected) else: msg = "'TYPE' column is required but the output doesn't have it: " raise tempest.lib.exceptions.TempestException(msg + output) def get_boot_time(self): cmd = 'cut -f1 -d. /proc/uptime' boot_secs = self.exec_command(cmd) boot_time = time.time() - int(boot_secs) return time.localtime(boot_time) def write_to_console(self, message): message = re.sub("([$\\`])", "\\\\\\\\\\1", message) # usually to /dev/ttyS0 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message return self.exec_command(cmd) def get_mac_address(self, nic=""): show_nic = "show {nic} ".format(nic=nic) if nic else "" cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic return self.exec_command(cmd).strip().lower() def get_nic_name_by_mac(self, address): cmd = "ip -o link | awk '/%s/ {print $2}'" % address nic = self.exec_command(cmd) return nic.strip().strip(":").split('@')[0].lower() def get_nic_name_by_ip(self, address): cmd = "ip -o addr | awk '/%s/ {print $2}'" % address nic = self.exec_command(cmd) return nic.strip().strip(":").split('@')[0].lower() def get_dns_servers(self): cmd = 'cat /etc/resolv.conf' resolve_file = self.exec_command(cmd).strip().split('\n') entries = (l.split() for l in resolve_file) dns_servers = [l[1] for l in entries if len(l) and l[0] == 'nameserver'] return dns_servers def _renew_lease_udhcpc(self, fixed_ip=None): """Renews DHCP lease via udhcpc client. """ file_path = '/var/run/udhcpc.' nic_name = self.get_nic_name_by_ip(fixed_ip) pid = self.exec_command('cat {path}{nic}.pid'. format(path=file_path, nic=nic_name)) pid = pid.strip() cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig='USR1') self.exec_command(cmd) def _renew_lease_dhclient(self, fixed_ip=None): """Renews DHCP lease via dhclient client. """ cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient" self.exec_command(cmd) def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'): """Wrapper method for renewing DHCP lease via given client Supporting: * udhcpc * dhclient """ # TODO(yfried): add support for dhcpcd supported_clients = ['udhcpc', 'dhclient'] if dhcp_client not in supported_clients: raise tempest.lib.exceptions.InvalidConfiguration( '%s DHCP client unsupported' % dhcp_client) if dhcp_client == 'udhcpc' and not fixed_ip: raise ValueError("need to set 'fixed_ip' for udhcpc client") return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip) def mount(self, dev_name, mount_path='/mnt'): cmd_mount = 'sudo mount /dev/%s %s' % (dev_name, mount_path) self.exec_command(cmd_mount) def umount(self, mount_path='/mnt'): self.exec_command('sudo umount %s' % mount_path) def make_fs(self, dev_name, fs='ext4'): cmd_mkfs = 'sudo /usr/sbin/mke2fs -t %s /dev/%s' % (fs, dev_name) try: self.exec_command(cmd_mkfs) 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)) raise tempest-17.2.0/tempest/common/utils/linux/__init__.py0000666000175100017510000000000013207044712022657 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/common/custom_matchers.py0000666000175100017510000003143613207044712022042 0ustar zuulzuul00000000000000# Copyright 2013 NTT 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. import re from testtools import helpers class ExistsAllResponseHeaders(object): """Specific matcher to check the existence of Swift's response headers This matcher checks the existence of common headers for each HTTP method or the target, which means account, container or object. When checking the existence of 'specific' headers such as X-Account-Meta-* or X-Object-Manifest for example, those headers must be checked in each test code. """ def __init__(self, target, method, policies=None): """Initialization of ExistsAllResponseHeaders param: target Account/Container/Object param: method PUT/GET/HEAD/DELETE/COPY/POST """ self.target = target self.method = method self.policies = policies or [] def _content_length_required(self, resp): # Verify whether given HTTP response must contain content-length. # Take into account the exceptions defined in RFC 7230. if resp.status in range(100, 200) or resp.status == 204: return False return True def match(self, actual): """Check headers param: actual HTTP response object containing headers and status """ # Check common headers for all HTTP methods. # # Please note that for 1xx and 204 responses Content-Length presence # is not checked intensionally. According to RFC 7230 a server MUST # NOT send the header in such responses. Thus, clients should not # depend on this header. However, the standard does not require them # to validate the server's behavior. We leverage that to not refuse # any implementation violating it like Swift [1] or some versions of # Ceph RadosGW [2]. # [1] https://bugs.launchpad.net/swift/+bug/1537811 # [2] http://tracker.ceph.com/issues/13582 if ('content-length' not in actual and self._content_length_required(actual)): return NonExistentHeader('content-length') if 'content-type' not in actual: return NonExistentHeader('content-type') if 'x-trans-id' not in actual: return NonExistentHeader('x-trans-id') if 'date' not in actual: return NonExistentHeader('date') # Check headers for a specific method or target if self.method == 'GET' or self.method == 'HEAD': if 'x-timestamp' not in actual: return NonExistentHeader('x-timestamp') if 'accept-ranges' not in actual: return NonExistentHeader('accept-ranges') if self.target == 'Account': if 'x-account-bytes-used' not in actual: return NonExistentHeader('x-account-bytes-used') if 'x-account-container-count' not in actual: return NonExistentHeader('x-account-container-count') if 'x-account-object-count' not in actual: return NonExistentHeader('x-account-object-count') if int(actual['x-account-container-count']) > 0: acct_header = "x-account-storage-policy-" matched_policy_count = 0 # Loop through the policies and look for account # usage data. There should be at least 1 set for policy in self.policies: front_header = acct_header + policy['name'].lower() usage_policies = [ front_header + '-bytes-used', front_header + '-object-count', front_header + '-container-count' ] # There should be 3 usage values for a give storage # policy in an account bytes, object count, and # container count policy_hdrs = sum(1 for use_hdr in usage_policies if use_hdr in actual) # If there are less than 3 headers here then 1 is # missing, let's figure out which one and report if policy_hdrs == 3: matched_policy_count = matched_policy_count + 1 else: if policy_hdrs > 0 and policy_hdrs < 3: for use_hdr in usage_policies: if use_hdr not in actual: return NonExistentHeader(use_hdr) # Only flag an error if actual policies have been read and # no usage has been found if self.policies and matched_policy_count == 0: return GenericError("No storage policy usage headers") elif self.target == 'Container': if 'x-container-bytes-used' not in actual: return NonExistentHeader('x-container-bytes-used') if 'x-container-object-count' not in actual: return NonExistentHeader('x-container-object-count') if 'x-storage-policy' not in actual: return NonExistentHeader('x-storage-policy') else: policy_name = actual['x-storage-policy'] # loop through the policies and ensure that # the value in the container header matches # one of the storage policies for policy in self.policies: if policy['name'] == policy_name: break else: # Ensure that there are actual policies stored if self.policies: return InvalidHeaderValue('x-storage-policy', policy_name) elif self.target == 'Object': if 'etag' not in actual: return NonExistentHeader('etag') if 'last-modified' not in actual: return NonExistentHeader('last-modified') elif self.method == 'PUT': if self.target == 'Object': if 'etag' not in actual: return NonExistentHeader('etag') if 'last-modified' not in actual: return NonExistentHeader('last-modified') elif self.method == 'COPY': if self.target == 'Object': if 'etag' not in actual: return NonExistentHeader('etag') if 'last-modified' not in actual: return NonExistentHeader('last-modified') if 'x-copied-from' not in actual: return NonExistentHeader('x-copied-from') if 'x-copied-from-last-modified' not in actual: return NonExistentHeader('x-copied-from-last-modified') return None class GenericError(object): """Informs an error message of a generic error during header evaluation""" def __init__(self, body): self.body = body def describe(self): return "%s" % self.body def get_details(self): return {} class NonExistentHeader(object): """Informs an error message in the case of missing a certain header""" def __init__(self, header): self.header = header def describe(self): return "%s header does not exist" % self.header def get_details(self): return {} class InvalidHeaderValue(object): """Informs an error message when a header contains a bad value""" def __init__(self, header, value): self.header = header self.value = value def describe(self): return "InvalidValue (%s, %s)" % (self.header, self.value) def get_details(self): return {} class AreAllWellFormatted(object): """Specific matcher to check the correctness of formats of values This matcher checks the format of values of response headers. When checking the format of values of 'specific' headers such as X-Account-Meta-* or X-Object-Manifest for example, those values must be checked in each test code. """ def match(self, actual): for key, value in actual.items(): if key in ('content-length', 'x-account-bytes-used', 'x-account-container-count', 'x-account-object-count', 'x-container-bytes-used', 'x-container-object-count')\ and not value.isdigit(): return InvalidFormat(key, value) elif key in ('content-type', 'date', 'last-modified', 'x-copied-from-last-modified') and not value: return InvalidFormat(key, value) elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value): return InvalidFormat(key, value) elif key == 'x-copied-from' and not re.match("\S+/\S+", value): return InvalidFormat(key, value) elif key == 'x-trans-id' and \ not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value): return InvalidFormat(key, value) elif key == 'accept-ranges' and not value == 'bytes': return InvalidFormat(key, value) elif key == 'etag' and not value.isalnum(): return InvalidFormat(key, value) elif key == 'transfer-encoding' and not value == 'chunked': return InvalidFormat(key, value) return None class InvalidFormat(object): """Informs an error message if a format of a certain header is invalid""" def __init__(self, key, value): self.key = key self.value = value def describe(self): return "InvalidFormat (%s, %s)" % (self.key, self.value) def get_details(self): return {} class MatchesDictExceptForKeys(object): """Matches two dictionaries. Verifies all items are equals except for those identified by a list of keys """ def __init__(self, expected, excluded_keys=None): self.expected = expected self.excluded_keys = excluded_keys if excluded_keys is not None else [] def match(self, actual): filtered_expected = helpers.dict_subtract(self.expected, self.excluded_keys) filtered_actual = helpers.dict_subtract(actual, self.excluded_keys) if filtered_actual != filtered_expected: return DictMismatch(filtered_expected, filtered_actual) class DictMismatch(object): """Mismatch between two dicts describes deltas""" def __init__(self, expected, actual): self.expected = expected self.actual = actual self.intersect = set(self.expected) & set(self.actual) self.symmetric_diff = set(self.expected) ^ set(self.actual) def _format_dict(self, dict_to_format): # Ensure the error string dict is printed in a set order # NOTE(mtreinish): needed to ensure a deterministic error msg for # testing. Otherwise the error message will be dependent on the # dict ordering. dict_string = "{" for key in sorted(dict_to_format): dict_string += "'%s': %s, " % (key, dict_to_format[key]) dict_string = dict_string[:-2] + '}' return dict_string def describe(self): msg = "" if self.symmetric_diff: only_expected = helpers.dict_subtract(self.expected, self.actual) only_actual = helpers.dict_subtract(self.actual, self.expected) if only_expected: msg += "Only in expected:\n %s\n" % self._format_dict( only_expected) if only_actual: msg += "Only in actual:\n %s\n" % self._format_dict( only_actual) diff_set = set(o for o in self.intersect if self.expected[o] != self.actual[o]) if diff_set: msg += "Differences:\n" for o in diff_set: msg += " %s: expected %s, actual %s\n" % ( o, self.expected[o], self.actual[o]) return msg def get_details(self): return {} tempest-17.2.0/tempest/common/__init__.py0000666000175100017510000000000013207044712020360 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/common/credentials_factory.py0000666000175100017510000003150513207044712022663 0ustar zuulzuul00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_concurrency import lockutils from tempest import clients from tempest import config from tempest.lib import auth from tempest.lib.common import dynamic_creds from tempest.lib.common import preprov_creds from tempest.lib import exceptions CONF = config.CONF """This module provides factories of credential and credential providers Credentials providers and clients are (going to be) part of tempest.lib, and so they may not hold any dependency to tempest configuration. Methods in this module collect the relevant configuration details and pass them to credentials providers and clients, so that test can have easy access to these features. Client managers with hard-coded configured credentials are also moved here, to avoid circular dependencies.""" # === Credential Providers # Subset of the parameters of credential providers that depend on configuration def _get_common_provider_params(identity_version): if identity_version == 'v3': identity_uri = CONF.identity.uri_v3 elif identity_version == 'v2': identity_uri = CONF.identity.uri else: raise exceptions.InvalidIdentityVersion( identity_version=identity_version) return { 'identity_version': identity_version, 'identity_uri': identity_uri, 'credentials_domain': CONF.auth.default_credentials_domain_name, 'admin_role': CONF.identity.admin_role } def get_dynamic_provider_params(identity_version, admin_creds=None): """Dynamic provider parameters setup from config This helper returns a dict of parameter that can be used to initialise a `DynamicCredentialProvider` according to tempest configuration. Parameters that are not configuration specific (name, network_resources) are not returned. :param identity_version: 'v2' or 'v3' :param admin_creds: An object of type `auth.Credentials`. If None, it is built from the configuration file as well. :return: A dict with the parameters """ _common_params = _get_common_provider_params(identity_version) admin_creds = admin_creds or get_configured_admin_credentials( fill_in=True, identity_version=identity_version) if identity_version == 'v3': endpoint_type = CONF.identity.v3_endpoint_type elif identity_version == 'v2': endpoint_type = CONF.identity.v2_admin_endpoint_type return dict(_common_params, **dict([ ('admin_creds', admin_creds), ('identity_admin_domain_scope', CONF.identity.admin_domain_scope), ('identity_admin_role', CONF.identity.admin_role), ('extra_roles', CONF.auth.tempest_roles), ('neutron_available', CONF.service_available.neutron), ('project_network_cidr', CONF.network.project_network_cidr), ('project_network_mask_bits', CONF.network.project_network_mask_bits), ('public_network_id', CONF.network.public_network_id), ('create_networks', (CONF.auth.create_isolated_networks and not CONF.network.shared_physical_network)), ('resource_prefix', CONF.resources_prefix), ('identity_admin_endpoint_type', endpoint_type) ])) def get_preprov_provider_params(identity_version): """Pre-provisioned provider parameters setup from config This helper returns a dict of parameter that can be used to initialise a `PreProvisionedCredentialProvider` according to tempest configuration. Parameters that are not configuration specific (name) are not returned. :param identity_version: 'v2' or 'v3' :return: A dict with the parameters """ _common_params = _get_common_provider_params(identity_version) reseller_admin_role = CONF.object_storage.reseller_admin_role return dict(_common_params, **dict([ ('accounts_lock_dir', lockutils.get_lock_path(CONF)), ('test_accounts_file', CONF.auth.test_accounts_file), ('object_storage_operator_role', CONF.object_storage.operator_role), ('object_storage_reseller_admin_role', reseller_admin_role) ])) def get_credentials_provider(name, network_resources=None, force_tenant_isolation=False, identity_version=None): """Return the right implementation of CredentialProvider based on config This helper returns the right implementation of CredentialProvider based on config and on the value of force_tenant_isolation. :param name: When provided, it makes it possible to associate credential artifacts back to the owner (test class). :param network_resources: Dictionary of network resources to be allocated for each test account. Only valid for the dynamic credentials provider. :param force_tenant_isolation: Always return a `DynamicCredentialProvider`, regardless of the configuration. :param identity_version: Use the specified identity API version, regardless of the configuration. Valid values are 'v2', 'v3'. """ # If a test requires a new account to work, it can have it via forcing # dynamic credentials. A new account will be produced only for that test. # In case admin credentials are not available for the account creation, # the test should be skipped else it would fail. identity_version = identity_version or CONF.identity.auth_version if CONF.auth.use_dynamic_credentials or force_tenant_isolation: return dynamic_creds.DynamicCredentialProvider( name=name, network_resources=network_resources, **get_dynamic_provider_params(identity_version)) else: if CONF.auth.test_accounts_file: # Most params are not relevant for pre-created accounts return preprov_creds.PreProvisionedCredentialProvider( name=name, **get_preprov_provider_params(identity_version)) else: raise exceptions.InvalidConfiguration( 'A valid credential provider is needed') def is_admin_available(identity_version): """Helper to check for admin credentials Helper function to check if a set of admin credentials is available so we can do a single call from skip_checks. This helper depends on identity_version as there may be admin credentials available for v2 but not for v3. :param identity_version: 'v2' or 'v3' """ is_admin = True # If dynamic credentials is enabled admin will be available if CONF.auth.use_dynamic_credentials: return is_admin # Check whether test accounts file has the admin specified or not elif CONF.auth.test_accounts_file: check_accounts = preprov_creds.PreProvisionedCredentialProvider( name='check_admin', **get_preprov_provider_params(identity_version)) if not check_accounts.admin_available(): is_admin = False else: try: get_configured_admin_credentials(fill_in=False, identity_version=identity_version) except exceptions.InvalidConfiguration: is_admin = False return is_admin def is_alt_available(identity_version): """Helper to check for alt credentials Helper function to check if a second set of credentials is available (aka alt credentials) so we can do a single call from skip_checks. This helper depends on identity_version as there may be alt credentials available for v2 but not for v3. :param identity_version: 'v2' or 'v3' """ # If dynamic credentials is enabled alt will be available if CONF.auth.use_dynamic_credentials: return True # Check whether test accounts file has the admin specified or not if CONF.auth.test_accounts_file: check_accounts = preprov_creds.PreProvisionedCredentialProvider( name='check_alt', **get_preprov_provider_params(identity_version)) else: raise exceptions.InvalidConfiguration( 'A valid credential provider is needed') try: if not check_accounts.is_multi_user(): return False else: return True except exceptions.InvalidConfiguration: return False # === Credentials # Type of credentials available from configuration CREDENTIAL_TYPES = { 'identity_admin': ('auth', 'admin'), 'user': ('identity', None), 'alt_user': ('identity', 'alt') } def get_configured_admin_credentials(fill_in=True, identity_version=None): """Get admin credentials from the config file Read credentials from configuration, builds a Credentials object based on the specified or configured version :param fill_in: If True, a request to the Token API is submitted, and the credential object is filled in with all names and IDs from the token API response. :param identity_version: The identity version to talk to and the type of credentials object to be created. 'v2' or 'v3'. :returns: An object of a sub-type of `auth.Credentials` """ identity_version = identity_version or CONF.identity.auth_version if identity_version not in ('v2', 'v3'): raise exceptions.InvalidConfiguration( 'Unsupported auth version: %s' % identity_version) conf_attributes = ['username', 'password', 'project_name'] if identity_version == 'v3': conf_attributes.append('domain_name') # Read the parts of credentials from config params = config.service_client_config() for attr in conf_attributes: params[attr] = getattr(CONF.auth, 'admin_' + attr) # Build and validate credentials. We are reading configured credentials, # so validate them even if fill_in is False credentials = get_credentials(fill_in=fill_in, identity_version=identity_version, **params) if not fill_in: if not credentials.is_valid(): msg = ("The admin credentials are incorrectly set in the config " "file for identity version %s. Double check that all " "required values are assigned.") raise exceptions.InvalidConfiguration(msg % identity_version) return credentials def get_credentials(fill_in=True, identity_version=None, **kwargs): """Get credentials from dict based on config Wrapper around auth.get_credentials to use the configured identity version if none is specified. :param fill_in: If True, a request to the Token API is submitted, and the credential object is filled in with all names and IDs from the token API response. :param identity_version: The identity version to talk to and the type of credentials object to be created. 'v2' or 'v3'. :param kwargs: Attributes to be used to build the Credentials object. :returns: An object of a sub-type of `auth.Credentials` """ params = dict(config.service_client_config(), **kwargs) identity_version = identity_version or CONF.identity.auth_version # In case of "v3" add the domain from config if not specified # To honour the "default_credentials_domain_name", if not domain # field is specified at all, add it the credential dict. if identity_version == 'v3': domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES if 'domain' in x) if not domain_fields.intersection(kwargs.keys()): domain_name = CONF.auth.default_credentials_domain_name # NOTE(andreaf) Setting domain_name implicitly sets user and # project domain names, if they are None params['domain_name'] = domain_name auth_url = CONF.identity.uri_v3 else: auth_url = CONF.identity.uri return auth.get_credentials(auth_url, fill_in=fill_in, identity_version=identity_version, **params) # === Credential / client managers class AdminManager(clients.Manager): """Manager that uses admin credentials for its managed client objects""" def __init__(self): super(AdminManager, self).__init__( credentials=get_configured_admin_credentials()) tempest-17.2.0/tempest/common/waiters.py0000666000175100017510000003031713207044712020315 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import time from oslo_log import log as logging from tempest.common import image as common_image from tempest import config from tempest import exceptions from tempest.lib.common.utils import test_utils from tempest.lib import exceptions as lib_exc from tempest.lib.services.image.v1 import images_client as images_v1_client CONF = config.CONF LOG = logging.getLogger(__name__) def _get_task_state(body): return body.get('OS-EXT-STS:task_state', None) # NOTE(afazekas): This function needs to know a token and a subject. def wait_for_server_status(client, server_id, status, ready_wait=True, extra_timeout=0, raise_on_error=True): """Waits for a server to reach a given status.""" # NOTE(afazekas): UNKNOWN status possible on ERROR # or in a very early stage. body = client.show_server(server_id)['server'] old_status = server_status = body['status'] old_task_state = task_state = _get_task_state(body) start_time = int(time.time()) timeout = client.build_timeout + extra_timeout while True: # NOTE(afazekas): Now the BUILD status only reached # between the UNKNOWN->ACTIVE transition. # TODO(afazekas): enumerate and validate the stable status set if status == 'BUILD' and server_status != 'UNKNOWN': return if server_status == status: if ready_wait: if status == 'BUILD': return # NOTE(afazekas): The instance is in "ready for action state" # when no task in progress if task_state is None: # without state api extension 3 sec usually enough time.sleep(CONF.compute.ready_wait) return else: return time.sleep(client.build_interval) body = client.show_server(server_id)['server'] server_status = body['status'] task_state = _get_task_state(body) if (server_status != old_status) or (task_state != old_task_state): LOG.info('State transition "%s" ==> "%s" after %d second wait', '/'.join((old_status, str(old_task_state))), '/'.join((server_status, str(task_state))), time.time() - start_time) if (server_status == 'ERROR') and raise_on_error: if 'fault' in body: raise exceptions.BuildErrorException(body['fault'], server_id=server_id) else: raise exceptions.BuildErrorException(server_id=server_id) timed_out = int(time.time()) - start_time >= timeout if timed_out: expected_task_state = 'None' if ready_wait else 'n/a' message = ('Server %(server_id)s failed to reach %(status)s ' 'status and task state "%(expected_task_state)s" ' 'within the required time (%(timeout)s s).' % {'server_id': server_id, 'status': status, 'expected_task_state': expected_task_state, 'timeout': timeout}) message += ' Current status: %s.' % server_status message += ' Current task state: %s.' % task_state caller = test_utils.find_test_caller() if caller: message = '(%s) %s' % (caller, message) raise lib_exc.TimeoutException(message) old_status = server_status old_task_state = task_state def wait_for_server_termination(client, server_id, ignore_error=False): """Waits for server to reach termination.""" try: body = client.show_server(server_id)['server'] except lib_exc.NotFound: return old_status = server_status = body['status'] old_task_state = task_state = _get_task_state(body) start_time = int(time.time()) while True: time.sleep(client.build_interval) try: body = client.show_server(server_id)['server'] except lib_exc.NotFound: return server_status = body['status'] task_state = _get_task_state(body) if (server_status != old_status) or (task_state != old_task_state): LOG.info('State transition "%s" ==> "%s" after %d second wait', '/'.join((old_status, str(old_task_state))), '/'.join((server_status, str(task_state))), time.time() - start_time) if server_status == 'ERROR' and not ignore_error: raise lib_exc.DeleteErrorException(resource_id=server_id) if int(time.time()) - start_time >= client.build_timeout: raise lib_exc.TimeoutException old_status = server_status old_task_state = task_state def wait_for_image_status(client, image_id, status): """Waits for an image to reach a given status. The client should have a show_image(image_id) method to get the image. The client should also have build_interval and build_timeout attributes. """ if isinstance(client, images_v1_client.ImagesClient): # The 'check_image' method is used here because the show_image method # returns image details plus the image itself which is very expensive. # The 'check_image' method returns just image details. def _show_image_v1(image_id): resp = client.check_image(image_id) return common_image.get_image_meta_from_headers(resp) show_image = _show_image_v1 else: show_image = client.show_image current_status = 'An unknown status' start = int(time.time()) while int(time.time()) - start < client.build_timeout: image = show_image(image_id) # Compute image client returns response wrapped in 'image' element # which is not the case with Glance image client. if 'image' in image: image = image['image'] current_status = image['status'] if current_status == status: return if current_status.lower() == 'killed': raise exceptions.ImageKilledException(image_id=image_id, status=status) if current_status.lower() == 'error': raise exceptions.AddImageException(image_id=image_id) time.sleep(client.build_interval) message = ('Image %(image_id)s failed to reach %(status)s state ' '(current state %(current_status)s) within the required ' 'time (%(timeout)s s).' % {'image_id': image_id, 'status': status, 'current_status': current_status, 'timeout': client.build_timeout}) caller = test_utils.find_test_caller() if caller: message = '(%s) %s' % (caller, message) raise lib_exc.TimeoutException(message) def wait_for_volume_resource_status(client, resource_id, statuses): """Waits for a volume resource to reach any of the specified statuses. This function is a common function for volume, snapshot and backup resources. The function extracts the name of the desired resource from the client class name of the resource. """ if not isinstance(statuses, list): statuses = [statuses] resource_name = re.findall( r'(volume|group-snapshot|snapshot|backup|group)', client.resource_type)[-1].replace('-', '_') show_resource = getattr(client, 'show_' + resource_name) resource_status = show_resource(resource_id)[resource_name]['status'] start = int(time.time()) while resource_status not in statuses: time.sleep(client.build_interval) resource_status = show_resource(resource_id)[ '{}'.format(resource_name)]['status'] if resource_status == 'error' and resource_status not in statuses: raise exceptions.VolumeResourceBuildErrorException( resource_name=resource_name, resource_id=resource_id) if resource_name == 'volume' and resource_status == 'error_restoring': raise exceptions.VolumeRestoreErrorException(volume_id=resource_id) if int(time.time()) - start >= client.build_timeout: message = ('%s %s failed to reach %s status (current %s) ' 'within the required time (%s s).' % (resource_name, resource_id, statuses, resource_status, client.build_timeout)) raise lib_exc.TimeoutException(message) LOG.info('%s %s reached %s after waiting for %f seconds', resource_name, resource_id, statuses, time.time() - start) def wait_for_volume_retype(client, volume_id, new_volume_type): """Waits for a Volume to have a new volume type.""" body = client.show_volume(volume_id)['volume'] current_volume_type = body['volume_type'] start = int(time.time()) while current_volume_type != new_volume_type: time.sleep(client.build_interval) body = client.show_volume(volume_id)['volume'] current_volume_type = body['volume_type'] if int(time.time()) - start >= client.build_timeout: message = ('Volume %s failed to reach %s volume type (current %s) ' 'within the required time (%s s).' % (volume_id, new_volume_type, current_volume_type, client.build_timeout)) raise lib_exc.TimeoutException(message) def wait_for_qos_operations(client, qos_id, operation, args=None): """Waits for a qos operations to be completed. NOTE : operation value is required for wait_for_qos_operations() operation = 'qos-key' / 'disassociate' / 'disassociate-all' args = keys[] when operation = 'qos-key' args = volume-type-id disassociated when operation = 'disassociate' args = None when operation = 'disassociate-all' """ start_time = int(time.time()) while True: if operation == 'qos-key-unset': body = client.show_qos(qos_id)['qos_specs'] if not any(key in body['specs'] for key in args): return elif operation == 'disassociate': body = client.show_association_qos(qos_id)['qos_associations'] if not any(args in body[i]['id'] for i in range(0, len(body))): return elif operation == 'disassociate-all': body = client.show_association_qos(qos_id)['qos_associations'] if not body: return else: msg = (" operation value is either not defined or incorrect.") raise lib_exc.UnprocessableEntity(msg) if int(time.time()) - start_time >= client.build_timeout: raise lib_exc.TimeoutException time.sleep(client.build_interval) def wait_for_interface_status(client, server_id, port_id, status): """Waits for an interface to reach a given status.""" body = (client.show_interface(server_id, port_id) ['interfaceAttachment']) interface_status = body['port_state'] start = int(time.time()) while(interface_status != status): time.sleep(client.build_interval) body = (client.show_interface(server_id, port_id) ['interfaceAttachment']) interface_status = body['port_state'] timed_out = int(time.time()) - start >= client.build_timeout if interface_status != status and timed_out: message = ('Interface %s failed to reach %s status ' '(current %s) within the required time (%s s).' % (port_id, status, interface_status, client.build_timeout)) raise lib_exc.TimeoutException(message) return body tempest-17.2.0/tempest/common/image.py0000666000175100017510000000407413207044712017722 0ustar zuulzuul00000000000000# 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 copy def get_image_meta_from_headers(resp): meta = {'properties': {}} for key in resp.response: value = resp.response[key] if key.startswith('x-image-meta-property-'): _key = key[22:] meta['properties'][_key] = value elif key.startswith('x-image-meta-'): _key = key[13:] meta[_key] = value for key in ['is_public', 'protected', 'deleted']: if key in meta: meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes', '1') for key in ['size', 'min_ram', 'min_disk']: if key in meta: try: meta[key] = int(meta[key]) except ValueError: pass return meta def image_meta_to_headers(**metadata): headers = {} fields_copy = copy.deepcopy(metadata) copy_from = fields_copy.pop('copy_from', None) purge = fields_copy.pop('purge_props', None) if purge is not None: headers['x-glance-registry-purge-props'] = purge if copy_from is not None: headers['x-glance-api-copy-from'] = copy_from for key, value in fields_copy.pop('properties', {}).items(): headers['x-image-meta-property-%s' % key] = str(value) for key, value in fields_copy.pop('api', {}).items(): headers['x-glance-api-property-%s' % key] = str(value) for key, value in fields_copy.items(): headers['x-image-meta-%s' % key] = str(value) return headers tempest-17.2.0/tempest/common/identity.py0000666000175100017510000000650513207044712020472 0ustar zuulzuul00000000000000# 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 import config from tempest.lib.common import cred_client from tempest.lib import exceptions as lib_exc CONF = config.CONF def get_project_by_name(client, project_name): projects = client.list_projects({'name': project_name})['projects'] for project in projects: if project['name'] == project_name: return project raise lib_exc.NotFound('No such project(%s) in %s' % (project_name, projects)) def get_tenant_by_name(client, tenant_name): tenants = client.list_tenants()['tenants'] for tenant in tenants: if tenant['name'] == tenant_name: return tenant raise lib_exc.NotFound('No such tenant(%s) in %s' % (tenant_name, tenants)) def get_user_by_username(client, tenant_id, username): users = client.list_tenant_users(tenant_id)['users'] for user in users: if user['name'] == username: return user raise lib_exc.NotFound('No such user(%s) in %s' % (username, users)) def get_user_by_project(users_client, roles_client, project_id, username): users = users_client.list_users(**{'name': username})['users'] users_in_project = roles_client.list_role_assignments( **{'scope.project.id': project_id})['role_assignments'] for user in users: if user['name'] == username: for u in users_in_project: if u['user']['id'] == user['id']: return user raise lib_exc.NotFound('No such user(%s) in %s' % (username, users)) def identity_utils(clients): """A client that abstracts v2 and v3 identity operations. This can be used for creating and tearing down projects in tests. It should not be used for testing identity features. :param clients: a client manager. :return """ if CONF.identity.auth_version == 'v2': client = clients.identity_client users_client = clients.users_client project_client = clients.tenants_client roles_client = clients.roles_client domains_client = None else: client = clients.identity_v3_client users_client = clients.users_v3_client project_client = clients.projects_client roles_client = clients.roles_v3_client domains_client = clients.domains_client try: domain = client.auth_provider.credentials.project_domain_name except AttributeError: domain = CONF.auth.default_credentials_domain_name return cred_client.get_creds_client(client, project_client, users_client, roles_client, domains_client, project_domain_name=domain) tempest-17.2.0/tempest/common/tempest_fixtures.py0000666000175100017510000000144613207044712022252 0ustar zuulzuul00000000000000# 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_concurrency.fixture import lockutils class LockFixture(lockutils.LockFixture): def __init__(self, name): super(LockFixture, self).__init__(name, 'tempest-') tempest-17.2.0/tempest/__init__.py0000666000175100017510000000000013207044712017070 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/exceptions.py0000666000175100017510000000365513207044712017535 0ustar zuulzuul00000000000000# 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 tempest.lib import exceptions class BuildErrorException(exceptions.TempestException): message = "Server %(server_id)s failed to build and is in ERROR status" class SnapshotNotFoundException(exceptions.TempestException): message = "Server snapshot image %(image_id)s not found." class ImageKilledException(exceptions.TempestException): message = "Image %(image_id)s 'killed' while waiting for '%(status)s'" class AddImageException(exceptions.TempestException): message = "Image %(image_id)s failed to become ACTIVE in the allotted time" class VolumeResourceBuildErrorException(exceptions.TempestException): message = ("%(resource_name)s %(resource_id)s failed to build and is in " "ERROR status") class VolumeRestoreErrorException(exceptions.TempestException): message = "Volume %(volume_id)s failed to restore and is in ERROR status" class StackBuildErrorException(exceptions.TempestException): message = ("Stack %(stack_identifier)s is in %(stack_status)s status " "due to '%(stack_status_reason)s'") class ServerUnreachable(exceptions.TempestException): message = ("Server %(server_id)s is not reachable via " "the configured network") class InvalidServiceTag(exceptions.TempestException): message = "Invalid service tag" tempest-17.2.0/tempest/services/0000775000175100017510000000000013207045130016605 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/services/__init__.py0000666000175100017510000000000013207044712020713 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/services/orchestration/0000775000175100017510000000000013207045130021471 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/services/orchestration/json/0000775000175100017510000000000013207045130022442 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/services/orchestration/json/__init__.py0000666000175100017510000000000013207044712024550 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/services/orchestration/json/orchestration_client.py0000666000175100017510000004071113207044712027250 0ustar zuulzuul00000000000000# 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 re import time from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest import exceptions from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc class OrchestrationClient(rest_client.RestClient): def list_stacks(self, params=None): """Lists all stacks for a user.""" uri = 'stacks' if params: uri += '?%s' % urllib.urlencode(params) resp, body = self.get(uri) self.expected_success(200, resp.status) body = json.loads(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, environment=None, files=None): if parameters is None: parameters = {} headers, body = self._prepare_update_create( name, disable_rollback, parameters, timeout_mins, template, template_url, environment, files) uri = 'stacks' resp, body = self.post(uri, headers=headers, body=body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_stack(self, stack_identifier, name, disable_rollback=True, parameters=None, timeout_mins=60, template=None, template_url=None, environment=None, files=None): if parameters is None: parameters = {} headers, body = self._prepare_update_create( name, disable_rollback, parameters, timeout_mins, template, template_url, environment) uri = "stacks/%s" % stack_identifier resp, body = self.put(uri, headers=headers, body=body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def _prepare_update_create(self, name, disable_rollback=True, parameters=None, timeout_mins=60, template=None, template_url=None, environment=None, files=None): if parameters is None: parameters = {} post_body = { "stack_name": name, "disable_rollback": disable_rollback, "parameters": parameters, "timeout_mins": timeout_mins, "template": "HeatTemplateFormatVersion: '2012-12-12'\n", "environment": environment, "files": files } if template: post_body['template'] = template if template_url: post_body['template_url'] = template_url body = json.dumps(post_body) # Password must be provided on stack create so that heat # can perform future operations on behalf of the user headers = self.get_headers() headers['X-Auth-Key'] = self.password headers['X-Auth-User'] = self.user return headers, body def show_stack(self, stack_identifier): """Returns the details of a single stack.""" url = "stacks/%s" % stack_identifier resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def suspend_stack(self, stack_identifier): """Suspend a stack.""" url = 'stacks/%s/actions' % stack_identifier body = {'suspend': None} resp, body = self.post(url, json.dumps(body)) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp) def resume_stack(self, stack_identifier): """Resume a stack.""" url = 'stacks/%s/actions' % stack_identifier body = {'resume': None} resp, body = self.post(url, json.dumps(body)) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp) def list_resources(self, stack_identifier): """Returns the details of a single resource.""" url = "stacks/%s/resources" % stack_identifier resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_resource(self, stack_identifier, resource_name): """Returns the details of a single resource.""" url = "stacks/%s/resources/%s" % (stack_identifier, resource_name) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(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 rest_client.ResponseBody(resp) def wait_for_stack_status(self, stack_identifier, status, failure_pattern='^.*_FAILED$'): """Waits for a Stack to reach a given status.""" start = int(time.time()) fail_regexp = re.compile(failure_pattern) while True: try: body = self.show_stack(stack_identifier)['stack'] except lib_exc.NotFound: if status == 'DELETE_COMPLETE': return stack_name = body['stack_name'] stack_status = body['stack_status'] if stack_status == status: return body if fail_regexp.search(stack_status): raise exceptions.StackBuildErrorException( stack_identifier=stack_identifier, stack_status=stack_status, stack_status_reason=body['stack_status_reason']) if int(time.time()) - start >= self.build_timeout: message = ('Stack %s failed to reach %s status (current: %s) ' 'within the required time (%s s).' % (stack_name, status, stack_status, self.build_timeout)) raise lib_exc.TimeoutException(message) time.sleep(self.build_interval) def show_resource_metadata(self, stack_identifier, resource_name): """Returns the resource's metadata.""" url = ('stacks/{stack_identifier}/resources/{resource_name}' '/metadata'.format(**locals())) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_events(self, stack_identifier): """Returns list of all events for a stack.""" url = 'stacks/{stack_identifier}/events'.format(**locals()) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(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.""" url = ('stacks/{stack_identifier}/resources/{resource_name}' '/events'.format(**locals())) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(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.""" url = ('stacks/{stack_identifier}/resources/{resource_name}/events' '/{event_id}'.format(**locals())) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_template(self, stack_identifier): """Returns the template for the stack.""" url = ('stacks/{stack_identifier}/template'.format(**locals())) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def _validate_template(self, post_body): """Returns the validation request result.""" post_body = json.dumps(post_body) resp, body = self.post('validate', post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def validate_template(self, template, parameters=None): """Returns the validation result for a template with parameters.""" if parameters is None: parameters = {} post_body = { 'template': template, 'parameters': parameters, } return self._validate_template(post_body) def validate_template_url(self, template_url, parameters=None): """Returns the validation result for a template with parameters.""" if parameters is None: parameters = {} post_body = { 'template_url': template_url, 'parameters': parameters, } return self._validate_template(post_body) def list_resource_types(self): """List resource types.""" resp, body = self.get('resource_types') self.expected_success(200, resp.status) body = json.loads(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 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 rest_client.ResponseBody(resp, json.loads(body)) def create_software_config(self, name=None, config=None, group=None, inputs=None, outputs=None, options=None): headers, body = self._prep_software_config_create( name, config, group, inputs, outputs, options) url = 'software_configs' resp, body = self.post(url, headers=headers, body=body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_software_config(self, conf_id): """Returns a software configuration resource.""" url = 'software_configs/%s' % str(conf_id) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(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 rest_client.ResponseBody(resp) def create_software_deploy(self, server_id=None, config_id=None, action=None, status=None, input_values=None, output_values=None, status_reason=None, signal_transport=None): """Creates or updates a software deployment.""" headers, body = self._prep_software_deploy_update( None, server_id, config_id, action, status, input_values, output_values, status_reason, signal_transport) url = 'software_deployments' resp, body = self.post(url, headers=headers, body=body) self.expected_success(200, resp.status) body = json.loads(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, input_values=None, output_values=None, status_reason=None, signal_transport=None): """Creates or updates a software deployment.""" headers, body = self._prep_software_deploy_update( deploy_id, server_id, config_id, action, status, input_values, output_values, status_reason, signal_transport) url = 'software_deployments/%s' % str(deploy_id) resp, body = self.put(url, headers=headers, body=body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_software_deployments(self): """Returns a list of all deployments.""" url = 'software_deployments' resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_software_deployment(self, deploy_id): """Returns a specific software deployment.""" url = 'software_deployments/%s' % str(deploy_id) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_software_deployment_metadata(self, server_id): """Return a config metadata for a specific server.""" url = 'software_deployments/metadata/%s' % server_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(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 rest_client.ResponseBody(resp) def _prep_software_config_create(self, name=None, conf=None, group=None, inputs=None, outputs=None, options=None): """Prepares a software configuration body.""" post_body = {} if name is not None: post_body["name"] = name if conf is not None: post_body["config"] = conf if group is not None: post_body["group"] = group if inputs is not None: post_body["inputs"] = inputs if outputs is not None: post_body["outputs"] = outputs if options is not None: post_body["options"] = options body = json.dumps(post_body) headers = self.get_headers() return headers, body def _prep_software_deploy_update(self, deploy_id=None, server_id=None, config_id=None, action=None, status=None, input_values=None, output_values=None, status_reason=None, signal_transport=None): """Prepares a deployment create or update (if an id was given).""" post_body = {} if deploy_id is not None: post_body["id"] = deploy_id if server_id is not None: post_body["server_id"] = server_id if config_id is not None: post_body["config_id"] = config_id if action is not None: post_body["action"] = action if status is not None: post_body["status"] = status if input_values is not None: post_body["input_values"] = input_values if output_values is not None: post_body["output_values"] = output_values if status_reason is not None: post_body["status_reason"] = status_reason if signal_transport is not None: post_body["signal_transport"] = signal_transport body = json.dumps(post_body) headers = self.get_headers() return headers, body tempest-17.2.0/tempest/services/orchestration/__init__.py0000666000175100017510000000135613207044712023616 0ustar zuulzuul00000000000000# 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. from tempest.services.orchestration.json.orchestration_client import \ OrchestrationClient __all__ = ['OrchestrationClient'] tempest-17.2.0/tempest/scenario/0000775000175100017510000000000013207045130016565 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/scenario/test_shelve_instance.py0000666000175100017510000000675313207044712023372 0ustar zuulzuul00000000000000# Copyright 2014 Scality # 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.common import compute from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF class TestShelveInstance(manager.ScenarioTest): """This test shelves then unshelves a Nova instance The following is the scenario outline: * boot an instance and create a timestamp file in it * shelve the instance * unshelve the instance * check the existence of the timestamp file in the unshelved instance """ @classmethod def skip_checks(cls): super(TestShelveInstance, cls).skip_checks() if not CONF.compute_feature_enabled.shelve: raise cls.skipException("Shelve is not available.") def _shelve_then_unshelve_server(self, server): compute.shelve_server(self.servers_client, server['id'], force_shelve_offload=True) self.servers_client.unshelve_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') def _create_server_then_shelve_and_unshelve(self, boot_from_volume=False): keypair = self.create_keypair() security_group = self._create_security_group() security_groups = [{'name': security_group['name']}] server = self.create_server( key_name=keypair['name'], security_groups=security_groups, volume_backed=boot_from_volume) instance_ip = self.get_server_ip(server) timestamp = self.create_timestamp(instance_ip, private_key=keypair['private_key']) # Prevent bug #1257594 from coming back # Unshelve used to boot the instance with the original image, not # with the instance snapshot self._shelve_then_unshelve_server(server) timestamp2 = self.get_timestamp(instance_ip, private_key=keypair['private_key']) self.assertEqual(timestamp, timestamp2) @decorators.attr(type='slow') @decorators.idempotent_id('1164e700-0af0-4a4c-8792-35909a88743c') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @utils.services('compute', 'network', 'image') def test_shelve_instance(self): self._create_server_then_shelve_and_unshelve() @decorators.attr(type='slow') @decorators.idempotent_id('c1b6318c-b9da-490b-9c67-9339b627271f') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @utils.services('compute', 'volume', 'network', 'image') def test_shelve_volume_backed_instance(self): self._create_server_then_shelve_and_unshelve(boot_from_volume=True) tempest-17.2.0/tempest/scenario/manager.py0000666000175100017510000015542613207044712020575 0ustar zuulzuul00000000000000# 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 subprocess import netaddr from oslo_log import log from oslo_serialization import jsonutils as json from oslo_utils import netutils from tempest.common import compute from tempest.common import image as common_image from tempest.common.utils.linux import remote_client from tempest.common.utils import net_utils from tempest.common import waiters from tempest import config from tempest import exceptions from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import exceptions as lib_exc import tempest.test CONF = config.CONF LOG = log.getLogger(__name__) class ScenarioTest(tempest.test.BaseTestCase): """Base class for scenario tests. Uses tempest own clients. """ credentials = ['primary'] @classmethod def setup_clients(cls): super(ScenarioTest, cls).setup_clients() # Clients (in alphabetical order) cls.flavors_client = cls.os_primary.flavors_client cls.compute_floating_ips_client = ( cls.os_primary.compute_floating_ips_client) if CONF.service_available.glance: # Check if glance v1 is available to determine which client to use. if CONF.image_feature_enabled.api_v1: cls.image_client = cls.os_primary.image_client elif CONF.image_feature_enabled.api_v2: cls.image_client = cls.os_primary.image_client_v2 else: raise lib_exc.InvalidConfiguration( 'Either api_v1 or api_v2 must be True in ' '[image-feature-enabled].') # Compute image client cls.compute_images_client = cls.os_primary.compute_images_client cls.keypairs_client = cls.os_primary.keypairs_client # Nova security groups client cls.compute_security_groups_client = ( cls.os_primary.compute_security_groups_client) cls.compute_security_group_rules_client = ( cls.os_primary.compute_security_group_rules_client) cls.servers_client = cls.os_primary.servers_client cls.interface_client = cls.os_primary.interfaces_client # Neutron network client cls.networks_client = cls.os_primary.networks_client cls.ports_client = cls.os_primary.ports_client cls.routers_client = cls.os_primary.routers_client cls.subnets_client = cls.os_primary.subnets_client cls.floating_ips_client = cls.os_primary.floating_ips_client cls.security_groups_client = cls.os_primary.security_groups_client cls.security_group_rules_client = ( cls.os_primary.security_group_rules_client) # Use the latest available volume clients if CONF.service_available.cinder: cls.volumes_client = cls.os_primary.volumes_client_latest cls.snapshots_client = cls.os_primary.snapshots_client_latest # ## Test functions library # # The create_[resource] functions only return body and discard the # resp part which is not used in scenario tests def create_port(self, network_id, client=None, **kwargs): if not client: client = self.ports_client name = data_utils.rand_name(self.__class__.__name__) result = client.create_port( name=name, network_id=network_id, **kwargs) port = result['port'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, client.delete_port, port['id']) return port def create_keypair(self, client=None): if not client: client = self.keypairs_client name = data_utils.rand_name(self.__class__.__name__) # We don't need to create a keypair by pubkey in scenario body = client.create_keypair(name=name) self.addCleanup(client.delete_keypair, name) return body['keypair'] def create_server(self, name=None, image_id=None, flavor=None, validatable=False, wait_until='ACTIVE', clients=None, **kwargs): """Wrapper utility that returns a test server. This wrapper utility calls the common create test server and returns a test server. The purpose of this wrapper is to minimize the impact on the code of the tests already using this function. """ # NOTE(jlanoux): As a first step, ssh checks in the scenario # tests need to be run regardless of the run_validation and # validatable parameters and thus until the ssh validation job # becomes voting in CI. The test resources management and IP # association are taken care of in the scenario tests. # Therefore, the validatable parameter is set to false in all # those tests. In this way create_server just return a standard # server and the scenario tests always perform ssh checks. # Needed for the cross_tenant_traffic test: if clients is None: clients = self.os_primary if name is None: name = data_utils.rand_name(self.__class__.__name__ + "-server") vnic_type = CONF.network.port_vnic_type # If vnic_type is configured create port for # every network if vnic_type: ports = [] create_port_body = {'binding:vnic_type': vnic_type} if kwargs: # Convert security group names to security group ids # to pass to create_port if 'security_groups' in kwargs: security_groups = \ clients.security_groups_client.list_security_groups( ).get('security_groups') sec_dict = dict([(s['name'], s['id']) for s in security_groups]) sec_groups_names = [s['name'] for s in kwargs.pop( 'security_groups')] security_groups_ids = [sec_dict[s] for s in sec_groups_names] if security_groups_ids: create_port_body[ 'security_groups'] = security_groups_ids networks = kwargs.pop('networks', []) else: networks = [] # If there are no networks passed to us we look up # for the project's private networks and create a port. # The same behaviour as we would expect when passing # the call to the clients with no networks if not networks: networks = clients.networks_client.list_networks( **{'router:external': False, 'fields': 'id'})['networks'] # It's net['uuid'] if networks come from kwargs # and net['id'] if they come from # clients.networks_client.list_networks for net in networks: net_id = net.get('uuid', net.get('id')) if 'port' not in net: port = self.create_port(network_id=net_id, client=clients.ports_client, **create_port_body) ports.append({'port': port['id']}) else: ports.append({'port': net['port']}) if ports: kwargs['networks'] = ports self.ports = ports tenant_network = self.get_tenant_network() body, _ = compute.create_test_server( clients, tenant_network=tenant_network, wait_until=wait_until, name=name, flavor=flavor, image_id=image_id, **kwargs) self.addCleanup(waiters.wait_for_server_termination, clients.servers_client, body['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, clients.servers_client.delete_server, body['id']) server = clients.servers_client.show_server(body['id'])['server'] return server def create_volume(self, size=None, name=None, snapshot_id=None, imageRef=None, volume_type=None): if size is None: size = CONF.volume.volume_size if imageRef: image = self.compute_images_client.show_image(imageRef)['image'] min_disk = image.get('minDisk') size = max(size, min_disk) if name is None: name = data_utils.rand_name(self.__class__.__name__ + "-volume") kwargs = {'display_name': name, 'snapshot_id': snapshot_id, 'imageRef': imageRef, 'volume_type': volume_type, 'size': size} volume = self.volumes_client.create_volume(**kwargs)['volume'] self.addCleanup(self.volumes_client.wait_for_resource_deletion, volume['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.volumes_client.delete_volume, volume['id']) self.assertEqual(name, volume['name']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # The volume retrieved on creation has a non-up-to-date status. # Retrieval after it becomes active ensures correct details. volume = self.volumes_client.show_volume(volume['id'])['volume'] return volume def create_volume_snapshot(self, volume_id, name=None, description=None, metadata=None, force=False): name = name or data_utils.rand_name( self.__class__.__name__ + '-snapshot') snapshot = self.snapshots_client.create_snapshot( volume_id=volume_id, force=force, display_name=name, description=description, metadata=metadata)['snapshot'] self.addCleanup(self.snapshots_client.wait_for_resource_deletion, snapshot['id']) self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id']) waiters.wait_for_volume_resource_status(self.snapshots_client, snapshot['id'], 'available') return snapshot def create_volume_type(self, client=None, name=None, backend_name=None): if not client: client = self.os_admin.volume_types_v2_client if not name: class_name = self.__class__.__name__ name = data_utils.rand_name(class_name + '-volume-type') randomized_name = data_utils.rand_name('scenario-type-' + name) LOG.debug("Creating a volume type: %s on backend %s", randomized_name, backend_name) extra_specs = {} if backend_name: extra_specs = {"volume_backend_name": backend_name} volume_type = client.create_volume_type( name=randomized_name, extra_specs=extra_specs)['volume_type'] self.addCleanup(client.delete_volume_type, volume_type['id']) return volume_type def _create_loginable_secgroup_rule(self, secgroup_id=None): _client = self.compute_security_groups_client _client_rules = self.compute_security_group_rules_client if secgroup_id is None: sgs = _client.list_security_groups()['security_groups'] for sg in sgs: if sg['name'] == 'default': secgroup_id = sg['id'] # These rules are intended to permit inbound ssh and icmp # traffic from all sources, so no group_id is provided. # Setting a group_id would only permit traffic from ports # belonging to the same security group. rulesets = [ { # ssh 'ip_protocol': 'tcp', 'from_port': 22, 'to_port': 22, 'cidr': '0.0.0.0/0', }, { # ping 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'cidr': '0.0.0.0/0', } ] rules = list() for ruleset in rulesets: sg_rule = _client_rules.create_security_group_rule( parent_group_id=secgroup_id, **ruleset)['security_group_rule'] rules.append(sg_rule) return rules def _create_security_group(self): # Create security group sg_name = data_utils.rand_name(self.__class__.__name__) sg_desc = sg_name + " description" secgroup = self.compute_security_groups_client.create_security_group( name=sg_name, description=sg_desc)['security_group'] self.assertEqual(secgroup['name'], sg_name) self.assertEqual(secgroup['description'], sg_desc) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.compute_security_groups_client.delete_security_group, secgroup['id']) # Add rules to the security group self._create_loginable_secgroup_rule(secgroup['id']) return secgroup def get_remote_client(self, ip_address, username=None, private_key=None, server=None): """Get a SSH client to a remote server @param ip_address the server floating or fixed IP address to use for ssh validation @param username name of the Linux account on the remote server @param private_key the SSH private key to use @param server: server dict, used for debugging purposes @return a RemoteClient object """ if username is None: username = CONF.validation.image_ssh_user # Set this with 'keypair' or others to log in with keypair or # username/password. if CONF.validation.auth_method == 'keypair': password = None if private_key is None: private_key = self.keypair['private_key'] else: password = CONF.validation.image_ssh_password private_key = None linux_client = remote_client.RemoteClient( ip_address, username, pkey=private_key, password=password, server=server, servers_client=self.servers_client) linux_client.validate_authentication() return linux_client def _image_create(self, name, fmt, path, disk_format=None, properties=None): if properties is None: properties = {} name = data_utils.rand_name('%s-' % name) params = { 'name': name, 'container_format': fmt, 'disk_format': disk_format or fmt, } if CONF.image_feature_enabled.api_v1: params['is_public'] = 'False' params['properties'] = properties params = {'headers': common_image.image_meta_to_headers(**params)} else: params['visibility'] = 'private' # Additional properties are flattened out in the v2 API. params.update(properties) body = self.image_client.create_image(**params) image = body['image'] if 'image' in body else body self.addCleanup(self.image_client.delete_image, image['id']) self.assertEqual("queued", image['status']) with open(path, 'rb') as image_file: if CONF.image_feature_enabled.api_v1: self.image_client.update_image(image['id'], data=image_file) else: self.image_client.store_image_file(image['id'], image_file) return image['id'] def glance_image_create(self): img_path = CONF.scenario.img_dir + "/" + CONF.scenario.img_file aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file img_container_format = CONF.scenario.img_container_format img_disk_format = CONF.scenario.img_disk_format img_properties = CONF.scenario.img_properties LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, " "properties: %s, ami: %s, ari: %s, aki: %s", img_path, img_container_format, img_disk_format, img_properties, ami_img_path, ari_img_path, aki_img_path) try: image = self._image_create('scenario-img', img_container_format, img_path, disk_format=img_disk_format, properties=img_properties) except IOError: LOG.debug("A qcow2 image was not found. Try to get a uec image.") kernel = self._image_create('scenario-aki', 'aki', aki_img_path) ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path) properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk} image = self._image_create('scenario-ami', 'ami', path=ami_img_path, properties=properties) LOG.debug("image:%s", image) return image def _log_console_output(self, servers=None, client=None): if not CONF.compute_feature_enabled.console_output: LOG.debug('Console output not supported, cannot log') return client = client or self.servers_client if not servers: servers = client.list_servers() servers = servers['servers'] for server in servers: try: console_output = client.get_console_output( server['id'])['output'] LOG.debug('Console output for %s\nbody=\n%s', server['id'], console_output) except lib_exc.NotFound: LOG.debug("Server %s disappeared(deleted) while looking " "for the console log", server['id']) def _log_net_info(self, exc): # network debug is called as part of ssh init if not isinstance(exc, lib_exc.SSHTimeout): LOG.debug('Network information on a devstack host') def create_server_snapshot(self, server, name=None): # Glance client _image_client = self.image_client # Compute client _images_client = self.compute_images_client if name is None: name = data_utils.rand_name(self.__class__.__name__ + 'snapshot') LOG.debug("Creating a snapshot image for server: %s", server['name']) image = _images_client.create_image(server['id'], name=name) image_id = image.response['location'].split('images/')[1] waiters.wait_for_image_status(_image_client, image_id, 'active') self.addCleanup(_image_client.wait_for_resource_deletion, image_id) self.addCleanup(test_utils.call_and_ignore_notfound_exc, _image_client.delete_image, image_id) if CONF.image_feature_enabled.api_v1: # In glance v1 the additional properties are stored in the headers. resp = _image_client.check_image(image_id) snapshot_image = common_image.get_image_meta_from_headers(resp) image_props = snapshot_image.get('properties', {}) else: # In glance v2 the additional properties are flattened. snapshot_image = _image_client.show_image(image_id) image_props = snapshot_image bdm = image_props.get('block_device_mapping') if bdm: bdm = json.loads(bdm) if bdm and 'snapshot_id' in bdm[0]: snapshot_id = bdm[0]['snapshot_id'] self.addCleanup( self.snapshots_client.wait_for_resource_deletion, snapshot_id) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.snapshots_client.delete_snapshot, snapshot_id) waiters.wait_for_volume_resource_status(self.snapshots_client, snapshot_id, 'available') image_name = snapshot_image['name'] self.assertEqual(name, image_name) LOG.debug("Created snapshot image %s for server %s", image_name, server['name']) return snapshot_image def nova_volume_attach(self, server, volume_to_attach): volume = self.servers_client.attach_volume( server['id'], volumeId=volume_to_attach['id'], device='/dev/%s' % CONF.compute.volume_device_name)['volumeAttachment'] self.assertEqual(volume_to_attach['id'], volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'in-use') # Return the updated volume after the attachment return self.volumes_client.show_volume(volume['id'])['volume'] def nova_volume_detach(self, server, volume): self.servers_client.detach_volume(server['id'], volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') def ping_ip_address(self, ip_address, should_succeed=True, ping_timeout=None, mtu=None): timeout = ping_timeout or CONF.validation.ping_timeout cmd = ['ping', '-c1', '-w1'] if mtu: cmd += [ # don't fragment '-M', 'do', # ping receives just the size of ICMP payload '-s', str(net_utils.get_ping_payload_size(mtu, 4)) ] cmd.append(ip_address) def ping(): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate() return (proc.returncode == 0) == should_succeed caller = test_utils.find_test_caller() LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the' ' expected result is %(should_succeed)s', { 'caller': caller, 'ip': ip_address, 'timeout': timeout, 'should_succeed': 'reachable' if should_succeed else 'unreachable' }) result = test_utils.call_until_true(ping, timeout, 1) LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the ' 'ping result is %(result)s', { 'caller': caller, 'ip': ip_address, 'timeout': timeout, 'result': 'expected' if result else 'unexpected' }) return result def check_vm_connectivity(self, ip_address, username=None, private_key=None, should_connect=True, mtu=None): """Check server connectivity :param ip_address: server to test against :param username: server's ssh username :param private_key: server's ssh private key to be used :param should_connect: True/False indicates positive/negative test positive - attempt ping and ssh negative - attempt ping and fail if succeed :param mtu: network MTU to use for connectivity validation :raises: AssertError if the result of the connectivity check does not match the value of the should_connect param """ if should_connect: msg = "Timed out waiting for %s to become reachable" % ip_address else: msg = "ip address %s is reachable" % ip_address self.assertTrue(self.ping_ip_address(ip_address, should_succeed=should_connect, mtu=mtu), msg=msg) if should_connect: # no need to check ssh for negative connectivity self.get_remote_client(ip_address, username, private_key) def check_public_network_connectivity(self, ip_address, username, private_key, should_connect=True, msg=None, servers=None, mtu=None): # The target login is assumed to have been configured for # key-based authentication by cloud-init. LOG.debug('checking network connections to IP %s with user: %s', ip_address, username) try: self.check_vm_connectivity(ip_address, username, private_key, should_connect=should_connect, mtu=mtu) except Exception: ex_msg = 'Public network connectivity check failed' if msg: ex_msg += ": " + msg LOG.exception(ex_msg) self._log_console_output(servers) raise def create_floating_ip(self, thing, pool_name=None): """Create a floating IP and associates to a server on Nova""" if not pool_name: pool_name = CONF.network.floating_network_name floating_ip = (self.compute_floating_ips_client. create_floating_ip(pool=pool_name)['floating_ip']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.compute_floating_ips_client.delete_floating_ip, floating_ip['id']) self.compute_floating_ips_client.associate_floating_ip_to_server( floating_ip['ip'], thing['id']) return floating_ip def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt', private_key=None): ssh_client = self.get_remote_client(ip_address, private_key=private_key) if dev_name is not None: ssh_client.make_fs(dev_name) ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name, mount_path)) cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path ssh_client.exec_command(cmd_timestamp) timestamp = ssh_client.exec_command('sudo cat %s/timestamp' % mount_path) if dev_name is not None: ssh_client.exec_command('sudo umount %s' % mount_path) return timestamp def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt', private_key=None): ssh_client = self.get_remote_client(ip_address, private_key=private_key) if dev_name is not None: ssh_client.mount(dev_name, mount_path) timestamp = ssh_client.exec_command('sudo cat %s/timestamp' % mount_path) if dev_name is not None: ssh_client.exec_command('sudo umount %s' % mount_path) return timestamp def get_server_ip(self, server): """Get the server fixed or floating IP. Based on the configuration we're in, return a correct ip address for validating that a guest is up. """ if CONF.validation.connect_method == 'floating': # The tests calling this method don't have a floating IP # and can't make use of the validation resources. So the # method is creating the floating IP there. return self.create_floating_ip(server)['ip'] elif CONF.validation.connect_method == 'fixed': # Determine the network name to look for based on config or creds # provider network resources. if CONF.validation.network_for_ssh: addresses = server['addresses'][ CONF.validation.network_for_ssh] else: network = self.get_tenant_network() addresses = (server['addresses'][network['name']] if network else []) for address in addresses: if (address['version'] == CONF.validation.ip_version_for_ssh and address['OS-EXT-IPS:type'] == 'fixed'): return address['addr'] raise exceptions.ServerUnreachable(server_id=server['id']) else: raise lib_exc.InvalidConfiguration() class NetworkScenarioTest(ScenarioTest): """Base class for network scenario tests. This class provide helpers for network scenario tests, using the neutron API. Helpers from ancestor which use the nova network API are overridden with the neutron API. This Class also enforces using Neutron instead of novanetwork. Subclassed tests will be skipped if Neutron is not enabled """ credentials = ['primary', 'admin'] @classmethod def skip_checks(cls): super(NetworkScenarioTest, cls).skip_checks() if not CONF.service_available.neutron: raise cls.skipException('Neutron not available') def _create_network(self, networks_client=None, tenant_id=None, namestart='network-smoke-', port_security_enabled=True): if not networks_client: networks_client = self.networks_client if not tenant_id: tenant_id = networks_client.tenant_id name = data_utils.rand_name(namestart) network_kwargs = dict(name=name, tenant_id=tenant_id) # Neutron disables port security by default so we have to check the # config before trying to create the network with port_security_enabled if CONF.network_feature_enabled.port_security: network_kwargs['port_security_enabled'] = port_security_enabled result = networks_client.create_network(**network_kwargs) network = result['network'] self.assertEqual(network['name'], name) self.addCleanup(test_utils.call_and_ignore_notfound_exc, networks_client.delete_network, network['id']) return network def create_subnet(self, network, subnets_client=None, namestart='subnet-smoke', **kwargs): """Create a subnet for the given network within the cidr block configured for tenant networks. """ if not subnets_client: subnets_client = self.subnets_client def cidr_in_use(cidr, tenant_id): """Check cidr existence :returns: True if subnet with cidr already exist in tenant False else """ cidr_in_use = self.os_admin.subnets_client.list_subnets( tenant_id=tenant_id, cidr=cidr)['subnets'] return len(cidr_in_use) != 0 ip_version = kwargs.pop('ip_version', 4) if ip_version == 6: tenant_cidr = netaddr.IPNetwork( CONF.network.project_network_v6_cidr) num_bits = CONF.network.project_network_v6_mask_bits else: tenant_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr) num_bits = CONF.network.project_network_mask_bits result = None str_cidr = None # Repeatedly attempt subnet creation with sequential cidr # blocks until an unallocated block is found. for subnet_cidr in tenant_cidr.subnet(num_bits): str_cidr = str(subnet_cidr) if cidr_in_use(str_cidr, tenant_id=network['tenant_id']): continue subnet = dict( name=data_utils.rand_name(namestart), network_id=network['id'], tenant_id=network['tenant_id'], cidr=str_cidr, ip_version=ip_version, **kwargs ) try: result = subnets_client.create_subnet(**subnet) break except lib_exc.Conflict as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise self.assertIsNotNone(result, 'Unable to allocate tenant network') subnet = result['subnet'] self.assertEqual(subnet['cidr'], str_cidr) self.addCleanup(test_utils.call_and_ignore_notfound_exc, subnets_client.delete_subnet, subnet['id']) return subnet def _get_server_port_id_and_ip4(self, server, ip_addr=None): ports = self.os_admin.ports_client.list_ports( device_id=server['id'], fixed_ip=ip_addr)['ports'] # A port can have more than one IP address in some cases. # If the network is dual-stack (IPv4 + IPv6), this port is associated # with 2 subnets p_status = ['ACTIVE'] # NOTE(vsaienko) With Ironic, instances live on separate hardware # servers. Neutron does not bind ports for Ironic instances, as a # result the port remains in the DOWN state. # TODO(vsaienko) remove once bug: #1599836 is resolved. if getattr(CONF.service_available, 'ironic', False): p_status.append('DOWN') port_map = [(p["id"], fxip["ip_address"]) for p in ports for fxip in p["fixed_ips"] if netutils.is_valid_ipv4(fxip["ip_address"]) and p['status'] in p_status] inactive = [p for p in ports if p['status'] != 'ACTIVE'] if inactive: LOG.warning("Instance has ports that are not ACTIVE: %s", inactive) self.assertNotEmpty(port_map, "No IPv4 addresses found in: %s" % ports) self.assertEqual(len(port_map), 1, "Found multiple IPv4 addresses: %s. " "Unable to determine which port to target." % port_map) return port_map[0] def _get_network_by_name(self, network_name): net = self.os_admin.networks_client.list_networks( name=network_name)['networks'] self.assertNotEmpty(net, "Unable to get network by name: %s" % network_name) return net[0] def create_floating_ip(self, thing, external_network_id=None, port_id=None, client=None): """Create a floating IP and associates to a resource/port on Neutron""" if not external_network_id: external_network_id = CONF.network.public_network_id if not client: client = self.floating_ips_client if not port_id: port_id, ip4 = self._get_server_port_id_and_ip4(thing) else: ip4 = None result = client.create_floatingip( floating_network_id=external_network_id, port_id=port_id, tenant_id=thing['tenant_id'], fixed_ip_address=ip4 ) floating_ip = result['floatingip'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, client.delete_floatingip, floating_ip['id']) return floating_ip def check_floating_ip_status(self, floating_ip, status): """Verifies floatingip reaches the given status :param dict floating_ip: floating IP dict to check status :param status: target status :raises: AssertionError if status doesn't match """ floatingip_id = floating_ip['id'] def refresh(): result = (self.floating_ips_client. show_floatingip(floatingip_id)['floatingip']) return status == result['status'] if not test_utils.call_until_true(refresh, CONF.network.build_timeout, CONF.network.build_interval): floating_ip = self.floating_ips_client.show_floatingip( floatingip_id)['floatingip'] self.assertEqual(status, floating_ip['status'], message="FloatingIP: {fp} is at status: {cst}. " "failed to reach status: {st}" .format(fp=floating_ip, cst=floating_ip['status'], st=status)) LOG.info("FloatingIP: {fp} is at status: {st}" .format(fp=floating_ip, st=status)) def check_tenant_network_connectivity(self, server, username, private_key, should_connect=True, servers_for_debug=None): if not CONF.network.project_networks_reachable: msg = 'Tenant networks not configured to be reachable.' LOG.info(msg) return # The target login is assumed to have been configured for # key-based authentication by cloud-init. try: for ip_addresses in server['addresses'].values(): for ip_address in ip_addresses: self.check_vm_connectivity(ip_address['addr'], username, private_key, should_connect=should_connect) except Exception as e: LOG.exception('Tenant network connectivity check failed') self._log_console_output(servers_for_debug) self._log_net_info(e) raise def check_remote_connectivity(self, source, dest, should_succeed=True, nic=None): """assert ping server via source ssh connection :param source: RemoteClient: an ssh connection from which to ping :param dest: an IP to ping against :param should_succeed: boolean: should ping succeed or not :param nic: specific network interface to ping from """ def ping_remote(): try: source.ping_host(dest, nic=nic) except lib_exc.SSHExecCommandFailed: LOG.warning('Failed to ping IP: %s via a ssh connection ' 'from: %s.', dest, source.ssh_client.host) return not should_succeed return should_succeed result = test_utils.call_until_true(ping_remote, CONF.validation.ping_timeout, 1) if result: return source_host = source.ssh_client.host if should_succeed: msg = "Timed out waiting for %s to become reachable from %s" \ % (dest, source_host) else: msg = "%s is reachable from %s" % (dest, source_host) self._log_console_output() self.fail(msg) def _create_security_group(self, security_group_rules_client=None, tenant_id=None, namestart='secgroup-smoke', security_groups_client=None): if security_group_rules_client is None: security_group_rules_client = self.security_group_rules_client if security_groups_client is None: security_groups_client = self.security_groups_client if tenant_id is None: tenant_id = security_groups_client.tenant_id secgroup = self._create_empty_security_group( namestart=namestart, client=security_groups_client, tenant_id=tenant_id) # Add rules to the security group rules = self._create_loginable_secgroup_rule( security_group_rules_client=security_group_rules_client, secgroup=secgroup, security_groups_client=security_groups_client) for rule in rules: self.assertEqual(tenant_id, rule['tenant_id']) self.assertEqual(secgroup['id'], rule['security_group_id']) return secgroup def _create_empty_security_group(self, client=None, tenant_id=None, namestart='secgroup-smoke'): """Create a security group without rules. Default rules will be created: - IPv4 egress to any - IPv6 egress to any :param tenant_id: secgroup will be created in this tenant :returns: the created security group """ if client is None: client = self.security_groups_client if not tenant_id: tenant_id = client.tenant_id sg_name = data_utils.rand_name(namestart) sg_desc = sg_name + " description" sg_dict = dict(name=sg_name, description=sg_desc) sg_dict['tenant_id'] = tenant_id result = client.create_security_group(**sg_dict) secgroup = result['security_group'] self.assertEqual(secgroup['name'], sg_name) self.assertEqual(tenant_id, secgroup['tenant_id']) self.assertEqual(secgroup['description'], sg_desc) self.addCleanup(test_utils.call_and_ignore_notfound_exc, client.delete_security_group, secgroup['id']) return secgroup def _create_security_group_rule(self, secgroup=None, sec_group_rules_client=None, tenant_id=None, security_groups_client=None, **kwargs): """Create a rule from a dictionary of rule parameters. Create a rule in a secgroup. if secgroup not defined will search for default secgroup in tenant_id. :param secgroup: the security group. :param tenant_id: if secgroup not passed -- the tenant in which to search for default secgroup :param kwargs: a dictionary containing rule parameters: for example, to allow incoming ssh: rule = { direction: 'ingress' protocol:'tcp', port_range_min: 22, port_range_max: 22 } """ if sec_group_rules_client is None: sec_group_rules_client = self.security_group_rules_client if security_groups_client is None: security_groups_client = self.security_groups_client if not tenant_id: tenant_id = security_groups_client.tenant_id if secgroup is None: # Get default secgroup for tenant_id default_secgroups = security_groups_client.list_security_groups( name='default', tenant_id=tenant_id)['security_groups'] msg = "No default security group for tenant %s." % (tenant_id) self.assertNotEmpty(default_secgroups, msg) secgroup = default_secgroups[0] ruleset = dict(security_group_id=secgroup['id'], tenant_id=secgroup['tenant_id']) ruleset.update(kwargs) sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset) sg_rule = sg_rule['security_group_rule'] self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id']) self.assertEqual(secgroup['id'], sg_rule['security_group_id']) return sg_rule def _create_loginable_secgroup_rule(self, security_group_rules_client=None, secgroup=None, security_groups_client=None): """Create loginable security group rule This function will create: 1. egress and ingress tcp port 22 allow rule in order to allow ssh access for ipv4. 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6. 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4. """ if security_group_rules_client is None: security_group_rules_client = self.security_group_rules_client if security_groups_client is None: security_groups_client = self.security_groups_client rules = [] rulesets = [ dict( # ssh protocol='tcp', port_range_min=22, port_range_max=22, ), dict( # ping protocol='icmp', ), dict( # ipv6-icmp for ping6 protocol='icmp', ethertype='IPv6', ) ] sec_group_rules_client = security_group_rules_client for ruleset in rulesets: for r_direction in ['ingress', 'egress']: ruleset['direction'] = r_direction try: sg_rule = self._create_security_group_rule( sec_group_rules_client=sec_group_rules_client, secgroup=secgroup, security_groups_client=security_groups_client, **ruleset) except lib_exc.Conflict as ex: # if rule already exist - skip rule and continue msg = 'Security group rule already exists' if msg not in ex._error_string: raise ex else: self.assertEqual(r_direction, sg_rule['direction']) rules.append(sg_rule) return rules def _get_router(self, client=None, tenant_id=None): """Retrieve a router for the given tenant id. If a public router has been configured, it will be returned. If a public router has not been configured, but a public network has, a tenant router will be created and returned that routes traffic to the public network. """ if not client: client = self.routers_client if not tenant_id: tenant_id = client.tenant_id router_id = CONF.network.public_router_id network_id = CONF.network.public_network_id if router_id: body = client.show_router(router_id) return body['router'] elif network_id: router = client.create_router( name=data_utils.rand_name(self.__class__.__name__ + '-router'), admin_state_up=True, tenant_id=tenant_id, external_gateway_info=dict(network_id=network_id))['router'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, client.delete_router, router['id']) return router else: raise Exception("Neither of 'public_router_id' or " "'public_network_id' has been defined.") def create_networks(self, networks_client=None, routers_client=None, subnets_client=None, tenant_id=None, dns_nameservers=None, port_security_enabled=True): """Create a network with a subnet connected to a router. The baremetal driver is a special case since all nodes are on the same shared network. :param tenant_id: id of tenant to create resources in. :param dns_nameservers: list of dns servers to send to subnet. :returns: network, subnet, router """ if CONF.network.shared_physical_network: # NOTE(Shrews): This exception is for environments where tenant # credential isolation is available, but network separation is # not (the current baremetal case). Likely can be removed when # test account mgmt is reworked: # https://blueprints.launchpad.net/tempest/+spec/test-accounts if not CONF.compute.fixed_network_name: m = 'fixed_network_name must be specified in config' raise lib_exc.InvalidConfiguration(m) network = self._get_network_by_name( CONF.compute.fixed_network_name) router = None subnet = None else: network = self._create_network( networks_client=networks_client, tenant_id=tenant_id, port_security_enabled=port_security_enabled) router = self._get_router(client=routers_client, tenant_id=tenant_id) subnet_kwargs = dict(network=network, subnets_client=subnets_client) # use explicit check because empty list is a valid option if dns_nameservers is not None: subnet_kwargs['dns_nameservers'] = dns_nameservers subnet = self.create_subnet(**subnet_kwargs) if not routers_client: routers_client = self.routers_client router_id = router['id'] routers_client.add_router_interface(router_id, subnet_id=subnet['id']) # save a cleanup job to remove this association between # router and subnet self.addCleanup(test_utils.call_and_ignore_notfound_exc, routers_client.remove_router_interface, router_id, subnet_id=subnet['id']) return network, subnet, router class EncryptionScenarioTest(ScenarioTest): """Base class for encryption scenario tests""" credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(EncryptionScenarioTest, cls).setup_clients() cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client cls.admin_encryption_types_client =\ cls.os_admin.encryption_types_v2_client def create_encryption_type(self, client=None, type_id=None, provider=None, key_size=None, cipher=None, control_location=None): if not client: client = self.admin_encryption_types_client if not type_id: volume_type = self.create_volume_type() type_id = volume_type['id'] LOG.debug("Creating an encryption type for volume type: %s", type_id) client.create_encryption_type( type_id, provider=provider, key_size=key_size, cipher=cipher, control_location=control_location)['encryption'] def create_encrypted_volume(self, encryption_provider, volume_type, key_size=256, cipher='aes-xts-plain64', control_location='front-end'): volume_type = self.create_volume_type(name=volume_type) self.create_encryption_type(type_id=volume_type['id'], provider=encryption_provider, key_size=key_size, cipher=cipher, control_location=control_location) return self.create_volume(volume_type=volume_type['name']) class ObjectStorageScenarioTest(ScenarioTest): """Provide harness to do Object Storage scenario tests. Subclasses implement the tests that use the methods provided by this class. """ @classmethod def skip_checks(cls): super(ObjectStorageScenarioTest, cls).skip_checks() if not CONF.service_available.swift: skip_msg = ("%s skipped as swift is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_credentials(cls): cls.set_network_resources() super(ObjectStorageScenarioTest, cls).setup_credentials() operator_role = CONF.object_storage.operator_role cls.os_operator = cls.get_client_manager(roles=[operator_role]) @classmethod def setup_clients(cls): super(ObjectStorageScenarioTest, cls).setup_clients() # Clients for Swift cls.account_client = cls.os_operator.account_client cls.container_client = cls.os_operator.container_client cls.object_client = cls.os_operator.object_client def get_swift_stat(self): """get swift status for our user account.""" self.account_client.list_account_containers() LOG.debug('Swift status information obtained successfully') def create_container(self, container_name=None): name = container_name or data_utils.rand_name( 'swift-scenario-container') self.container_client.update_container(name) # look for the container to assure it is created self.list_and_check_container_objects(name) LOG.debug('Container %s created', name) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.container_client.delete_container, name) return name def delete_container(self, container_name): self.container_client.delete_container(container_name) LOG.debug('Container %s deleted', container_name) def upload_object_to_container(self, container_name, obj_name=None): obj_name = obj_name or data_utils.rand_name('swift-scenario-object') obj_data = data_utils.random_bytes() self.object_client.create_object(container_name, obj_name, obj_data) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.object_client.delete_object, container_name, obj_name) return obj_name, obj_data def delete_object(self, container_name, filename): self.object_client.delete_object(container_name, filename) self.list_and_check_container_objects(container_name, not_present_obj=[filename]) def list_and_check_container_objects(self, container_name, present_obj=None, not_present_obj=None): # List objects for a given container and assert which are present and # which are not. if present_obj is None: present_obj = [] if not_present_obj is None: not_present_obj = [] _, object_list = self.container_client.list_container_objects( container_name) if present_obj: for obj in present_obj: self.assertIn(obj, object_list) if not_present_obj: for obj in not_present_obj: self.assertNotIn(obj, object_list) def download_and_verify(self, container_name, obj_name, expected_data): _, obj = self.object_client.get_object(container_name, obj_name) self.assertEqual(obj, expected_data) tempest-17.2.0/tempest/scenario/test_server_basic_ops.py0000666000175100017510000001451613207044712023544 0ustar zuulzuul00000000000000# 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 json import re from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions from tempest.scenario import manager CONF = config.CONF class TestServerBasicOps(manager.ScenarioTest): """The test suite for server basic operations This smoke test case follows this basic set of operations: * Create a keypair for use in launching an instance * Create a security group to control network access in instance * Add simple permissive rules to the security group * Launch an instance * Perform ssh to instance * Verify metadata service * Verify metadata on config_drive * Terminate the instance """ @classmethod def skip_checks(cls): super(TestServerBasicOps, cls).skip_checks() if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") def setUp(self): super(TestServerBasicOps, self).setUp() self.run_ssh = CONF.validation.run_validation self.ssh_user = CONF.validation.image_ssh_user def verify_ssh(self, keypair): if self.run_ssh: # Obtain a floating IP self.fip = self.create_floating_ip(self.instance)['ip'] # Check ssh self.ssh_client = self.get_remote_client( ip_address=self.fip, username=self.ssh_user, private_key=keypair['private_key'], server=self.instance) def verify_metadata(self): if self.run_ssh and CONF.compute_feature_enabled.metadata_service: # Verify metadata service md_url = 'http://169.254.169.254/latest/meta-data/public-ipv4' def exec_cmd_and_verify_output(): cmd = 'curl ' + md_url result = self.ssh_client.exec_command(cmd) if result: msg = ('Failed while verifying metadata on server. Result ' 'of command "%s" is NOT "%s".' % (cmd, self.fip)) self.assertEqual(self.fip, result, msg) return 'Verification is successful!' if not test_utils.call_until_true(exec_cmd_and_verify_output, CONF.compute.build_timeout, CONF.compute.build_interval): raise exceptions.TimeoutException('Timed out while waiting to ' 'verify metadata on server. ' '%s is empty.' % md_url) # Also, test a POST md_url = 'http://169.254.169.254/openstack/2013-10-17/password' data = data_utils.arbitrary_string(100) cmd = 'curl -X POST -d ' + data + ' ' + md_url self.ssh_client.exec_command(cmd) result = self.servers_client.show_password(self.instance['id']) self.assertEqual(data, result['password']) def _mount_config_drive(self): cmd_blkid = 'blkid | grep -i config-2' result = self.ssh_client.exec_command(cmd_blkid) dev_name = re.match('([^:]+)', result).group() self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name) def _unmount_config_drive(self): self.ssh_client.exec_command('sudo umount /mnt') def verify_metadata_on_config_drive(self): if self.run_ssh and CONF.compute_feature_enabled.config_drive: # Verify metadata on config_drive self._mount_config_drive() cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json' result = self.ssh_client.exec_command(cmd_md) self._unmount_config_drive() result = json.loads(result) self.assertIn('meta', result) msg = ('Failed while verifying metadata on config_drive on server.' ' Result of command "%s" is NOT "%s".' % (cmd_md, self.md)) self.assertEqual(self.md, result['meta'], msg) def verify_networkdata_on_config_drive(self): if self.run_ssh and CONF.compute_feature_enabled.config_drive: # Verify network data on config_drive self._mount_config_drive() cmd_md = 'sudo cat /mnt/openstack/latest/network_data.json' result = self.ssh_client.exec_command(cmd_md) self._unmount_config_drive() result = json.loads(result) self.assertIn('services', result) self.assertIn('links', result) self.assertIn('networks', result) # TODO(clarkb) construct network_data from known network # instance info and do direct comparison. @decorators.idempotent_id('7fff3fb3-91d8-4fd0-bd7d-0204f1f180ba') @decorators.attr(type='smoke') @utils.services('compute', 'network') def test_server_basic_ops(self): keypair = self.create_keypair() security_group = self._create_security_group() self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'} self.instance = self.create_server( key_name=keypair['name'], security_groups=[{'name': security_group['name']}], config_drive=CONF.compute_feature_enabled.config_drive, metadata=self.md) self.verify_ssh(keypair) self.verify_metadata() self.verify_metadata_on_config_drive() self.verify_networkdata_on_config_drive() self.servers_client.delete_server(self.instance['id']) waiters.wait_for_server_termination( self.servers_client, self.instance['id'], ignore_error=False) tempest-17.2.0/tempest/scenario/README.rst0000666000175100017510000000303513207044712020264 0ustar zuulzuul00000000000000.. _scenario_field_guide: Tempest Field Guide to Scenario tests ===================================== What are these tests? --------------------- Scenario tests are "through path" tests of OpenStack function. Complicated setups where one part might depend on completion of a previous part. They ideally involve the integration between multiple OpenStack services to exercise the touch points between them. Any scenario test should have a real-life use case. An example would be: - "As operator I want to start with a blank environment": 1. upload a glance image 2. deploy a vm from it 3. ssh to the guest 4. create a snapshot of the vm Why are these tests in Tempest? ------------------------------- This is one of Tempest's core purposes, testing the integration between projects. Scope of these tests -------------------- Scenario tests should always use the Tempest implementation of the OpenStack API, as we want to ensure that bugs aren't hidden by the official clients. Tests should be tagged with which services they exercise, as determined by which client libraries are used directly by the test. Example of a good test ---------------------- While we are looking for interaction of 2 or more services, be specific in your interactions. A giant "this is my data center" smoke test is hard to debug when it goes wrong. A flow of interactions between Glance and Nova, like in the introduction, is a good example. Especially if it involves a repeated interaction when a resource is setup, modified, detached, and then reused later again. tempest-17.2.0/tempest/scenario/test_server_multinode.py0000666000175100017510000000716513207044712023604 0ustar zuulzuul00000000000000# 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 tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions from tempest.scenario import manager CONF = config.CONF class TestServerMultinode(manager.ScenarioTest): """This is a set of tests specific to multinode testing.""" credentials = ['primary', 'admin'] @classmethod def skip_checks(cls): super(TestServerMultinode, cls).skip_checks() if CONF.compute.min_compute_nodes < 2: raise cls.skipException( "Less than 2 compute nodes, skipping multinode tests.") @decorators.idempotent_id('9cecbe35-b9d4-48da-a37e-7ce70aa43d30') @decorators.attr(type='smoke') @utils.services('compute', 'network') def test_schedule_to_all_nodes(self): available_zone = \ self.os_admin.availability_zone_client.list_availability_zones( detail=True)['availabilityZoneInfo'] hosts = [] for zone in available_zone: if zone['zoneState']['available']: for host in zone['hosts']: if 'nova-compute' in zone['hosts'][host] and \ zone['hosts'][host]['nova-compute']['available']: hosts.append({'zone': zone['zoneName'], 'host_name': host}) # ensure we have at least as many compute hosts as we expect if len(hosts) < CONF.compute.min_compute_nodes: raise exceptions.InvalidConfiguration( "Host list %s is shorter than min_compute_nodes. " "Did a compute worker not boot correctly?" % hosts) # create 1 compute for each node, up to the min_compute_nodes # threshold (so that things don't get crazy if you have 1000 # compute nodes but set min to 3). servers = [] for host in hosts[:CONF.compute.min_compute_nodes]: # by getting to active state here, this means this has # landed on the host in question. # in order to use the availability_zone:host scheduler hint, # admin client is need here. inst = self.create_server( clients=self.os_admin, availability_zone='%(zone)s:%(host_name)s' % host) server = self.os_admin.servers_client.show_server( inst['id'])['server'] # ensure server is located on the requested host self.assertEqual(host['host_name'], server['OS-EXT-SRV-ATTR:host']) servers.append(server) # make sure we really have the number of servers we think we should self.assertEqual( len(servers), CONF.compute.min_compute_nodes, "Incorrect number of servers built %s" % servers) # ensure that every server ended up on a different host host_ids = [x['hostId'] for x in servers] self.assertEqual( len(set(host_ids)), len(servers), "Incorrect number of distinct host_ids scheduled to %s" % servers) tempest-17.2.0/tempest/scenario/test_security_groups_basic_ops.py0000666000175100017510000006663413207044712025514 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_log import log import testtools from tempest.common import compute from tempest.common import utils from tempest.common.utils import net_info from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF LOG = log.getLogger(__name__) class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): """The test suite for security groups This test suite assumes that Nova has been configured to boot VM's with Neutron-managed networking, and attempts to verify cross tenant connectivity as follows ssh: in order to overcome "ip namespace", each tenant has an "access point" VM with floating-ip open to incoming ssh connection allowing network commands (ping/ssh) to be executed from within the tenant-network-namespace Tempest host performs key-based authentication to the ssh server via floating IP address connectivity test is done by pinging destination server via source server ssh connection. success - ping returns failure - ping_timeout reached multi-node: Multi-Node mode is enabled when CONF.compute.min_compute_nodes > 1. Tests connectivity between servers on different compute nodes. When enabled, test will boot each new server to different compute nodes. setup: for primary tenant: 1. create a network&subnet 2. create a router (if public router isn't configured) 3. connect tenant network to public network via router 4. create an access point: a. a security group open to incoming ssh connection b. a VM with a floating ip 5. create a general empty security group (same as "default", but without rules allowing in-tenant traffic) tests: 1. _verify_network_details 2. _verify_mac_addr: for each access point verify that (subnet, fix_ip, mac address) are as defined in the port list 3. _test_in_tenant_block: test that in-tenant traffic is disabled without rules allowing it 4. _test_in_tenant_allow: test that in-tenant traffic is enabled once an appropriate rule has been created 5. _test_cross_tenant_block: test that cross-tenant traffic is disabled without a rule allowing it on destination tenant 6. _test_cross_tenant_allow: * test that cross-tenant traffic is enabled once an appropriate rule has been created on destination tenant. * test that reverse traffic is still blocked * test than reverse traffic is enabled once an appropriate rule has been created on source tenant 7._test_port_update_new_security_group: * test that traffic is blocked with default security group * test that traffic is enabled after updating port with new security group having appropriate rule 8. _test_multiple_security_groups: test multiple security groups can be associated with the vm assumptions: 1. alt_tenant/user existed and is different from primary_tenant/user 2. Public network is defined and reachable from the Tempest host 3. Public router can either be: * defined, in which case all tenants networks can connect directly to it, and cross tenant check will be done on the private IP of the destination tenant or * not defined (empty string), in which case each tenant will have its own router connected to the public network """ credentials = ['primary', 'alt', 'admin'] class TenantProperties(object): """helper class to save tenant details id credentials network subnet security groups servers access point """ def __init__(self, clients): # Credentials from manager are filled with both names and IDs self.manager = clients self.creds = self.manager.credentials self.network = None self.subnet = None self.router = None self.security_groups = {} self.servers = list() self.access_point = None def set_network(self, network, subnet, router): self.network = network self.subnet = subnet self.router = router @classmethod def skip_checks(cls): super(TestSecurityGroupsBasicOps, cls).skip_checks() if CONF.network.port_vnic_type in ['direct', 'macvtap']: msg = ('Not currently supported when using vnic_type' ' direct or macvtap') raise cls.skipException(msg) if not (CONF.network.project_networks_reachable or CONF.network.public_network_id): msg = ('Either project_networks_reachable must be "true", or ' 'public_network_id must be defined.') raise cls.skipException(msg) if not utils.is_extension_enabled('security-group', 'network'): msg = "security-group extension not enabled." raise cls.skipException(msg) if CONF.network.shared_physical_network: msg = ('Deployment uses a shared physical network, security ' 'groups not supported') raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def setup_credentials(cls): # Create no network resources for these tests. cls.set_network_resources() super(TestSecurityGroupsBasicOps, cls).setup_credentials() @classmethod def resource_setup(cls): super(TestSecurityGroupsBasicOps, cls).resource_setup() cls.multi_node = CONF.compute.min_compute_nodes > 1 and \ compute.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 = {} cls.primary_tenant = cls.TenantProperties(cls.os_primary) cls.alt_tenant = cls.TenantProperties(cls.os_alt) for tenant in [cls.primary_tenant, cls.alt_tenant]: cls.tenants[tenant.creds.tenant_id] = tenant 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) self._verify_mac_addr(self.primary_tenant) def _create_tenant_keypairs(self, tenant): keypair = self.create_keypair(tenant.manager.keypairs_client) tenant.keypair = keypair def _create_tenant_security_groups(self, tenant): access_sg = self._create_empty_security_group( namestart='secgroup_access-', tenant_id=tenant.creds.tenant_id, client=tenant.manager.security_groups_client ) # don't use default secgroup since it allows in-project traffic def_sg = self._create_empty_security_group( namestart='secgroup_general-', tenant_id=tenant.creds.tenant_id, client=tenant.manager.security_groups_client ) tenant.security_groups.update(access=access_sg, default=def_sg) ssh_rule = dict( protocol='tcp', port_range_min=22, port_range_max=22, direction='ingress', ) sec_group_rules_client = tenant.manager.security_group_rules_client self._create_security_group_rule( secgroup=access_sg, sec_group_rules_client=sec_group_rules_client, **ssh_rule) def _verify_network_details(self, tenant): # Checks that we see the newly created network/subnet/router via # checking the result of list_[networks,routers,subnets] # Check that (router, subnet) couple exist in port_list seen_nets = self.os_admin.networks_client.list_networks() seen_names = [n['name'] for n in seen_nets['networks']] seen_ids = [n['id'] for n in seen_nets['networks']] self.assertIn(tenant.network['name'], seen_names) self.assertIn(tenant.network['id'], seen_ids) seen_subnets = [ (n['id'], n['cidr'], n['network_id']) for n in self.os_admin.subnets_client.list_subnets()['subnets'] ] mysubnet = (tenant.subnet['id'], tenant.subnet['cidr'], tenant.network['id']) self.assertIn(mysubnet, seen_subnets) seen_routers = self.os_admin.routers_client.list_routers() seen_router_ids = [n['id'] for n in seen_routers['routers']] seen_router_names = [n['name'] for n in seen_routers['routers']] self.assertIn(tenant.router['name'], seen_router_names) self.assertIn(tenant.router['id'], seen_router_ids) myport = (tenant.router['id'], tenant.subnet['id']) router_ports = [ (i['device_id'], f['subnet_id']) for i in self.os_admin.ports_client.list_ports( device_id=tenant.router['id'])['ports'] if net_info.is_router_interface_port(i) for f in i['fixed_ips'] ] self.assertIn(myport, router_ports) def _create_server(self, name, tenant, security_groups, **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. """ 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, clients=tenant.manager, **kwargs) if 'security_groups' in server: 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.os_admin.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): for i in range(num): name = 'server-{tenant}-gen-{num}'.format( tenant=tenant.creds.tenant_name, num=i ) name = data_utils.rand_name(name) server = self._create_server(name, tenant, [tenant.security_groups['default']]) tenant.servers.append(server) def _set_access_point(self, tenant): # creates a server in a secgroup with rule allowing external ssh # in order to access project internal network # workaround ip namespace secgroups = tenant.security_groups.values() name = 'server-{tenant}-access_point'.format( tenant=tenant.creds.tenant_name) name = data_utils.rand_name(name) server = self._create_server(name, tenant, security_groups=secgroups) tenant.access_point = server self._assign_floating_ips(tenant, server) def _assign_floating_ips(self, tenant, server): public_network_id = CONF.network.public_network_id floating_ip = self.create_floating_ip( server, public_network_id, client=tenant.manager.floating_ips_client) self.floating_ips.setdefault(server['id'], floating_ip) def _create_tenant_network(self, tenant, port_security_enabled=True): network, subnet, router = self.create_networks( networks_client=tenant.manager.networks_client, routers_client=tenant.manager.routers_client, subnets_client=tenant.manager.subnets_client, port_security_enabled=port_security_enabled) tenant.set_network(network, subnet, router) def _deploy_tenant(self, tenant_or_id): """creates: network subnet router (if public not defined) access security group access-point server """ if not isinstance(tenant_or_id, self.TenantProperties): tenant = self.tenants[tenant_or_id] else: tenant = tenant_or_id self._create_tenant_keypairs(tenant) self._create_tenant_network(tenant) self._create_tenant_security_groups(tenant) self._set_access_point(tenant) def _get_server_ip(self, server, floating=False): """returns the ip (floating/internal) of a server""" if floating: server_ip = self.floating_ips[server['id']]['floating_ip_address'] else: server_ip = None network_name = self.tenants[server['tenant_id']].network['name'] if network_name in server['addresses']: server_ip = server['addresses'][network_name][0]['addr'] return server_ip def _connect_to_access_point(self, tenant): """create ssh connection to tenant access point""" access_point_ssh = \ self.floating_ips[tenant.access_point['id']]['floating_ip_address'] private_key = tenant.keypair['private_key'] access_point_ssh = self.get_remote_client( access_point_ssh, private_key=private_key) return access_point_ssh def _test_in_tenant_block(self, tenant): access_point_ssh = self._connect_to_access_point(tenant) for server in tenant.servers: self.check_remote_connectivity(source=access_point_ssh, dest=self._get_server_ip(server), should_succeed=False) def _test_in_tenant_allow(self, tenant): ruleset = dict( protocol='icmp', remote_group_id=tenant.security_groups['default']['id'], direction='ingress' ) 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) for server in tenant.servers: self.check_remote_connectivity(source=access_point_ssh, dest=self._get_server_ip(server)) def _test_cross_tenant_block(self, source_tenant, dest_tenant): # if public router isn't defined, then dest_tenant access is via # floating-ip access_point_ssh = self._connect_to_access_point(source_tenant) ip = self._get_server_ip(dest_tenant.access_point, floating=self.floating_ip_access) self.check_remote_connectivity(source=access_point_ssh, dest=ip, should_succeed=False) def _test_cross_tenant_allow(self, source_tenant, dest_tenant): """check for each direction: creating rule for tenant incoming traffic enables only 1way traffic """ ruleset = dict( protocol='icmp', direction='ingress' ) sec_group_rules_client = ( dest_tenant.manager.security_group_rules_client) self._create_security_group_rule( secgroup=dest_tenant.security_groups['default'], sec_group_rules_client=sec_group_rules_client, **ruleset ) access_point_ssh = self._connect_to_access_point(source_tenant) ip = self._get_server_ip(dest_tenant.access_point, floating=self.floating_ip_access) self.check_remote_connectivity(access_point_ssh, ip) # test that reverse traffic is still blocked self._test_cross_tenant_block(dest_tenant, source_tenant) # allow reverse traffic and check sec_group_rules_client = ( source_tenant.manager.security_group_rules_client) self._create_security_group_rule( secgroup=source_tenant.security_groups['default'], sec_group_rules_client=sec_group_rules_client, **ruleset ) access_point_ssh_2 = self._connect_to_access_point(dest_tenant) ip = self._get_server_ip(source_tenant.access_point, floating=self.floating_ip_access) self.check_remote_connectivity(access_point_ssh_2, ip) def _verify_mac_addr(self, tenant): """Verify that VM has the same ip, mac as listed in port""" access_point_ssh = self._connect_to_access_point(tenant) mac_addr = access_point_ssh.get_mac_address() mac_addr = mac_addr.strip().lower() # Get the fixed_ips and mac_address fields of all ports. Select # only those two columns to reduce the size of the response. port_list = self.os_admin.ports_client.list_ports( fields=['fixed_ips', 'mac_address'])['ports'] port_detail_list = [ (port['fixed_ips'][0]['subnet_id'], port['fixed_ips'][0]['ip_address'], port['mac_address'].lower()) for port in port_list if port['fixed_ips'] ] server_ip = self._get_server_ip(tenant.access_point) subnet_id = tenant.subnet['id'] self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list) def _log_console_output_for_all_tenants(self): for tenant in self.tenants.values(): client = tenant.manager.servers_client self._log_console_output(servers=tenant.servers, client=client) if tenant.access_point is not None: self._log_console_output( servers=[tenant.access_point], client=client) @decorators.idempotent_id('e79f879e-debb-440c-a7e4-efeda05b6848') @utils.services('compute', 'network') def test_cross_tenant_traffic(self): if not self.credentials_provider.is_multi_tenant(): raise self.skipException("No secondary tenant defined") try: # deploy new project self._deploy_tenant(self.alt_tenant) self._verify_network_details(self.alt_tenant) self._verify_mac_addr(self.alt_tenant) # cross tenant check source_tenant = self.primary_tenant dest_tenant = self.alt_tenant self._test_cross_tenant_block(source_tenant, dest_tenant) self._test_cross_tenant_allow(source_tenant, dest_tenant) except Exception: self._log_console_output_for_all_tenants() raise @decorators.idempotent_id('63163892-bbf6-4249-aa12-d5ea1f8f421b') @utils.services('compute', 'network') def test_in_tenant_traffic(self): try: self._create_tenant_servers(self.primary_tenant, num=1) # in-tenant check self._test_in_tenant_block(self.primary_tenant) self._test_in_tenant_allow(self.primary_tenant) except Exception: self._log_console_output_for_all_tenants() raise @decorators.idempotent_id('f4d556d7-1526-42ad-bafb-6bebf48568f6') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_port_update_new_security_group(self): """Verifies the traffic after updating the vm port With new security group having appropriate rule. """ new_tenant = self.primary_tenant # Create empty security group and add icmp rule in it new_sg = self._create_empty_security_group( namestart='secgroup_new-', tenant_id=new_tenant.creds.tenant_id, client=new_tenant.manager.security_groups_client) icmp_rule = dict( protocol='icmp', direction='ingress', ) sec_group_rules_client = new_tenant.manager.security_group_rules_client self._create_security_group_rule( secgroup=new_sg, sec_group_rules_client=sec_group_rules_client, **icmp_rule) new_tenant.security_groups.update(new_sg=new_sg) # Create server with default security group name = 'server-{tenant}-gen-1'.format( tenant=new_tenant.creds.tenant_name ) name = data_utils.rand_name(name) server = self._create_server(name, new_tenant, [new_tenant.security_groups['default']]) # Check connectivity failure with default security group try: access_point_ssh = self._connect_to_access_point(new_tenant) self.check_remote_connectivity(source=access_point_ssh, dest=self._get_server_ip(server), should_succeed=False) server_id = server['id'] port_id = self.os_admin.ports_client.list_ports( device_id=server_id)['ports'][0]['id'] # update port with new security group and check connectivity self.ports_client.update_port(port_id, security_groups=[ new_tenant.security_groups['new_sg']['id']]) self.check_remote_connectivity( source=access_point_ssh, dest=self._get_server_ip(server)) except Exception: self._log_console_output_for_all_tenants() raise @decorators.idempotent_id('d2f77418-fcc4-439d-b935-72eca704e293') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_multiple_security_groups(self): """Verify multiple security groups and checks that rules provided in the both the groups is applied onto VM """ tenant = self.primary_tenant ip = self._get_server_ip(tenant.access_point, floating=self.floating_ip_access) ssh_login = CONF.validation.image_ssh_user private_key = tenant.keypair['private_key'] self.check_vm_connectivity(ip, should_connect=False) ruleset = dict( protocol='icmp', direction='ingress' ) self._create_security_group_rule( secgroup=tenant.security_groups['default'], **ruleset ) # NOTE: Vm now has 2 security groups one with ssh rule( # already added in setUp() method),and other with icmp rule # (added in the above step).The check_vm_connectivity tests # -that vm ping test is successful # -ssh to vm is successful self.check_vm_connectivity(ip, username=ssh_login, private_key=private_key, should_connect=True) @decorators.attr(type='slow') @utils.requires_ext(service='network', extension='port-security') @decorators.idempotent_id('7c811dcc-263b-49a3-92d2-1b4d8405f50c') @utils.services('compute', 'network') def test_port_security_disable_security_group(self): """Verify the default security group rules is disabled.""" new_tenant = self.primary_tenant # Create server name = 'server-{tenant}-gen-1'.format( tenant=new_tenant.creds.tenant_name ) name = data_utils.rand_name(name) server = self._create_server(name, new_tenant, [new_tenant.security_groups['default']]) access_point_ssh = self._connect_to_access_point(new_tenant) server_id = server['id'] port_id = self.os_admin.ports_client.list_ports( device_id=server_id)['ports'][0]['id'] # Flip the port's port security and check connectivity try: self.ports_client.update_port(port_id, port_security_enabled=True, security_groups=[]) self.check_remote_connectivity(source=access_point_ssh, dest=self._get_server_ip(server), should_succeed=False) self.ports_client.update_port(port_id, port_security_enabled=False, security_groups=[]) self.check_remote_connectivity( source=access_point_ssh, dest=self._get_server_ip(server)) except Exception: self._log_console_output_for_all_tenants() raise @decorators.attr(type='slow') @utils.requires_ext(service='network', extension='port-security') @decorators.idempotent_id('13ccf253-e5ad-424b-9c4a-97b88a026699') # TODO(mriedem): We shouldn't actually need to check this since neutron # disables the port_security extension by default, but the problem is nova # assumes port_security_enabled=True if it's not set on the network # resource, which will mean nova may attempt to apply a security group on # a port on that network which would fail. This is really a bug in nova. @testtools.skipUnless( CONF.network_feature_enabled.port_security, 'Port security must be enabled.') @utils.services('compute', 'network') def test_boot_into_disabled_port_security_network_without_secgroup(self): tenant = self.primary_tenant self._create_tenant_network(tenant, port_security_enabled=False) self.assertFalse(tenant.network['port_security_enabled']) name = data_utils.rand_name('server-smoke') sec_groups = [] server = self._create_server(name, tenant, sec_groups) server_id = server['id'] ports = self.os_admin.ports_client.list_ports( device_id=server_id)['ports'] self.assertEqual(1, len(ports)) for port in ports: self.assertEmpty(port['security_groups'], "Neutron shouldn't even use it's default sec " "group.") tempest-17.2.0/tempest/scenario/test_network_v6.py0000666000175100017510000002534613207044712022323 0ustar zuulzuul00000000000000# Copyright 2014 Cisco 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. from tempest.common import utils from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF class TestGettingAddress(manager.NetworkScenarioTest): """Test Summary: 1. Create network with subnets: 1.1. one IPv4 and 1.2. one or more IPv6 in a given address mode 2. Boot 2 VMs on this network 3. Allocate and assign 2 FIP4 4. Check that vNICs of all VMs gets all addresses actually assigned 5. Each VM will ping the other's v4 private address 6. If ping6 available in VM, each VM will ping all of the other's v6 addresses as well as the router's """ @classmethod def skip_checks(cls): super(TestGettingAddress, cls).skip_checks() if not (CONF.network_feature_enabled.ipv6 and CONF.network_feature_enabled.ipv6_subnet_attributes): raise cls.skipException('IPv6 or its attributes not supported') if not (CONF.network.project_networks_reachable or CONF.network.public_network_id): msg = ('Either project_networks_reachable must be "true", or ' 'public_network_id must be defined.') raise cls.skipException(msg) if CONF.network.shared_physical_network: msg = 'Deployment uses a shared physical network' raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def setup_credentials(cls): # Create no network resources for these tests. cls.set_network_resources() super(TestGettingAddress, cls).setup_credentials() def setUp(self): super(TestGettingAddress, self).setUp() self.keypair = self.create_keypair() self.sec_grp = self._create_security_group() def prepare_network(self, address6_mode, n_subnets6=1, dualnet=False): """Prepare network Creates network with given number of IPv6 subnets in the given mode and one IPv4 subnet. Creates router with ports on all subnets. if dualnet - create IPv6 subnets on a different network :return: list of created networks """ network = self._create_network() if dualnet: network_v6 = self._create_network() sub4 = self.create_subnet(network=network, namestart='sub4', ip_version=4) router = self._get_router() self.routers_client.add_router_interface(router['id'], subnet_id=sub4['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.routers_client.remove_router_interface, router['id'], subnet_id=sub4['id']) self.subnets_v6 = [] for _ in range(n_subnets6): net6 = network_v6 if dualnet else network sub6 = self.create_subnet(network=net6, namestart='sub6', ip_version=6, ipv6_ra_mode=address6_mode, ipv6_address_mode=address6_mode) self.routers_client.add_router_interface(router['id'], subnet_id=sub6['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.routers_client.remove_router_interface, router['id'], subnet_id=sub6['id']) self.subnets_v6.append(sub6) return [network, network_v6] if dualnet else [network] @staticmethod def define_server_ips(srv): ips = {'4': None, '6': []} for nics in srv['addresses'].values(): for nic in nics: if nic['version'] == 6: ips['6'].append(nic['addr']) else: ips['4'] = nic['addr'] return ips def prepare_server(self, networks=None): username = CONF.validation.image_ssh_user srv = self.create_server( key_name=self.keypair['name'], security_groups=[{'name': self.sec_grp['name']}], networks=[{'uuid': n['id']} for n in networks]) fip = self.create_floating_ip(thing=srv) ips = self.define_server_ips(srv=srv) ssh = self.get_remote_client( ip_address=fip['floating_ip_address'], username=username, server=srv) return ssh, ips, srv def turn_nic6_on(self, ssh, sid, network_id): """Turns the IPv6 vNIC on Required because guest images usually set only the first vNIC on boot. Searches for the IPv6 vNIC's MAC and brings it up. @param ssh: RemoteClient ssh instance to server @param sid: server uuid @param network_id: the network id the NIC is connected to """ ports = [ p["mac_address"] for p in self.os_admin.ports_client.list_ports( device_id=sid, network_id=network_id)['ports'] ] self.assertEqual(1, len(ports), message=("Multiple IPv6 ports found on network %s. " "ports: %s") % (network_id, ports)) mac6 = ports[0] nic = ssh.get_nic_name_by_mac(mac6) ssh.exec_command("sudo ip link set %s up" % nic) def _prepare_and_test(self, address6_mode, n_subnets6=1, dualnet=False): net_list = self.prepare_network(address6_mode=address6_mode, n_subnets6=n_subnets6, dualnet=dualnet) sshv4_1, ips_from_api_1, srv1 = self.prepare_server(networks=net_list) sshv4_2, ips_from_api_2, srv2 = self.prepare_server(networks=net_list) def guest_has_address(ssh, addr): return addr in ssh.exec_command("ip address") # Turn on 2nd NIC for Cirros when dualnet if dualnet: _, network_v6 = net_list self.turn_nic6_on(sshv4_1, srv1['id'], network_v6['id']) self.turn_nic6_on(sshv4_2, srv2['id'], network_v6['id']) # get addresses assigned to vNIC as reported by 'ip address' utility ips_from_ip_1 = sshv4_1.exec_command("ip address") ips_from_ip_2 = sshv4_2.exec_command("ip address") self.assertIn(ips_from_api_1['4'], ips_from_ip_1) self.assertIn(ips_from_api_2['4'], ips_from_ip_2) for i in range(n_subnets6): # v6 should be configured since the image supports it # It can take time for ipv6 automatic address to get assigned for srv, ssh, ips in ( (srv1, sshv4_1, ips_from_api_1), (srv2, sshv4_2, ips_from_api_2)): ip = ips['6'][i] result = test_utils.call_until_true( guest_has_address, CONF.validation.ping_timeout, 1, ssh, ip) if not result: self._log_console_output(servers=[srv]) self.fail( 'Address %s not configured for instance %s, ' 'ip address output is\n%s' % (ip, srv['id'], ssh.exec_command("ip address"))) self.check_remote_connectivity(sshv4_1, ips_from_api_2['4']) self.check_remote_connectivity(sshv4_2, ips_from_api_1['4']) for i in range(n_subnets6): self.check_remote_connectivity(sshv4_1, ips_from_api_2['6'][i]) self.check_remote_connectivity(sshv4_1, self.subnets_v6[i]['gateway_ip']) self.check_remote_connectivity(sshv4_2, ips_from_api_1['6'][i]) self.check_remote_connectivity(sshv4_2, self.subnets_v6[i]['gateway_ip']) @decorators.attr(type='slow') @decorators.idempotent_id('2c92df61-29f0-4eaa-bee3-7c65bef62a43') @utils.services('compute', 'network') def test_slaac_from_os(self): self._prepare_and_test(address6_mode='slaac') @decorators.attr(type='slow') @decorators.idempotent_id('d7e1f858-187c-45a6-89c9-bdafde619a9f') @utils.services('compute', 'network') def test_dhcp6_stateless_from_os(self): self._prepare_and_test(address6_mode='dhcpv6-stateless') @decorators.attr(type='slow') @decorators.idempotent_id('7ab23f41-833b-4a16-a7c9-5b42fe6d4123') @utils.services('compute', 'network') def test_multi_prefix_dhcpv6_stateless(self): self._prepare_and_test(address6_mode='dhcpv6-stateless', n_subnets6=2) @decorators.attr(type='slow') @decorators.idempotent_id('dec222b1-180c-4098-b8c5-cc1b8342d611') @utils.services('compute', 'network') def test_multi_prefix_slaac(self): self._prepare_and_test(address6_mode='slaac', n_subnets6=2) @decorators.attr(type='slow') @decorators.idempotent_id('b6399d76-4438-4658-bcf5-0d6c8584fde2') @utils.services('compute', 'network') def test_dualnet_slaac_from_os(self): self._prepare_and_test(address6_mode='slaac', dualnet=True) @decorators.attr(type='slow') @decorators.idempotent_id('76f26acd-9688-42b4-bc3e-cd134c4cb09e') @utils.services('compute', 'network') def test_dualnet_dhcp6_stateless_from_os(self): self._prepare_and_test(address6_mode='dhcpv6-stateless', dualnet=True) @decorators.attr(type='slow') @decorators.idempotent_id('cf1c4425-766b-45b8-be35-e2959728eb00') @utils.services('compute', 'network') def test_dualnet_multi_prefix_dhcpv6_stateless(self): self._prepare_and_test(address6_mode='dhcpv6-stateless', n_subnets6=2, dualnet=True) @decorators.idempotent_id('9178ad42-10e4-47e9-8987-e02b170cc5cd') @utils.services('compute', 'network') def test_dualnet_multi_prefix_slaac(self): self._prepare_and_test(address6_mode='slaac', n_subnets6=2, dualnet=True) tempest-17.2.0/tempest/scenario/__init__.py0000666000175100017510000000000013207044712020673 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/scenario/test_aggregates_basic_ops.py0000666000175100017510000001227413207044712024346 0ustar zuulzuul00000000000000# 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 tempest.common import tempest_fixtures as fixtures from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.scenario import manager class TestAggregatesBasicOps(manager.ScenarioTest): """Creates an aggregate within an availability zone Adds a host to the aggregate Checks aggregate details Updates aggregate's name Removes host from aggregate Deletes aggregate """ credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(TestAggregatesBasicOps, cls).setup_clients() # Use admin client by default cls.aggregates_client = cls.os_admin.aggregates_client cls.hosts_client = cls.os_admin.hosts_client def _create_aggregate(self, **kwargs): aggregate = (self.aggregates_client.create_aggregate(**kwargs) ['aggregate']) self.addCleanup(self.aggregates_client.delete_aggregate, aggregate['id']) aggregate_name = kwargs['name'] availability_zone = kwargs['availability_zone'] self.assertEqual(aggregate['name'], aggregate_name) self.assertEqual(aggregate['availability_zone'], availability_zone) return aggregate def _get_host_name(self): hosts = self.hosts_client.list_hosts()['hosts'] self.assertNotEmpty(hosts) computes = [x for x in hosts if x['service'] == 'compute'] return computes[0]['host_name'] def _add_host(self, aggregate_id, host): aggregate = (self.aggregates_client.add_host(aggregate_id, host=host) ['aggregate']) self.addCleanup(self._remove_host, aggregate['id'], host) self.assertIn(host, aggregate['hosts']) def _remove_host(self, aggregate_id, host): aggregate = self.aggregates_client.remove_host(aggregate_id, host=host) self.assertNotIn(host, aggregate['aggregate']['hosts']) def _check_aggregate_details(self, aggregate, aggregate_name, azone, hosts, metadata): aggregate = (self.aggregates_client.show_aggregate(aggregate['id']) ['aggregate']) self.assertEqual(aggregate_name, aggregate['name']) self.assertEqual(azone, aggregate['availability_zone']) self.assertEqual(hosts, aggregate['hosts']) for meta_key in metadata: self.assertIn(meta_key, aggregate['metadata']) self.assertEqual(metadata[meta_key], aggregate['metadata'][meta_key]) def _set_aggregate_metadata(self, aggregate, meta): aggregate = self.aggregates_client.set_metadata(aggregate['id'], metadata=meta) for key in meta.keys(): self.assertEqual(meta[key], aggregate['aggregate']['metadata'][key]) def _update_aggregate(self, aggregate, aggregate_name, availability_zone): aggregate = self.aggregates_client.update_aggregate( aggregate['id'], name=aggregate_name, availability_zone=availability_zone)['aggregate'] self.assertEqual(aggregate['name'], aggregate_name) self.assertEqual(aggregate['availability_zone'], availability_zone) return aggregate @decorators.idempotent_id('cb2b4c4f-0c7c-4164-bdde-6285b302a081') @decorators.attr(type='slow') @utils.services('compute') def test_aggregate_basic_ops(self): self.useFixture(fixtures.LockFixture('availability_zone')) az = 'foo_zone' aggregate_name = data_utils.rand_name('aggregate-scenario') aggregate = self._create_aggregate(name=aggregate_name, availability_zone=az) metadata = {'meta_key': 'meta_value'} self._set_aggregate_metadata(aggregate, metadata) host = self._get_host_name() self._add_host(aggregate['id'], host) self._check_aggregate_details(aggregate, aggregate_name, az, [host], metadata) aggregate_name = data_utils.rand_name('renamed-aggregate-scenario') # Updating the name alone. The az must be specified again otherwise # the tempest client would send None in the put body aggregate = self._update_aggregate(aggregate, aggregate_name, az) new_metadata = {'foo': 'bar'} self._set_aggregate_metadata(aggregate, new_metadata) self._check_aggregate_details(aggregate, aggregate['name'], az, [host], new_metadata) tempest-17.2.0/tempest/scenario/test_network_basic_ops.py0000666000175100017510000011465413207044725023737 0ustar zuulzuul00000000000000# 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 collections import re from oslo_log import log as logging import testtools from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions from tempest.scenario import manager CONF = config.CONF LOG = logging.getLogger(__name__) Floating_IP_tuple = collections.namedtuple('Floating_IP_tuple', ['floating_ip', 'server']) class TestNetworkBasicOps(manager.NetworkScenarioTest): """The test suite of network basic operations This smoke test suite assumes that Nova has been configured to boot VM's with Neutron-managed networking, and attempts to verify network connectivity as follows: There are presumed to be two types of networks: tenant and public. A tenant network may or may not be reachable from the Tempest host. A public network is assumed to be reachable from the Tempest host, and it should be possible to associate a public ('floating') IP address with a tenant ('fixed') IP address to facilitate external connectivity to a potentially unroutable tenant IP address. This test suite can be configured to test network connectivity to a VM via a tenant network, a public network, or both. If both networking types are to be evaluated, tests that need to be executed remotely on the VM (via ssh) will only be run against one of the networks (to minimize test execution time). Determine which types of networks to test as follows: * Configure tenant network checks (via the 'project_networks_reachable' key) if the Tempest host should have direct connectivity to tenant networks. This is likely to be the case if Tempest is running on the same host as a single-node devstack installation with IP namespaces disabled. * Configure checks for a public network if a public network has been configured prior to the test suite being run and if the Tempest host should have connectivity to that public network. Checking connectivity for a public network requires that a value be provided for 'public_network_id'. A value can optionally be provided for 'public_router_id' if tenants will use a shared router to access a public network (as is likely to be the case when IP namespaces are not enabled). If a value is not provided for 'public_router_id', a router will be created for each tenant and use the network identified by 'public_network_id' as its gateway. """ @classmethod def skip_checks(cls): super(TestNetworkBasicOps, cls).skip_checks() if not (CONF.network.project_networks_reachable or CONF.network.public_network_id): msg = ('Either project_networks_reachable must be "true", or ' 'public_network_id must be defined.') raise cls.skipException(msg) for ext in ['router', 'security-group']: if not utils.is_extension_enabled(ext, 'network'): msg = "%s extension not enabled." % ext raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def setup_credentials(cls): # Create no network resources for these tests. cls.set_network_resources() super(TestNetworkBasicOps, cls).setup_credentials() def setUp(self): super(TestNetworkBasicOps, self).setUp() self.keypairs = {} self.servers = [] def _setup_network_and_servers(self, **kwargs): boot_with_port = kwargs.pop('boot_with_port', False) self.network, self.subnet, self.router = self.create_networks(**kwargs) self.check_networks() self.ports = [] port_id = None if boot_with_port: # create a port on the network and boot with that port_id = self.create_port(self.network['id'])['id'] self.ports.append({'port': port_id}) server = self._create_server(self.network, port_id) ssh_login = CONF.validation.image_ssh_user for server in self.servers: # call the common method in the parent class self.check_tenant_network_connectivity( server, ssh_login, self._get_server_key(server), servers_for_debug=self.servers) floating_ip = self.create_floating_ip(server) self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server) def check_networks(self): """Checks that we see the newly created network/subnet/router via checking the result of list_[networks,routers,subnets] """ seen_nets = self.os_admin.networks_client.list_networks() seen_names = [n['name'] for n in seen_nets['networks']] seen_ids = [n['id'] for n in seen_nets['networks']] self.assertIn(self.network['name'], seen_names) self.assertIn(self.network['id'], seen_ids) if self.subnet: seen_subnets = self.os_admin.subnets_client.list_subnets() seen_net_ids = [n['network_id'] for n in seen_subnets['subnets']] seen_subnet_ids = [n['id'] for n in seen_subnets['subnets']] self.assertIn(self.network['id'], seen_net_ids) self.assertIn(self.subnet['id'], seen_subnet_ids) if self.router: seen_routers = self.os_admin.routers_client.list_routers() seen_router_ids = [n['id'] for n in seen_routers['routers']] seen_router_names = [n['name'] for n in seen_routers['routers']] self.assertIn(self.router['name'], seen_router_names) self.assertIn(self.router['id'], seen_router_ids) def _create_server(self, network, port_id=None): keypair = self.create_keypair() self.keypairs[keypair['name']] = keypair security_groups = [ {'name': self._create_security_group()['name']} ] network = {'uuid': network['id']} if port_id is not None: network['port'] = port_id server = self.create_server( networks=[network], key_name=keypair['name'], security_groups=security_groups) self.servers.append(server) return server def _get_server_key(self, server): return self.keypairs[server['key_name']]['private_key'] def check_public_network_connectivity( self, should_connect=True, msg=None, should_check_floating_ip_status=True, mtu=None): """Verifies connectivty to a VM via public network and floating IP and verifies floating IP has resource status is correct. :param should_connect: bool. determines if connectivity check is negative or positive. :param msg: Failure message to add to Error message. Should describe the place in the test scenario where the method was called, to indicate the context of the failure :param should_check_floating_ip_status: bool. should status of floating_ip be checked or not :param mtu: int. MTU network to use for connectivity validation """ ssh_login = CONF.validation.image_ssh_user floating_ip, server = self.floating_ip_tuple ip_address = floating_ip['floating_ip_address'] private_key = None floatingip_status = 'DOWN' if should_connect: private_key = self._get_server_key(server) floatingip_status = 'ACTIVE' # Check FloatingIP Status before initiating a connection if should_check_floating_ip_status: self.check_floating_ip_status(floating_ip, floatingip_status) # call the common method in the parent class super(TestNetworkBasicOps, self).check_public_network_connectivity( ip_address, ssh_login, private_key, should_connect, msg, self.servers, mtu=mtu) def _disassociate_floating_ips(self): floating_ip, _ = self.floating_ip_tuple floating_ip = self.floating_ips_client.update_floatingip( floating_ip['id'], port_id=None)['floatingip'] self.assertIsNone(floating_ip['port_id']) self.floating_ip_tuple = Floating_IP_tuple(floating_ip, None) def _reassociate_floating_ips(self): floating_ip, server = self.floating_ip_tuple # create a new server for the floating ip server = self._create_server(self.network) port_id, _ = self._get_server_port_id_and_ip4(server) floating_ip = self.floating_ips_client.update_floatingip( floating_ip['id'], port_id=port_id)['floatingip'] self.assertEqual(port_id, floating_ip['port_id']) self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server) def _create_new_network(self, create_gateway=False): self.new_net = self._create_network() if create_gateway: self.new_subnet = self.create_subnet( network=self.new_net) else: self.new_subnet = self.create_subnet( network=self.new_net, gateway_ip=None) def _hotplug_server(self): old_floating_ip, server = self.floating_ip_tuple ip_address = old_floating_ip['floating_ip_address'] private_key = self._get_server_key(server) ssh_client = self.get_remote_client( ip_address, private_key=private_key, server=server) old_nic_list = self._get_server_nics(ssh_client) # get a port from a list of one item port_list = self.os_admin.ports_client.list_ports( device_id=server['id'])['ports'] self.assertEqual(1, len(port_list)) old_port = port_list[0] interface = self.interface_client.create_interface( server_id=server['id'], net_id=self.new_net['id'])['interfaceAttachment'] self.addCleanup(self.ports_client.wait_for_resource_deletion, interface['port_id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.interface_client.delete_interface, server['id'], interface['port_id']) def check_ports(): self.new_port_list = [ port for port in self.os_admin.ports_client.list_ports( device_id=server['id'])['ports'] if port['id'] != old_port['id'] ] return len(self.new_port_list) == 1 if not test_utils.call_until_true( check_ports, CONF.network.build_timeout, CONF.network.build_interval): raise exceptions.TimeoutException( "No new port attached to the server in time (%s sec)! " "Old port: %s. Number of new ports: %d" % ( CONF.network.build_timeout, old_port, len(self.new_port_list))) new_port = self.new_port_list[0] def check_new_nic(): new_nic_list = self._get_server_nics(ssh_client) self.diff_list = [n for n in new_nic_list if n not in old_nic_list] return len(self.diff_list) == 1 if not test_utils.call_until_true( check_new_nic, CONF.network.build_timeout, CONF.network.build_interval): raise exceptions.TimeoutException("Interface not visible on the " "guest after %s sec" % CONF.network.build_timeout) _, new_nic = self.diff_list[0] ssh_client.exec_command("sudo ip addr add %s/%s dev %s" % ( new_port['fixed_ips'][0]['ip_address'], CONF.network.project_network_mask_bits, new_nic)) ssh_client.exec_command("sudo ip link set %s up" % new_nic) def _get_server_nics(self, ssh_client): reg = re.compile(r'(?P\d+): (?P\w+)[@]?.*:') ipatxt = ssh_client.exec_command("ip address") return reg.findall(ipatxt) def _check_network_internal_connectivity(self, network, should_connect=True): """via ssh check VM internal connectivity: - ping internal gateway and DHCP port, implying in-tenant connectivity pinging both, because L3 and DHCP agents might be on different nodes """ floating_ip, server = self.floating_ip_tuple # get internal ports' ips: # get all network ports in the new network internal_ips = ( p['fixed_ips'][0]['ip_address'] for p in self.os_admin.ports_client.list_ports( tenant_id=server['tenant_id'], network_id=network['id'])['ports'] if p['device_owner'].startswith('network') ) self._check_server_connectivity(floating_ip, internal_ips, should_connect) def _check_network_external_connectivity(self): """ping default gateway to imply external connectivity""" if not CONF.network.public_network_id: msg = 'public network not defined.' LOG.info(msg) return # We ping the external IP from the instance using its floating IP # which is always IPv4, so we must only test connectivity to # external IPv4 IPs if the external network is dualstack. v4_subnets = [ s for s in self.os_admin.subnets_client.list_subnets( network_id=CONF.network.public_network_id)['subnets'] if s['ip_version'] == 4 ] self.assertEqual(1, len(v4_subnets), "Found %d IPv4 subnets" % len(v4_subnets)) external_ips = [v4_subnets[0]['gateway_ip']] self._check_server_connectivity(self.floating_ip_tuple.floating_ip, external_ips) def _check_server_connectivity(self, floating_ip, address_list, should_connect=True): ip_address = floating_ip['floating_ip_address'] private_key = self._get_server_key(self.floating_ip_tuple.server) ssh_source = self.get_remote_client( ip_address, private_key=private_key, server=self.floating_ip_tuple.server) for remote_ip in address_list: self.check_remote_connectivity(ssh_source, remote_ip, should_connect) def _update_router_admin_state(self, router, admin_state_up): kwargs = dict(admin_state_up=admin_state_up) router = self.routers_client.update_router( router['id'], **kwargs)['router'] self.assertEqual(admin_state_up, router['admin_state_up']) @decorators.attr(type='smoke') @decorators.idempotent_id('f323b3ba-82f8-4db7-8ea6-6a895869ec49') @utils.services('compute', 'network') def test_network_basic_ops(self): """Basic network operation test For a freshly-booted VM with an IP address ("port") on a given network: - the Tempest host can ping the IP address. This implies, but does not guarantee (see the ssh check that follows), that the VM has been assigned the correct IP address and has connectivity to the Tempest host. - the Tempest host can perform key-based authentication to an ssh server hosted at the IP address. This check guarantees that the IP address is associated with the target VM. - the Tempest host can ssh into the VM via the IP address and successfully execute the following: - ping an external IP address, implying external connectivity. - ping an external hostname, implying that dns is correctly configured. - ping an internal IP address, implying connectivity to another VM on the same network. - detach the floating-ip from the VM and verify that it becomes unreachable - associate detached floating ip to a new VM and verify connectivity. VMs are created with unique keypair so connectivity also asserts that floating IP is associated with the new VM instead of the old one Verifies that floating IP status is updated correctly after each change """ self._setup_network_and_servers() self.check_public_network_connectivity(should_connect=True) self._check_network_internal_connectivity(network=self.network) self._check_network_external_connectivity() self._disassociate_floating_ips() self.check_public_network_connectivity(should_connect=False, msg="after disassociate " "floating ip") self._reassociate_floating_ips() self.check_public_network_connectivity(should_connect=True, msg="after re-associate " "floating ip") @decorators.idempotent_id('b158ea55-472e-4086-8fa9-c64ac0c6c1d0') @testtools.skipUnless(utils.is_extension_enabled('net-mtu', 'network'), 'No way to calculate MTU for networks') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_mtu_sized_frames(self): """Validate that network MTU sized frames fit through.""" self._setup_network_and_servers() self.check_public_network_connectivity( should_connect=True, mtu=self.network['mtu']) @decorators.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15') @testtools.skipIf(CONF.network.shared_physical_network, 'Connectivity can only be tested when in a ' 'multitenant network environment') @decorators.skip_because(bug="1610994") @decorators.attr(type='slow') @utils.services('compute', 'network') def test_connectivity_between_vms_on_different_networks(self): """Test connectivity between VMs on different networks For a freshly-booted VM with an IP address ("port") on a given network: - the Tempest host can ping the IP address. - the Tempest host can ssh into the VM via the IP address and successfully execute the following: - ping an external IP address, implying external connectivity. - ping an external hostname, implying that dns is correctly configured. - ping an internal IP address, implying connectivity to another VM on the same network. - Create another network on the same tenant with subnet, create an VM on the new network. - Ping the new VM from previous VM failed since the new network was not attached to router yet. - Attach the new network to the router, Ping the new VM from previous VM succeed. """ self._setup_network_and_servers() self.check_public_network_connectivity(should_connect=True) self._check_network_internal_connectivity(network=self.network) self._check_network_external_connectivity() self._create_new_network(create_gateway=True) self._create_server(self.new_net) self._check_network_internal_connectivity(network=self.new_net, should_connect=False) router_id = self.router['id'] self.routers_client.add_router_interface( router_id, subnet_id=self.new_subnet['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.routers_client.remove_router_interface, router_id, subnet_id=self.new_subnet['id']) self._check_network_internal_connectivity(network=self.new_net, should_connect=True) @decorators.idempotent_id('c5adff73-e961-41f1-b4a9-343614f18cfa') @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach, 'NIC hotplug not available') @testtools.skipIf(CONF.network.port_vnic_type in ['direct', 'macvtap'], 'NIC hotplug not supported for ' 'vnic_type direct or macvtap') @utils.services('compute', 'network') def test_hotplug_nic(self): """Test hotplug network interface 1. Create a network and a VM. 2. Check connectivity to the VM via a public network. 3. Create a new network, with no gateway. 4. Bring up a new interface 5. check the VM reach the new network """ self._setup_network_and_servers() self.check_public_network_connectivity(should_connect=True) self._create_new_network() self._hotplug_server() self._check_network_internal_connectivity(network=self.new_net) @decorators.idempotent_id('04b9fe4e-85e8-4aea-b937-ea93885ac59f') @testtools.skipIf(CONF.network.shared_physical_network, 'Router state can be altered only with multitenant ' 'networks capabilities') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_update_router_admin_state(self): """Test to update admin state up of router 1. Check public connectivity before updating admin_state_up attribute of router to False 2. Check public connectivity after updating admin_state_up attribute of router to False 3. Check public connectivity after updating admin_state_up attribute of router to True """ self._setup_network_and_servers() self.check_public_network_connectivity( should_connect=True, msg="before updating " "admin_state_up of router to False") self._update_router_admin_state(self.router, False) # TODO(alokmaurya): Remove should_check_floating_ip_status=False check # once bug 1396310 is fixed self.check_public_network_connectivity( should_connect=False, msg="after updating " "admin_state_up of router to False", should_check_floating_ip_status=False) self._update_router_admin_state(self.router, True) self.check_public_network_connectivity( should_connect=True, msg="after updating " "admin_state_up of router to True") @decorators.idempotent_id('d8bb918e-e2df-48b2-97cd-b73c95450980') @testtools.skipIf(CONF.network.shared_physical_network, 'network isolation not available') @testtools.skipUnless(CONF.scenario.dhcp_client, "DHCP client is not available.") @decorators.attr(type='slow') @utils.services('compute', 'network') def test_subnet_details(self): """Tests that subnet's extra configuration details are affecting VMs. This test relies on non-shared, isolated tenant networks. NOTE: Neutron subnets push data to servers via dhcp-agent, so any update in subnet requires server to actively renew its DHCP lease. 1. Configure subnet with dns nameserver 2. retrieve the VM's configured dns and verify it matches the one configured for the subnet. 3. update subnet's dns 4. retrieve the VM's configured dns and verify it matches the new one configured for the subnet. TODO(yfried): add host_routes any resolution check would be testing either: * l3 forwarding (tested in test_network_basic_ops) * Name resolution of an external DNS nameserver - out of scope for Tempest """ # this test check only updates (no actual resolution) so using # arbitrary ip addresses as nameservers, instead of parsing CONF initial_dns_server = '1.2.3.4' alt_dns_server = '9.8.7.6' # renewal should be immediate. # Timeouts are suggested by salvatore-orlando in # https://bugs.launchpad.net/neutron/+bug/1412325/comments/3 renew_delay = CONF.network.build_interval renew_timeout = CONF.network.build_timeout self._setup_network_and_servers(dns_nameservers=[initial_dns_server]) self.check_public_network_connectivity(should_connect=True) floating_ip, server = self.floating_ip_tuple ip_address = floating_ip['floating_ip_address'] private_key = self._get_server_key(server) ssh_client = self.get_remote_client( ip_address, private_key=private_key, server=server) dns_servers = [initial_dns_server] servers = ssh_client.get_dns_servers() self.assertEqual(set(dns_servers), set(servers), 'Looking for servers: {trgt_serv}. ' 'Retrieved DNS nameservers: {act_serv} ' 'From host: {host}.' .format(host=ssh_client.ssh_client.host, act_serv=servers, trgt_serv=dns_servers)) self.subnet = self.subnets_client.update_subnet( self.subnet['id'], dns_nameservers=[alt_dns_server])['subnet'] # asserts that Neutron DB has updated the nameservers self.assertEqual([alt_dns_server], self.subnet['dns_nameservers'], "Failed to update subnet's nameservers") def check_new_dns_server(): # NOTE: Server needs to renew its dhcp lease in order to get new # definitions from subnet # NOTE(amuller): we are renewing the lease as part of the retry # because Neutron updates dnsmasq asynchronously after the # subnet-update API call returns. ssh_client.renew_lease(fixed_ip=floating_ip['fixed_ip_address'], dhcp_client=CONF.scenario.dhcp_client) if ssh_client.get_dns_servers() != [alt_dns_server]: LOG.debug("Failed to update DNS nameservers") return False return True self.assertTrue(test_utils.call_until_true(check_new_dns_server, renew_timeout, renew_delay), msg="DHCP renewal failed to fetch " "new DNS nameservers") @decorators.idempotent_id('f5dfcc22-45fd-409f-954c-5bd500d7890b') @testtools.skipUnless(CONF.network_feature_enabled.port_admin_state_change, "Changing a port's admin state is not supported " "by the test environment") @decorators.attr(type='slow') @utils.services('compute', 'network') def test_update_instance_port_admin_state(self): """Test to update admin_state_up attribute of instance port 1. Check public and project connectivity before updating admin_state_up attribute of instance port to False 2. Check public and project connectivity after updating admin_state_up attribute of instance port to False 3. Check public and project connectivity after updating admin_state_up attribute of instance port to True """ self._setup_network_and_servers() _, server = self.floating_ip_tuple server_id = server['id'] port_id = self.os_admin.ports_client.list_ports( device_id=server_id)['ports'][0]['id'] server_pip = server['addresses'][self.network['name']][0]['addr'] server2 = self._create_server(self.network) server2_fip = self.create_floating_ip(server2) private_key = self._get_server_key(server2) ssh_client = self.get_remote_client(server2_fip['floating_ip_address'], private_key=private_key, server=server2) self.check_public_network_connectivity( should_connect=True, msg="before updating " "admin_state_up of instance port to False") self.check_remote_connectivity(ssh_client, dest=server_pip, should_succeed=True) self.ports_client.update_port(port_id, admin_state_up=False) self.check_public_network_connectivity( should_connect=False, msg="after updating " "admin_state_up of instance port to False", should_check_floating_ip_status=False) self.check_remote_connectivity(ssh_client, dest=server_pip, should_succeed=False) self.ports_client.update_port(port_id, admin_state_up=True) self.check_public_network_connectivity( should_connect=True, msg="after updating " "admin_state_up of instance port to True") self.check_remote_connectivity(ssh_client, dest=server_pip, should_succeed=True) @decorators.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_preserve_preexisting_port(self): """Test preserve pre-existing port Tests that a pre-existing port provided on server boot is not deleted if the server is deleted. Nova should unbind the port from the instance on delete if the port was not created by Nova as part of the boot request. We should also be able to boot another server with the same port. """ # Setup the network, create a port and boot the server from that port. self._setup_network_and_servers(boot_with_port=True) _, server = self.floating_ip_tuple self.assertEqual(1, len(self.ports), 'There should only be one port created for ' 'server %s.' % server['id']) port_id = self.ports[0]['port'] self.assertIsNotNone(port_id, 'Server should have been created from a ' 'pre-existing port.') # Assert the port is bound to the server. port_list = self.os_admin.ports_client.list_ports( device_id=server['id'], network_id=self.network['id'])['ports'] self.assertEqual(1, len(port_list), 'There should only be one port created for ' 'server %s.' % server['id']) self.assertEqual(port_id, port_list[0]['id']) # Delete the server. self.servers_client.delete_server(server['id']) waiters.wait_for_server_termination(self.servers_client, server['id']) # Assert the port still exists on the network but is unbound from # the deleted server. port = self.ports_client.show_port(port_id)['port'] self.assertEqual(self.network['id'], port['network_id']) self.assertEqual('', port['device_id']) self.assertEqual('', port['device_owner']) # Boot another server with the same port to make sure nothing was # left around that could cause issues. server = self._create_server(self.network, port['id']) port_list = self.os_admin.ports_client.list_ports( device_id=server['id'], network_id=self.network['id'])['ports'] self.assertEqual(1, len(port_list), 'There should only be one port created for ' 'server %s.' % server['id']) self.assertEqual(port['id'], port_list[0]['id']) @utils.requires_ext(service='network', extension='l3_agent_scheduler') @decorators.idempotent_id('2e788c46-fb3f-4ac9-8f82-0561555bea73') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_router_rescheduling(self): """Tests that router can be removed from agent and add to a new agent. 1. Verify connectivity 2. Remove router from all l3-agents 3. Verify connectivity is down 4. Assign router to new l3-agent (or old one if no new agent is available) 5. Verify connectivity """ # TODO(yfried): refactor this test to be used for other agents (dhcp) # as well list_hosts = (self.os_admin.routers_client. list_l3_agents_hosting_router) schedule_router = (self.os_admin.network_agents_client. create_router_on_l3_agent) unschedule_router = (self.os_admin.network_agents_client. delete_router_from_l3_agent) agent_list_alive = set( a["id"] for a in self.os_admin.network_agents_client.list_agents( agent_type="L3 agent")['agents'] if a["alive"] is True ) self._setup_network_and_servers() # NOTE(kevinbenton): we have to use the admin credentials to check # for the distributed flag because self.router only has a project view. admin = self.os_admin.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) self.check_public_network_connectivity(should_connect=True) # remove resource from agents hosting_agents = set(a["id"] for a in list_hosts(self.router['id'])['agents']) no_migration = agent_list_alive == hosting_agents LOG.info("Router will be assigned to {mig} hosting agent". format(mig="the same" if no_migration else "a new")) for hosting_agent in hosting_agents: unschedule_router(hosting_agent, self.router['id']) self.assertNotIn(hosting_agent, [a["id"] for a in list_hosts(self.router['id'])['agents']], 'unscheduling router failed') # verify resource is un-functional self.check_public_network_connectivity( should_connect=False, msg='after router unscheduling', ) # schedule resource to new agent target_agent = list(hosting_agents if no_migration else agent_list_alive - hosting_agents)[0] schedule_router(target_agent, router_id=self.router['id']) self.assertEqual( target_agent, list_hosts(self.router['id'])['agents'][0]['id'], "Router failed to reschedule. Hosting agent doesn't match " "target agent") # verify resource is functional self.check_public_network_connectivity( should_connect=True, msg='After router rescheduling') @utils.requires_ext(service='network', extension='port-security') @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach, 'NIC hotplug not available') @decorators.idempotent_id('7c0bb1a2-d053-49a4-98f9-ca1a1d849f63') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_port_security_macspoofing_port(self): """Tests port_security extension enforces mac spoofing Neutron security groups always apply anti-spoof rules on the VMs. This allows traffic to originate and terminate at the VM as expected, but prevents traffic to pass through the VM. Anti-spoof rules are not required in cases where the VM routes traffic through it. The test steps are : 1. Create a new network. 2. Connect (hotplug) the VM to a new network. 3. Check the VM can ping a server on the new network ("peer") 4. Spoof the mac address of the new VM interface. 5. Check the Security Group enforces mac spoofing and blocks pings via spoofed interface (VM cannot ping the peer). 6. Disable port-security of the spoofed port- set the flag to false. 7. Retest 3rd step and check that the Security Group allows pings via the spoofed interface. """ spoof_mac = "00:00:00:00:00:01" # Create server self._setup_network_and_servers() self.check_public_network_connectivity(should_connect=True) self._create_new_network() self._hotplug_server() fip, server = self.floating_ip_tuple new_ports = self.os_admin.ports_client.list_ports( device_id=server["id"], network_id=self.new_net["id"])['ports'] spoof_port = new_ports[0] private_key = self._get_server_key(server) ssh_client = self.get_remote_client(fip['floating_ip_address'], private_key=private_key, server=server) spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"]) peer = self._create_server(self.new_net) peer_address = peer['addresses'][self.new_net['name']][0]['addr'] self.check_remote_connectivity(ssh_client, dest=peer_address, nic=spoof_nic, should_succeed=True) # Set a mac address by making nic down temporary cmd = ("sudo ip link set {nic} down;" "sudo ip link set dev {nic} address {mac};" "sudo ip link set {nic} up").format(nic=spoof_nic, mac=spoof_mac) ssh_client.exec_command(cmd) new_mac = ssh_client.get_mac_address(nic=spoof_nic) self.assertEqual(spoof_mac, new_mac) self.check_remote_connectivity(ssh_client, dest=peer_address, nic=spoof_nic, should_succeed=False) self.ports_client.update_port(spoof_port["id"], port_security_enabled=False, security_groups=[]) self.check_remote_connectivity(ssh_client, dest=peer_address, nic=spoof_nic, should_succeed=True) tempest-17.2.0/tempest/scenario/test_stamp_pattern.py0000666000175100017510000001256313207044712023075 0ustar zuulzuul00000000000000# 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_log import log as logging import testtools from tempest.common import utils from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc from tempest.scenario import manager CONF = config.CONF LOG = logging.getLogger(__name__) class TestStampPattern(manager.ScenarioTest): """The test suite for both snapshoting and attaching of volume This test is for snapshotting an instance/volume and attaching the volume created from snapshot to the instance booted from snapshot. The following is the scenario outline: 1. Boot an instance "instance1" 2. Create a volume "volume1" 3. Attach volume1 to instance1 4. Create a filesystem on volume1 5. Mount volume1 6. Create a file which timestamp is written in volume1 7. Unmount volume1 8. Detach volume1 from instance1 9. Get a snapshot "snapshot_from_volume" of volume1 10. Get a snapshot "snapshot_from_instance" of instance1 11. Boot an instance "instance2" from snapshot_from_instance 12. Create a volume "volume2" from snapshot_from_volume 13. Attach volume2 to instance2 14. Check the existence of a file which created at 6. in volume2 """ @classmethod def skip_checks(cls): super(TestStampPattern, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") def _wait_for_volume_available_on_the_system(self, ip_address, private_key): ssh = self.get_remote_client(ip_address, private_key=private_key) def _func(): disks = ssh.get_disks() LOG.debug("Disks: %s", disks) return CONF.compute.volume_device_name in disks if not test_utils.call_until_true(_func, CONF.compute.build_timeout, CONF.compute.build_interval): raise lib_exc.TimeoutException @decorators.attr(type='slow') @decorators.skip_because(bug="1664793") @decorators.idempotent_id('10fd234a-515c-41e5-b092-8323060598c5') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting is not available.') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @utils.services('compute', 'network', 'volume', 'image') def test_stamp_pattern(self): # prepare for booting an instance keypair = self.create_keypair() security_group = self._create_security_group() # boot an instance and create a timestamp file in it volume = self.create_volume() server = self.create_server( key_name=keypair['name'], security_groups=[{'name': security_group['name']}]) # create and add floating IP to server1 ip_for_server = self.get_server_ip(server) self.nova_volume_attach(server, volume) self._wait_for_volume_available_on_the_system(ip_for_server, keypair['private_key']) timestamp = self.create_timestamp(ip_for_server, CONF.compute.volume_device_name, private_key=keypair['private_key']) self.nova_volume_detach(server, volume) # snapshot the volume volume_snapshot = self.create_volume_snapshot(volume['id']) # snapshot the instance snapshot_image = self.create_server_snapshot(server=server) # create second volume from the snapshot(volume2) volume_from_snapshot = self.create_volume( snapshot_id=volume_snapshot['id']) # boot second instance from the snapshot(instance2) server_from_snapshot = self.create_server( image_id=snapshot_image['id'], key_name=keypair['name'], security_groups=[{'name': security_group['name']}]) # create and add floating IP to server_from_snapshot ip_for_snapshot = self.get_server_ip(server_from_snapshot) # attach volume2 to instance2 self.nova_volume_attach(server_from_snapshot, volume_from_snapshot) self._wait_for_volume_available_on_the_system(ip_for_snapshot, keypair['private_key']) # check the existence of the timestamp file in the volume2 timestamp2 = self.get_timestamp(ip_for_snapshot, CONF.compute.volume_device_name, private_key=keypair['private_key']) self.assertEqual(timestamp, timestamp2) tempest-17.2.0/tempest/scenario/test_minimum_basic.py0000666000175100017510000001561213207044712023026 0ustar zuulzuul00000000000000# 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. import testtools from tempest.common import custom_matchers from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions from tempest.scenario import manager CONF = config.CONF class TestMinimumBasicScenario(manager.ScenarioTest): """This is a basic minimum scenario test. This test below: * across the multiple components * as a regular user * with and without optional parameters * check command outputs Steps: 1. Create image 2. Create keypair 3. Boot instance with keypair and get list of instances 4. Create volume and show list of volumes 5. Attach volume to instance and getlist of volumes 6. Add IP to instance 7. Create and add security group to instance 8. Check SSH connection to instance 9. Reboot instance 10. Check SSH connection to instance after reboot """ def nova_show(self, server): got_server = (self.servers_client.show_server(server['id']) ['server']) excluded_keys = ['OS-EXT-AZ:availability_zone'] # Exclude these keys because of LP:#1486475 excluded_keys.extend(['OS-EXT-STS:power_state', 'updated']) self.assertThat( server, custom_matchers.MatchesDictExceptForKeys( got_server, excluded_keys=excluded_keys)) def cinder_show(self, volume): got_volume = self.volumes_client.show_volume(volume['id'])['volume'] self.assertEqual(volume, got_volume) def nova_reboot(self, server): self.servers_client.reboot_server(server['id'], type='SOFT') waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') def check_disks(self): # NOTE(andreaf) The device name may be different on different guest OS disks = self.linux_client.get_disks() self.assertEqual(1, disks.count(CONF.compute.volume_device_name)) def create_and_add_security_group_to_server(self, server): secgroup = self._create_security_group() self.servers_client.add_security_group(server['id'], name=secgroup['name']) self.addCleanup(self.servers_client.remove_security_group, server['id'], name=secgroup['name']) def wait_for_secgroup_add(): body = (self.servers_client.show_server(server['id']) ['server']) return {'name': secgroup['name']} in body['security_groups'] if not test_utils.call_until_true(wait_for_secgroup_add, CONF.compute.build_timeout, CONF.compute.build_interval): msg = ('Timed out waiting for adding security group %s to server ' '%s' % (secgroup['id'], server['id'])) raise exceptions.TimeoutException(msg) def _get_floating_ip_in_server_addresses(self, floating_ip, server): for addresses in server['addresses'].values(): for address in addresses: if (address['OS-EXT-IPS:type'] == 'floating' and address['addr'] == floating_ip['ip']): return address @decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @testtools.skipUnless(CONF.network_feature_enabled.floating_ips, 'Floating ips are not available') @utils.services('compute', 'volume', 'image', 'network') def test_minimum_basic_scenario(self): image = self.glance_image_create() keypair = self.create_keypair() server = self.create_server(image_id=image, key_name=keypair['name']) servers = self.servers_client.list_servers()['servers'] self.assertIn(server['id'], [x['id'] for x in servers]) self.nova_show(server) volume = self.create_volume() volumes = self.volumes_client.list_volumes()['volumes'] self.assertIn(volume['id'], [x['id'] for x in volumes]) self.cinder_show(volume) volume = self.nova_volume_attach(server, volume) self.addCleanup(self.nova_volume_detach, server, volume) self.cinder_show(volume) floating_ip = self.create_floating_ip(server) # fetch the server again to make sure the addresses were refreshed # after associating the floating IP server = self.servers_client.show_server(server['id'])['server'] address = self._get_floating_ip_in_server_addresses( floating_ip, server) self.assertIsNotNone( address, "Failed to find floating IP '%s' in server addresses: %s" % (floating_ip['ip'], server['addresses'])) self.create_and_add_security_group_to_server(server) # check that we can SSH to the server before reboot self.linux_client = self.get_remote_client( floating_ip['ip'], private_key=keypair['private_key'], server=server) self.nova_reboot(server) # check that we can SSH to the server after reboot # (both connections are part of the scenario) self.linux_client = self.get_remote_client( floating_ip['ip'], private_key=keypair['private_key'], server=server) self.check_disks() # delete the floating IP, this should refresh the server addresses self.compute_floating_ips_client.delete_floating_ip(floating_ip['id']) def is_floating_ip_detached_from_server(): server_info = self.servers_client.show_server( server['id'])['server'] address = self._get_floating_ip_in_server_addresses( floating_ip, server_info) return (not address) if not test_utils.call_until_true( is_floating_ip_detached_from_server, CONF.compute.build_timeout, CONF.compute.build_interval): msg = ("Floating IP '%s' should not be in server addresses: %s" % (floating_ip['ip'], server['addresses'])) raise exceptions.TimeoutException(msg) tempest-17.2.0/tempest/scenario/test_server_advanced_ops.py0000666000175100017510000000432113207044712024221 0ustar zuulzuul00000000000000# 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_log import log as logging import testtools from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF LOG = logging.getLogger(__name__) class TestServerAdvancedOps(manager.ScenarioTest): """The test suite for server advanced operations This test case stresses some advanced server instance operations: * Resizing a volume-backed instance * Sequence suspend resume """ @classmethod def setup_credentials(cls): cls.set_network_resources() super(TestServerAdvancedOps, cls).setup_credentials() @decorators.attr(type='slow') @decorators.idempotent_id('949da7d5-72c8-4808-8802-e3d70df98e2c') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @utils.services('compute') def test_server_sequence_suspend_resume(self): # We create an instance for use in this test instance_id = self.create_server()['id'] for _ in range(2): LOG.debug("Suspending instance %s", instance_id) self.servers_client.suspend_server(instance_id) waiters.wait_for_server_status(self.servers_client, instance_id, 'SUSPENDED') LOG.debug("Resuming instance %s", instance_id) self.servers_client.resume_server(instance_id) waiters.wait_for_server_status(self.servers_client, instance_id, 'ACTIVE') tempest-17.2.0/tempest/scenario/test_object_storage_basic_ops.py0000666000175100017510000000650413207044712025226 0ustar zuulzuul00000000000000# 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 tempest.common import utils from tempest.lib import decorators from tempest.scenario import manager class TestObjectStorageBasicOps(manager.ObjectStorageScenarioTest): @decorators.idempotent_id('b920faf1-7b8a-4657-b9fe-9c4512bfb381') @utils.services('object_storage') def test_swift_basic_ops(self): """Test swift basic ops. * get swift stat. * create container. * upload a file to the created container. * list container's objects and assure that the uploaded file is present. * download the object and check the content * delete object from container. * list container's objects and assure that the deleted file is gone. * delete a container. """ self.get_swift_stat() container_name = self.create_container() obj_name, obj_data = self.upload_object_to_container(container_name) self.list_and_check_container_objects(container_name, present_obj=[obj_name]) self.download_and_verify(container_name, obj_name, obj_data) self.delete_object(container_name, obj_name) self.list_and_check_container_objects(container_name, not_present_obj=[obj_name]) self.delete_container(container_name) @decorators.idempotent_id('916c7111-cb1f-44b2-816d-8f760e4ea910') @decorators.attr(type='slow') @utils.services('object_storage') def test_swift_acl_anonymous_download(self): """This test will cover below steps: 1. Create container 2. Upload object to the new container 3. Change the ACL of the container 4. Check if the object can be download by anonymous user 5. Delete the object and container """ container_name = self.create_container() obj_name, obj_data = self.upload_object_to_container(container_name) obj_url = '%s/%s/%s' % (self.object_client.base_url, container_name, obj_name) resp, _ = self.object_client.raw_request(obj_url, 'GET') self.assertEqual(resp.status, 401) metadata_param = {'X-Container-Read': '.r:*'} self.container_client.create_update_or_delete_container_metadata( container_name, create_update_metadata=metadata_param, create_update_metadata_prefix='') resp, _ = self.container_client.list_container_metadata(container_name) self.assertEqual(metadata_param['X-Container-Read'], resp['x-container-read']) resp, data = self.object_client.raw_request(obj_url, 'GET') self.assertEqual(resp.status, 200) self.assertEqual(obj_data, data) tempest-17.2.0/tempest/scenario/test_snapshot_pattern.py0000666000175100017510000000557613207044712023616 0ustar zuulzuul00000000000000# 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. import testtools from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF class TestSnapshotPattern(manager.ScenarioTest): """This test is for snapshotting an instance and booting with it. The following is the scenario outline: * boot an instance and create a timestamp file in it * snapshot the instance * boot a second instance from the snapshot * check the existence of the timestamp file in the second instance """ @classmethod def skip_checks(cls): super(TestSnapshotPattern, cls).skip_checks() if not CONF.compute_feature_enabled.snapshot: raise cls.skipException("Snapshotting is not available.") @decorators.idempotent_id('608e604b-1d63-4a82-8e3e-91bc665c90b4') @decorators.attr(type='slow') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @utils.services('compute', 'network', 'image') def test_snapshot_pattern(self): # prepare for booting an instance keypair = self.create_keypair() security_group = self._create_security_group() # boot an instance and create a timestamp file in it server = self.create_server( key_name=keypair['name'], security_groups=[{'name': security_group['name']}]) instance_ip = self.get_server_ip(server) timestamp = self.create_timestamp(instance_ip, private_key=keypair['private_key']) # snapshot the instance snapshot_image = self.create_server_snapshot(server=server) # boot a second instance from the snapshot server_from_snapshot = self.create_server( image_id=snapshot_image['id'], key_name=keypair['name'], security_groups=[{'name': security_group['name']}]) # check the existence of the timestamp file in the second instance server_from_snapshot_ip = self.get_server_ip(server_from_snapshot) timestamp2 = self.get_timestamp(server_from_snapshot_ip, private_key=keypair['private_key']) self.assertEqual(timestamp, timestamp2) tempest-17.2.0/tempest/scenario/test_network_advanced_server_ops.py0000666000175100017510000002662513207044712026005 0ustar zuulzuul00000000000000# 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 testtools from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF class TestNetworkAdvancedServerOps(manager.NetworkScenarioTest): """Check VM connectivity after some advanced instance operations executed: * Stop/Start an instance * Reboot an instance * Rebuild an instance * Pause/Unpause an instance * Suspend/Resume an instance * Resize an instance """ @classmethod def setup_clients(cls): super(TestNetworkAdvancedServerOps, cls).setup_clients() cls.admin_servers_client = cls.os_admin.servers_client @classmethod def skip_checks(cls): super(TestNetworkAdvancedServerOps, cls).skip_checks() if not (CONF.network.project_networks_reachable or CONF.network.public_network_id): msg = ('Either project_networks_reachable must be "true", or ' 'public_network_id must be defined.') raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def setup_credentials(cls): # Create no network resources for these tests. cls.set_network_resources() super(TestNetworkAdvancedServerOps, cls).setup_credentials() def _setup_server(self, keypair): security_groups = [] if utils.is_extension_enabled('security-group', 'network'): security_group = self._create_security_group() security_groups = [{'name': security_group['name']}] network, _, _ = self.create_networks() server = self.create_server( networks=[{'uuid': network['id']}], key_name=keypair['name'], security_groups=security_groups) return server def _setup_network(self, server, keypair): public_network_id = CONF.network.public_network_id floating_ip = self.create_floating_ip(server, public_network_id) # Verify that we can indeed connect to the server before we mess with # it's state self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) return floating_ip def _check_network_connectivity(self, server, keypair, floating_ip, should_connect=True): username = CONF.validation.image_ssh_user private_key = keypair['private_key'] self.check_tenant_network_connectivity( server, username, private_key, should_connect=should_connect, servers_for_debug=[server]) floating_ip_addr = floating_ip['floating_ip_address'] # Check FloatingIP status before checking the connectivity self.check_floating_ip_status(floating_ip, 'ACTIVE') self.check_public_network_connectivity(floating_ip_addr, username, private_key, should_connect, servers=[server]) def _wait_server_status_and_check_network_connectivity(self, server, keypair, floating_ip): waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') self._check_network_connectivity(server, keypair, floating_ip) def _get_host_for_server(self, server_id): body = self.admin_servers_client.show_server(server_id)['server'] return body['OS-EXT-SRV-ATTR:host'] @decorators.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_stop_start(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) self.servers_client.stop_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SHUTOFF') self._check_network_connectivity(server, keypair, floating_ip, should_connect=False) self.servers_client.start_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.idempotent_id('7b6860c2-afa3-4846-9522-adeb38dfbe08') @utils.services('compute', 'network') def test_server_connectivity_reboot(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) self.servers_client.reboot_server(server['id'], type='SOFT') self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.idempotent_id('88a529c2-1daa-4c85-9aec-d541ba3eb699') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_rebuild(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) image_ref_alt = CONF.compute.image_ref_alt self.servers_client.rebuild_server(server['id'], image_ref=image_ref_alt) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.idempotent_id('2b2642db-6568-4b35-b812-eceed3fa20ce') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_pause_unpause(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) self.servers_client.pause_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'PAUSED') self._check_network_connectivity(server, keypair, floating_ip, should_connect=False) self.servers_client.unpause_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.idempotent_id('5cdf9499-541d-4923-804e-b9a60620a7f0') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_suspend_resume(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) self.servers_client.suspend_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SUSPENDED') self._check_network_connectivity(server, keypair, floating_ip, should_connect=False) self.servers_client.resume_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.idempotent_id('719eb59d-2f42-4b66-b8b1-bb1254473967') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize is not available.') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_resize(self): resize_flavor = CONF.compute.flavor_ref_alt keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) self.servers_client.resize_server(server['id'], flavor_ref=resize_flavor) waiters.wait_for_server_status(self.servers_client, server['id'], 'VERIFY_RESIZE') self.servers_client.confirm_resize_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) @decorators.idempotent_id('a4858f6c-401e-4155-9a49-d5cd053d1a2f') @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, 'Cold migration is not available.') @testtools.skipUnless(CONF.compute.min_compute_nodes > 1, 'Less than 2 compute nodes, skipping multinode ' 'tests.') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_cold_migration(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) src_host = self._get_host_for_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) self.admin_servers_client.migrate_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'VERIFY_RESIZE') self.servers_client.confirm_resize_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) dst_host = self._get_host_for_server(server['id']) self.assertNotEqual(src_host, dst_host) @decorators.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6') @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, 'Cold migration is not available.') @testtools.skipUnless(CONF.compute.min_compute_nodes > 1, 'Less than 2 compute nodes, skipping multinode ' 'tests.') @decorators.attr(type='slow') @utils.services('compute', 'network') def test_server_connectivity_cold_migration_revert(self): keypair = self.create_keypair() server = self._setup_server(keypair) floating_ip = self._setup_network(server, keypair) src_host = self._get_host_for_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) self.admin_servers_client.migrate_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'VERIFY_RESIZE') self.servers_client.revert_resize_server(server['id']) self._wait_server_status_and_check_network_connectivity( server, keypair, floating_ip) dst_host = self._get_host_for_server(server['id']) self.assertEqual(src_host, dst_host) tempest-17.2.0/tempest/scenario/test_encrypted_cinder_volumes.py0000666000175100017510000000574013207044712025306 0ustar zuulzuul00000000000000# Copyright (c) 2014 The Johns Hopkins University/Applied Physics Laboratory # 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.common import utils from tempest import config from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF class TestEncryptedCinderVolumes(manager.EncryptionScenarioTest): """The test suite for encrypted cinder volumes This test is for verifying the functionality of encrypted cinder volumes. For both LUKS and cryptsetup encryption types, this test performs the following: * Creates an image in Glance * Boots an instance from the image * Creates an encryption type (as admin) * Creates a volume of that encryption type (as a regular user) * Attaches and detaches the encrypted volume to the instance """ @classmethod def skip_checks(cls): super(TestEncryptedCinderVolumes, cls).skip_checks() if not CONF.compute_feature_enabled.attach_encrypted_volume: raise cls.skipException('Encrypted volume attach is not supported') def launch_instance(self): image = self.glance_image_create() keypair = self.create_keypair() return self.create_server(image_id=image, key_name=keypair['name']) def attach_detach_volume(self, server, volume): attached_volume = self.nova_volume_attach(server, volume) self.nova_volume_detach(server, attached_volume) @decorators.idempotent_id('79165fb4-5534-4b9d-8429-97ccffb8f86e') @decorators.attr(type='slow') @utils.services('compute', 'volume', 'image') def test_encrypted_cinder_volumes_luks(self): server = self.launch_instance() volume = self.create_encrypted_volume('nova.volume.encryptors.' 'luks.LuksEncryptor', volume_type='luks') self.attach_detach_volume(server, volume) @decorators.idempotent_id('cbc752ed-b716-4717-910f-956cce965722') @decorators.attr(type='slow') @utils.services('compute', 'volume', 'image') def test_encrypted_cinder_volumes_cryptsetup(self): server = self.launch_instance() volume = self.create_encrypted_volume('nova.volume.encryptors.' 'cryptsetup.CryptsetupEncryptor', volume_type='cryptsetup') self.attach_detach_volume(server, volume) tempest-17.2.0/tempest/scenario/test_volume_migrate_attached.py0000666000175100017510000001210413207044712025057 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF LOG = logging.getLogger(__name__) class TestVolumeMigrateRetypeAttached(manager.ScenarioTest): """This test case attempts to reproduce the following steps: * Create 2 volume types representing 2 different backends * Create in Cinder some bootable volume importing a Glance image using * volume_type_1 * Boot an instance from the bootable volume * Write to the volume * Perform a cinder retype --on-demand of the volume to type of backend #2 * Check written content of migrated volume """ credentials = ['primary', 'admin'] @classmethod def skip_checks(cls): super(TestVolumeMigrateRetypeAttached, cls).skip_checks() if not CONF.volume_feature_enabled.multi_backend: raise cls.skipException("Cinder multi-backend feature disabled") if len(set(CONF.volume.backend_names)) < 2: raise cls.skipException("Requires at least two different " "backend names") def _boot_instance_from_volume(self, vol_id, keypair, security_group): key_name = keypair['name'] security_groups = [{'name': security_group['name']}] block_device_mapping = [{'device_name': 'vda', 'volume_id': vol_id, 'delete_on_termination': False}] return self.create_server(image_id='', key_name=key_name, security_groups=security_groups, block_device_mapping=block_device_mapping) def _create_volume_types(self): backend_names = CONF.volume.backend_names backend_source = backend_names[0] backend_dest = backend_names[1] source_body = self.create_volume_type(backend_name=backend_source) dest_body = self.create_volume_type(backend_name=backend_dest) LOG.info("Created Volume types: %(src)s -> %(src_backend)s, %(dst)s " "-> %(dst_backend)s", {'src': source_body['name'], 'src_backend': backend_source, 'dst': dest_body['name'], 'dst_backend': backend_dest}) return source_body['name'], dest_body['name'] def _volume_retype_with_migration(self, volume_id, new_volume_type): migration_policy = 'on-demand' self.volumes_client.retype_volume( volume_id, new_type=new_volume_type, migration_policy=migration_policy) waiters.wait_for_volume_retype(self.volumes_client, volume_id, new_volume_type) @decorators.attr(type='slow') @decorators.idempotent_id('deadd2c2-beef-4dce-98be-f86765ff311b') @utils.services('compute', 'volume') def test_volume_migrate_attached(self): LOG.info("Creating keypair and security group") keypair = self.create_keypair() security_group = self._create_security_group() # create volume types LOG.info("Creating Volume types") source_type, dest_type = self._create_volume_types() # create an instance from volume LOG.info("Booting instance from volume") volume_origin = self.create_volume(imageRef=CONF.compute.image_ref, volume_type=source_type) instance = self._boot_instance_from_volume(volume_origin['id'], keypair, security_group) # write content to volume on instance LOG.info("Setting timestamp in instance %s", instance['id']) ip_instance = self.get_server_ip(instance) timestamp = self.create_timestamp(ip_instance, private_key=keypair['private_key']) # retype volume with migration from backend #1 to backend #2 LOG.info("Retyping Volume %s to new type %s", volume_origin['id'], dest_type) self._volume_retype_with_migration(volume_origin['id'], dest_type) # check the content of written file LOG.info("Getting timestamp in postmigrated instance %s", instance['id']) timestamp2 = self.get_timestamp(ip_instance, private_key=keypair['private_key']) self.assertEqual(timestamp, timestamp2) tempest-17.2.0/tempest/scenario/test_volume_boot_pattern.py0000666000175100017510000002366113207044712024304 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import testtools from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.scenario import manager CONF = config.CONF LOG = logging.getLogger(__name__) class TestVolumeBootPattern(manager.EncryptionScenarioTest): # 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() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") def _create_volume_from_image(self): img_uuid = CONF.compute.image_ref vol_name = data_utils.rand_name( self.__class__.__name__ + '-volume-origin') return self.create_volume(name=vol_name, imageRef=img_uuid) def _get_bdm(self, source_id, source_type, delete_on_termination=False): bd_map_v2 = [{ 'uuid': source_id, 'source_type': source_type, 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': delete_on_termination}] return {'block_device_mapping_v2': bd_map_v2} def _boot_instance_from_resource(self, source_id, source_type, keypair=None, security_group=None, delete_on_termination=False): create_kwargs = dict() if keypair: create_kwargs['key_name'] = keypair['name'] if security_group: create_kwargs['security_groups'] = [ {'name': security_group['name']}] create_kwargs.update(self._get_bdm( source_id, source_type, delete_on_termination=delete_on_termination)) return self.create_server(image_id='', **create_kwargs) def _delete_server(self, server): self.servers_client.delete_server(server['id']) waiters.wait_for_server_termination(self.servers_client, server['id']) @decorators.idempotent_id('557cd2c2-4eb8-4dce-98be-f86765ff311b') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @utils.services('compute', 'volume', 'image') def test_volume_boot_pattern(self): """This test case attempts to reproduce the following steps: * Create in Cinder some bootable volume importing a Glance image * Boot an instance from the bootable volume * Write content to the volume * Delete an instance and Boot a new instance from the volume * Check written content in the instance * Create a volume snapshot while the instance is running * Boot an additional instance from the new snapshot based volume * Check written content in the instance booted from snapshot """ 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_resource( source_id=volume_origin['id'], source_type='volume', keypair=keypair, security_group=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_resource( source_id=volume_origin['id'], source_type='volume', keypair=keypair, security_group=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_volume_snapshot(volume_origin['id'], force=True) # create a 3rd instance from snapshot LOG.info("Creating third instance from snapshot: %s", snapshot['id']) volume = self.create_volume(snapshot_id=snapshot['id'], size=snapshot['size']) LOG.info("Booting third instance from snapshot") server_from_snapshot = ( self._boot_instance_from_resource(source_id=volume['id'], source_type='volume', keypair=keypair, security_group=security_group)) LOG.info("Booted third instance %s", server_from_snapshot) # 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']) self.assertEqual(timestamp, timestamp3) @decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489') @decorators.attr(type='slow') @utils.services('compute', 'image', 'volume') def test_create_server_from_volume_snapshot(self): # Create a volume from an image boot_volume = self._create_volume_from_image() # Create a snapshot boot_snapshot = self.create_volume_snapshot(boot_volume['id']) # Create a server from a volume snapshot server = self._boot_instance_from_resource( source_id=boot_snapshot['id'], source_type='snapshot', delete_on_termination=True) server_info = self.servers_client.show_server(server['id'])['server'] # The created volume when creating a server from a snapshot created_volume = server_info['os-extended-volumes:volumes_attached'] self.assertNotEmpty(created_volume, "No volume attachment found.") created_volume_info = self.volumes_client.show_volume( created_volume[0]['id'])['volume'] # Verify the server was created from the snapshot self.assertEqual( boot_volume['volume_image_metadata']['image_id'], created_volume_info['volume_image_metadata']['image_id']) self.assertEqual(boot_snapshot['id'], created_volume_info['snapshot_id']) self.assertEqual(server['id'], created_volume_info['attachments'][0]['server_id']) self.assertEqual(created_volume[0]['id'], created_volume_info['attachments'][0]['volume_id']) @decorators.idempotent_id('36c34c67-7b54-4b59-b188-02a2f458a63b') @utils.services('compute', 'volume', 'image') def test_create_ebs_image_and_check_boot(self): # create an instance from volume volume_origin = self._create_volume_from_image() instance = self._boot_instance_from_resource( source_id=volume_origin['id'], source_type='volume', delete_on_termination=True) # create EBS image image = self.create_server_snapshot(instance) # delete instance self._delete_server(instance) # boot instance from EBS image instance = self.create_server(image_id=image['id']) # just ensure that instance booted # delete instance self._delete_server(instance) @decorators.idempotent_id('cb78919a-e553-4bab-b73b-10cf4d2eb125') @testtools.skipUnless(CONF.compute_feature_enabled.attach_encrypted_volume, 'Encrypted volume attach is not supported') @utils.services('compute', 'volume') def test_boot_server_from_encrypted_volume_luks(self): # Create an encrypted volume volume = self.create_encrypted_volume('nova.volume.encryptors.' 'luks.LuksEncryptor', volume_type='luks') self.volumes_client.set_bootable_volume(volume['id'], bootable=True) # Boot a server from the encrypted volume server = self._boot_instance_from_resource( source_id=volume['id'], source_type='volume', delete_on_termination=False) server_info = self.servers_client.show_server(server['id'])['server'] created_volume = server_info['os-extended-volumes:volumes_attached'] self.assertEqual(volume['id'], created_volume[0]['id']) tempest-17.2.0/tempest/api/0000775000175100017510000000000013207045130015533 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/README.rst0000666000175100017510000000351513207044712017235 0ustar zuulzuul00000000000000.. _api_field_guide: Tempest Field Guide to API tests ================================ What are these tests? --------------------- One of Tempest's prime function is to ensure that your OpenStack cloud works with the OpenStack API as documented. The current largest portion of Tempest code is devoted to test cases that do exactly this. It's also important to test not only the expected positive path on APIs, but also to provide them with invalid data to ensure they fail in expected and documented ways. The latter type of tests is called ``negative tests`` in Tempest source code. Over the course of the OpenStack project Tempest has discovered many fundamental bugs by doing just this. In order for some APIs to return meaningful results, there must be enough data in the system. This means these tests might start by spinning up a server, image, etc, then operating on it. Why are these tests in Tempest? ------------------------------- This is one of the core missions for the Tempest project, and where it started. Many people use this bit of function in Tempest to ensure their clouds haven't broken the OpenStack API. It could be argued that some of the negative testing could be done back in the projects themselves, and we might evolve there over time, but currently in the OpenStack gate this is a fundamentally important place to keep things. Scope of these tests -------------------- API tests should always use the Tempest implementation of the OpenStack API, as we want to ensure that bugs aren't hidden by the official clients. They should test specific API calls, and can build up complex state if it's needed for the API call to be meaningful. They should send not only good data, but bad data at the API and look for error codes. They should all be able to be run on their own, not depending on the state created by a previous test. tempest-17.2.0/tempest/api/image/0000775000175100017510000000000013207045130016615 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/image/v1/0000775000175100017510000000000013207045130017143 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/image/v1/test_images_negative.py0000666000175100017510000000723113207044712023715 0ustar zuulzuul00000000000000# 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 tempest.api.image import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest): """Here are negative tests for the deletion and creation of images.""" @decorators.attr(type=['negative']) @decorators.idempotent_id('036ede36-6160-4463-8c01-c781eee6369d') def test_register_with_invalid_container_format(self): # Negative tests for invalid data supplied to POST /images self.assertRaises(lib_exc.BadRequest, self.client.create_image, headers={'x-image-meta-name': 'test', 'x-image-meta-container_format': 'wrong', 'x-image-meta-disk_format': 'vhd'}) @decorators.attr(type=['negative']) @decorators.idempotent_id('993face5-921d-4e84-aabf-c1bba4234a67') def test_register_with_invalid_disk_format(self): self.assertRaises(lib_exc.BadRequest, self.client.create_image, headers={'x-image-meta-name': 'test', 'x-image-meta-container_format': 'bare', 'x-image-meta-disk_format': 'wrong'}) @decorators.attr(type=['negative']) @decorators.idempotent_id('ec652588-7e3c-4b67-a2f2-0fa96f57c8fc') def test_delete_non_existent_image(self): # Return an error while trying to delete a non-existent image non_existent_image_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.delete_image, non_existent_image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('04f72aa3-fcec-45a3-81a3-308ef7cc82bc') def test_delete_image_blank_id(self): # Return an error while trying to delete an image with blank Id self.assertRaises(lib_exc.NotFound, self.client.delete_image, '') @decorators.attr(type=['negative']) @decorators.idempotent_id('950e5054-a3c7-4dee-ada5-e576f1087abd') def test_delete_image_non_hex_string_id(self): # Return an error while trying to delete an image with non hex id invalid_image_id = data_utils.rand_uuid()[:-1] + "j" self.assertRaises(lib_exc.NotFound, self.client.delete_image, invalid_image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('4ed757cd-450c-44b1-9fd1-c819748c650d') def test_delete_image_negative_image_id(self): # Return an error while trying to delete an image with negative id self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1) @decorators.attr(type=['negative']) @decorators.idempotent_id('a4a448ab-3db2-4d2d-b9b2-6a1271241dfe') def test_delete_image_id_over_character_limit(self): # Return an error while trying to delete image with id over limit overlimit_image_id = data_utils.rand_uuid() + "1" self.assertRaises(lib_exc.NotFound, self.client.delete_image, overlimit_image_id) tempest-17.2.0/tempest/api/image/v1/test_image_members_negative.py0000666000175100017510000000502613207044712025244 0ustar zuulzuul00000000000000# 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 tempest.api.image import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ImageMembersNegativeTest(base.BaseV1ImageMembersTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('147a9536-18e3-45da-91ea-b037a028f364') def test_add_member_with_non_existing_image(self): # Add member with non existing image. non_exist_image = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.image_member_client.create_image_member, non_exist_image, self.alt_tenant_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('e1559f05-b667-4f1b-a7af-518b52dc0c0f') def test_delete_member_with_non_existing_image(self): # Delete member with non existing image. non_exist_image = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.image_member_client.delete_image_member, non_exist_image, self.alt_tenant_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('f5720333-dd69-4194-bb76-d2f048addd56') def test_delete_member_with_non_existing_tenant(self): # Delete member with non existing tenant. image_id = self._create_image() non_exist_tenant = data_utils.rand_uuid_hex() self.assertRaises(lib_exc.NotFound, self.image_member_client.delete_image_member, image_id, non_exist_tenant) @decorators.attr(type=['negative']) @decorators.idempotent_id('f25f89e4-0b6c-453b-a853-1f80b9d7ef26') def test_get_image_without_membership(self): # Image is hidden from another tenants. image_id = self._create_image() self.assertRaises(lib_exc.NotFound, self.alt_img_cli.show_image, image_id) tempest-17.2.0/tempest/api/image/v1/test_image_members.py0000666000175100017510000000506113207044712023361 0ustar zuulzuul00000000000000# 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 tempest.api.image import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ImageMembersTest(base.BaseV1ImageMembersTest): @decorators.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad') def test_add_image_member(self): image = self._create_image() self.image_member_client.create_image_member(image, self.alt_tenant_id) body = self.image_member_client.list_image_members(image) members = body['members'] members = [member['member_id'] for member in members] self.assertIn(self.alt_tenant_id, members) # get image as alt user self.alt_img_cli.show_image(image) @decorators.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9') def test_get_shared_images(self): image = self._create_image() self.image_member_client.create_image_member(image, self.alt_tenant_id) share_image = self._create_image() self.image_member_client.create_image_member(share_image, self.alt_tenant_id) body = self.image_member_client.list_shared_images( self.alt_tenant_id) images = body['shared_images'] images = [img['image_id'] for img in images] self.assertIn(share_image, images) self.assertIn(image, images) @decorators.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138') def test_remove_member(self): image_id = self._create_image() self.image_member_client.create_image_member(image_id, self.alt_tenant_id) self.image_member_client.delete_image_member(image_id, self.alt_tenant_id) body = self.image_member_client.list_image_members(image_id) members = body['members'] self.assertEmpty(members) self.assertRaises( lib_exc.NotFound, self.alt_img_cli.show_image, image_id) tempest-17.2.0/tempest/api/image/v1/test_images.py0000666000175100017510000003615713207044712022044 0ustar zuulzuul00000000000000# 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 six from tempest.api.image import base from tempest.common import image as common_image from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF def get_container_and_disk_format(): a_formats = ['ami', 'ari', 'aki'] container_format = CONF.image.container_formats[0] # In v1, If container_format is one of ['ami', 'ari', 'aki'], then # disk_format must be same with container_format. # If they are of different item sequence in tempest.conf, such as: # container_formats = ami,ari,aki,bare # disk_formats = ari,ami,aki,vhd # we can select one in disk_format list that is same with container_format. if container_format in a_formats: if container_format in CONF.image.disk_formats: disk_format = container_format else: msg = ("The container format and the disk format don't match. " "Container format: %(container)s, Disk format: %(disk)s." % {'container': container_format, 'disk': CONF.image.disk_formats}) raise exceptions.InvalidConfiguration(msg) else: disk_format = CONF.image.disk_formats[0] return container_format, disk_format class CreateRegisterImagesTest(base.BaseV1ImageTest): """Here we test the registration and creation of images.""" @decorators.idempotent_id('3027f8e6-3492-4a11-8575-c3293017af4d') def test_register_then_upload(self): # Register, then upload an image properties = {'prop1': 'val1'} container_format, disk_format = get_container_and_disk_format() image = self.create_image(name='New Name', container_format=container_format, disk_format=disk_format, is_public=False, properties=properties) self.assertEqual('New Name', image.get('name')) self.assertFalse(image.get('is_public')) self.assertEqual('queued', image.get('status')) for key, val in properties.items(): self.assertEqual(val, image.get('properties')[key]) # Now try uploading an image file image_file = six.BytesIO(data_utils.random_bytes()) body = self.client.update_image(image['id'], data=image_file)['image'] self.assertIn('size', body) self.assertEqual(1024, body.get('size')) @decorators.idempotent_id('69da74d9-68a9-404b-9664-ff7164ccb0f5') def test_register_remote_image(self): # Register a new remote image container_format, disk_format = get_container_and_disk_format() body = self.create_image(name='New Remote Image', container_format=container_format, disk_format=disk_format, is_public=False, location=CONF.image.http_image, properties={'key1': 'value1', 'key2': 'value2'}) self.assertEqual('New Remote Image', body.get('name')) self.assertFalse(body.get('is_public')) self.assertEqual('active', body.get('status')) properties = body.get('properties') self.assertEqual(properties['key1'], 'value1') self.assertEqual(properties['key2'], 'value2') @decorators.idempotent_id('6d0e13a7-515b-460c-b91f-9f4793f09816') def test_register_http_image(self): container_format, disk_format = get_container_and_disk_format() image = self.create_image(name='New Http Image', container_format=container_format, disk_format=disk_format, is_public=False, copy_from=CONF.image.http_image) self.assertEqual('New Http Image', image.get('name')) self.assertFalse(image.get('is_public')) waiters.wait_for_image_status(self.client, image['id'], 'active') self.client.show_image(image['id']) @decorators.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b') def test_register_image_with_min_ram(self): # Register an image with min ram container_format, disk_format = get_container_and_disk_format() properties = {'prop1': 'val1'} body = self.create_image(name='New_image_with_min_ram', container_format=container_format, disk_format=disk_format, is_public=False, min_ram=40, properties=properties) self.assertEqual('New_image_with_min_ram', body.get('name')) self.assertFalse(body.get('is_public')) self.assertEqual('queued', body.get('status')) self.assertEqual(40, body.get('min_ram')) for key, val in properties.items(): self.assertEqual(val, body.get('properties')[key]) self.client.delete_image(body['id']) class ListImagesTest(base.BaseV1ImageTest): """Here we test the listing of image information""" @classmethod def skip_checks(cls): super(ListImagesTest, cls).skip_checks() if (len(CONF.image.container_formats) < 2 or len(CONF.image.disk_formats) < 2): skip_msg = ("%s skipped as multiple container formats " "or disk formats are not available." % cls.__name__) raise cls.skipException(skip_msg) @classmethod def resource_setup(cls): super(ListImagesTest, cls).resource_setup() # We add a few images here to test the listing functionality of # the images API a_formats = ['ami', 'ari', 'aki'] (cls.container_format, container_format_alt) = CONF.image.container_formats[:2] cls.disk_format, cls.disk_format_alt = CONF.image.disk_formats[:2] if cls.container_format in a_formats: cls.disk_format = cls.container_format if container_format_alt in a_formats: cls.disk_format_alt = container_format_alt img1 = cls._create_remote_image('one', cls.container_format, cls.disk_format) img2 = cls._create_remote_image('two', container_format_alt, cls.disk_format_alt) img3 = cls._create_remote_image('dup', cls.container_format, cls.disk_format) img4 = cls._create_remote_image('dup', cls.container_format, cls.disk_format) img5 = cls._create_standard_image('1', container_format_alt, cls.disk_format_alt, 42) img6 = cls._create_standard_image('2', container_format_alt, cls.disk_format_alt, 142) img7 = cls._create_standard_image('33', cls.container_format, cls.disk_format, 142) img8 = cls._create_standard_image('33', cls.container_format, cls.disk_format, 142) cls.created_set = set(cls.created_images) # same container format cls.same_container_format_set = set((img1, img3, img4, img7, img8)) # same disk format cls.same_disk_format_set = set((img2, img5, img6)) # 1x with size 42 cls.size42_set = set((img5,)) # 3x with size 142 cls.size142_set = set((img6, img7, img8,)) # dup named cls.dup_set = set((img3, img4)) @classmethod def _create_remote_image(cls, name, container_format, disk_format): """Create a new remote image and return newly-registered image-id""" name = 'New Remote Image %s' % name location = CONF.image.http_image image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=False, location=location) return image['id'] @classmethod def _create_standard_image(cls, name, container_format, disk_format, size): """Create a new standard image and return newly-registered image-id Note that the size of the new image is a random number between 1024 and 4096 """ image_file = six.BytesIO(data_utils.random_bytes(size)) name = 'New Standard Image %s' % name image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=False, data=image_file) return image['id'] @decorators.idempotent_id('246178ab-3b33-4212-9a4b-a7fe8261794d') def test_index_no_params(self): # Simple test to see all fixture images returned images_list = self.client.list_images()['images'] image_list = [image['id'] for image in images_list] for image_id in self.created_images: self.assertIn(image_id, image_list) @decorators.idempotent_id('f1755589-63d6-4468-b098-589820eb4031') def test_index_disk_format(self): images_list = self.client.list_images( disk_format=self.disk_format_alt)['images'] for image in images_list: self.assertEqual(image['disk_format'], self.disk_format_alt) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.same_disk_format_set <= result_set) self.assertFalse(self.created_set - self.same_disk_format_set <= result_set) @decorators.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b') def test_index_container_format(self): images_list = self.client.list_images( container_format=self.container_format)['images'] for image in images_list: self.assertEqual(image['container_format'], self.container_format) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.same_container_format_set <= result_set) self.assertFalse(self.created_set - self.same_container_format_set <= result_set) @decorators.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14') def test_index_max_size(self): images_list = self.client.list_images(size_max=42)['images'] for image in images_list: self.assertLessEqual(image['size'], 42) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.size42_set <= result_set) self.assertFalse(self.created_set - self.size42_set <= result_set) @decorators.idempotent_id('6ffc16d0-4cbf-4401-95c8-4ac63eac34d8') def test_index_min_size(self): images_list = self.client.list_images(size_min=142)['images'] for image in images_list: self.assertGreaterEqual(image['size'], 142) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.size142_set <= result_set) self.assertFalse(self.size42_set <= result_set) @decorators.idempotent_id('e5dc26d9-9aa2-48dd-bda5-748e1445da98') def test_index_status_active_detail(self): images_list = self.client.list_images(detail=True, status='active', sort_key='size', sort_dir='desc')['images'] top_size = images_list[0]['size'] # We have non-zero sized images for image in images_list: size = image['size'] self.assertLessEqual(size, top_size) top_size = size self.assertEqual(image['status'], 'active') @decorators.idempotent_id('097af10a-bae8-4342-bff4-edf89969ed2a') def test_index_name(self): images_list = self.client.list_images( detail=True, name='New Remote Image dup')['images'] result_set = set(map(lambda x: x['id'], images_list)) for image in images_list: self.assertEqual(image['name'], 'New Remote Image dup') self.assertTrue(self.dup_set <= result_set) self.assertFalse(self.created_set - self.dup_set <= result_set) class UpdateImageMetaTest(base.BaseV1ImageTest): @classmethod def resource_setup(cls): super(UpdateImageMetaTest, cls).resource_setup() container_format, disk_format = get_container_and_disk_format() cls.image_id = cls._create_standard_image('1', container_format, disk_format, 42) @classmethod def _create_standard_image(cls, name, container_format, disk_format, size): """Create a new standard image and return newly-registered image-id""" image_file = six.BytesIO(data_utils.random_bytes(size)) name = 'New Standard Image %s' % name image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=False, data=image_file, properties={'key1': 'value1'}) return image['id'] @decorators.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928') def test_list_image_metadata(self): # All metadata key/value pairs for an image should be returned resp = self.client.check_image(self.image_id) resp_metadata = common_image.get_image_meta_from_headers(resp) expected = {'key1': 'value1'} self.assertEqual(expected, resp_metadata['properties']) @decorators.idempotent_id('d6d7649c-08ce-440d-9ea7-e3dda552f33c') def test_update_image_metadata(self): # The metadata for the image should match the updated values req_metadata = {'key1': 'alt1', 'key2': 'value2'} resp = self.client.check_image(self.image_id) metadata = common_image.get_image_meta_from_headers(resp) self.assertEqual(metadata['properties'], {'key1': 'value1'}) metadata['properties'].update(req_metadata) headers = common_image.image_meta_to_headers( properties=metadata['properties']) self.client.update_image(self.image_id, headers=headers) resp = self.client.check_image(self.image_id) resp_metadata = common_image.get_image_meta_from_headers(resp) self.assertEqual(req_metadata, resp_metadata['properties']) tempest-17.2.0/tempest/api/image/v1/__init__.py0000666000175100017510000000000013207044712021251 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/image/__init__.py0000666000175100017510000000000013207044712020723 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/image/base.py0000666000175100017510000001623613207044712020120 0ustar zuulzuul00000000000000# 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 six from tempest.common import image as common_image from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils import tempest.test CONF = config.CONF class BaseImageTest(tempest.test.BaseTestCase): """Base test class for Image API tests.""" credentials = ['primary'] @classmethod def skip_checks(cls): super(BaseImageTest, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_credentials(cls): cls.set_network_resources() super(BaseImageTest, cls).setup_credentials() @classmethod def resource_setup(cls): super(BaseImageTest, cls).resource_setup() cls.created_images = [] @classmethod def create_image(cls, data=None, **kwargs): """Wrapper that returns a test image.""" if 'name' not in kwargs: name = data_utils.rand_name(cls.__name__ + "-image") kwargs['name'] = name params = cls._get_create_params(**kwargs) if data: # NOTE: On glance v1 API, the data should be passed on # a header. Then here handles the data separately. params['data'] = data image = cls.client.create_image(**params) # Image objects returned by the v1 client have the image # data inside a dict that is keyed against 'image'. if 'image' in image: image = image['image'] cls.created_images.append(image['id']) cls.addClassResourceCleanup(cls.client.wait_for_resource_deletion, image['id']) cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.client.delete_image, image['id']) return image @classmethod def _get_create_params(cls, **kwargs): return kwargs class BaseV1ImageTest(BaseImageTest): @classmethod def skip_checks(cls): super(BaseV1ImageTest, cls).skip_checks() if not CONF.image_feature_enabled.api_v1: msg = "Glance API v1 not supported" raise cls.skipException(msg) @classmethod def setup_clients(cls): super(BaseV1ImageTest, cls).setup_clients() cls.client = cls.os_primary.image_client @classmethod def _get_create_params(cls, **kwargs): return {'headers': common_image.image_meta_to_headers(**kwargs)} class BaseV1ImageMembersTest(BaseV1ImageTest): credentials = ['primary', 'alt'] @classmethod def setup_clients(cls): super(BaseV1ImageMembersTest, cls).setup_clients() cls.image_member_client = cls.os_primary.image_member_client cls.alt_image_member_client = cls.os_alt.image_member_client cls.alt_img_cli = cls.os_alt.image_client @classmethod def resource_setup(cls): super(BaseV1ImageMembersTest, cls).resource_setup() cls.alt_tenant_id = cls.alt_image_member_client.tenant_id def _create_image(self): image_file = six.BytesIO(data_utils.random_bytes()) image = self.create_image(container_format='bare', disk_format='raw', is_public=False, data=image_file) return image['id'] class BaseV2ImageTest(BaseImageTest): @classmethod def skip_checks(cls): super(BaseV2ImageTest, cls).skip_checks() if not CONF.image_feature_enabled.api_v2: msg = "Glance API v2 not supported" raise cls.skipException(msg) @classmethod def setup_clients(cls): super(BaseV2ImageTest, cls).setup_clients() cls.client = cls.os_primary.image_client_v2 cls.namespaces_client = cls.os_primary.namespaces_client cls.resource_types_client = cls.os_primary.resource_types_client cls.namespace_properties_client =\ cls.os_primary.namespace_properties_client cls.namespace_objects_client = cls.os_primary.namespace_objects_client cls.namespace_tags_client = cls.os_primary.namespace_tags_client cls.schemas_client = cls.os_primary.schemas_client cls.versions_client = cls.os_primary.image_versions_client def create_namespace(cls, namespace_name=None, visibility='public', description='Tempest', protected=False, **kwargs): if not namespace_name: namespace_name = data_utils.rand_name('test-ns') kwargs.setdefault('display_name', namespace_name) namespace = cls.namespaces_client.create_namespace( namespace=namespace_name, visibility=visibility, description=description, protected=protected, **kwargs) cls.addCleanup(cls.namespaces_client.delete_namespace, namespace_name) return namespace class BaseV2MemberImageTest(BaseV2ImageTest): credentials = ['primary', 'alt'] @classmethod def setup_clients(cls): super(BaseV2MemberImageTest, cls).setup_clients() cls.image_member_client = cls.os_primary.image_member_client_v2 cls.alt_image_member_client = cls.os_alt.image_member_client_v2 cls.alt_img_client = cls.os_alt.image_client_v2 @classmethod def resource_setup(cls): super(BaseV2MemberImageTest, cls).resource_setup() cls.alt_tenant_id = cls.alt_image_member_client.tenant_id def _list_image_ids_as_alt(self): image_list = self.alt_img_client.list_images()['images'] image_ids = map(lambda x: x['id'], image_list) return image_ids def _create_image(self): name = data_utils.rand_name(self.__class__.__name__ + '-image') image = self.client.create_image(name=name, container_format='bare', disk_format='raw') self.addCleanup(self.client.delete_image, image['id']) return image['id'] class BaseV1ImageAdminTest(BaseImageTest): credentials = ['admin', 'primary'] @classmethod def setup_clients(cls): super(BaseV1ImageAdminTest, cls).setup_clients() cls.client = cls.os_primary.image_client cls.admin_client = cls.os_admin.image_client class BaseV2ImageAdminTest(BaseImageTest): credentials = ['admin', 'primary'] @classmethod def setup_clients(cls): super(BaseV2ImageAdminTest, cls).setup_clients() cls.client = cls.os_primary.image_client_v2 cls.admin_client = cls.os_admin.image_client_v2 tempest-17.2.0/tempest/api/image/v2/0000775000175100017510000000000013207045130017144 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/image/v2/test_images_negative.py0000666000175100017510000001103513207044712023713 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # 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 tempest.api.image import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ImagesNegativeTest(base.BaseV2ImageTest): """here we have -ve tests for show_image and delete_image api Tests ** get non-existent image ** get image with image_id=NULL ** get the deleted image ** delete non-existent image ** delete image with image_id=NULL ** delete the deleted image """ @decorators.attr(type=['negative']) @decorators.idempotent_id('668743d5-08ad-4480-b2b8-15da34f81d9f') def test_get_non_existent_image(self): # get the non-existent image non_existent_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.show_image, non_existent_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('ef45000d-0a72-4781-866d-4cb7bf2562ad') def test_get_image_null_id(self): # get image with image_id = NULL image_id = "" self.assertRaises(lib_exc.NotFound, self.client.show_image, image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('e57fc127-7ba0-4693-92d7-1d8a05ebcba9') def test_get_delete_deleted_image(self): # get and delete the deleted image # create and delete image image = self.client.create_image(name='test', container_format='bare', disk_format='raw') self.client.delete_image(image['id']) self.client.wait_for_resource_deletion(image['id']) # get the deleted image self.assertRaises(lib_exc.NotFound, self.client.show_image, image['id']) # delete the deleted image self.assertRaises(lib_exc.NotFound, self.client.delete_image, image['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('6fe40f1c-57bd-4918-89cc-8500f850f3de') def test_delete_non_existing_image(self): # delete non-existent image non_existent_image_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.delete_image, non_existent_image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('32248db1-ab88-4821-9604-c7c369f1f88c') def test_delete_image_null_id(self): # delete image with image_id=NULL image_id = "" self.assertRaises(lib_exc.NotFound, self.client.delete_image, image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('292bd310-369b-41c7-a7a3-10276ef76753') def test_register_with_invalid_container_format(self): # Negative tests for invalid data supplied to POST /images self.assertRaises(lib_exc.BadRequest, self.client.create_image, name='test', container_format='wrong', disk_format='vhd') @decorators.attr(type=['negative']) @decorators.idempotent_id('70c6040c-5a97-4111-9e13-e73665264ce1') def test_register_with_invalid_disk_format(self): self.assertRaises(lib_exc.BadRequest, self.client.create_image, name='test', container_format='bare', disk_format='wrong') @decorators.attr(type=['negative']) @decorators.idempotent_id('ab980a34-8410-40eb-872b-f264752f46e5') def test_delete_protected_image(self): # Create a protected image image = self.create_image(protected=True) self.addCleanup(self.client.update_image, image['id'], [dict(replace="/protected", value=False)]) # Try deleting the protected image self.assertRaises(lib_exc.Forbidden, self.client.delete_image, image['id']) tempest-17.2.0/tempest/api/image/v2/test_images_metadefs_namespace_properties.py0000666000175100017510000000556013207044712030177 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.image import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class MetadataNamespacePropertiesTest(base.BaseV2ImageTest): """Test the Metadata definition namespace property basic functionality""" @decorators.idempotent_id('b1a3765e-3a5d-4f6d-a3a7-3ca3476ae768') def test_basic_meta_def_namespace_property(self): # Get the available resource types and use one resource_type body = self.resource_types_client.list_resource_types() resource_name = body['resource_types'][0]['name'] enum = ["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"] # Create a namespace namespace = self.create_namespace() # Create resource type association body = self.resource_types_client.create_resource_type_association( namespace['namespace'], name=resource_name) # Create a property property_title = data_utils.rand_name('property') body = self.namespace_properties_client.create_namespace_property( namespace=namespace['namespace'], title=property_title, name=resource_name, type="string", enum=enum) self.assertEqual(property_title, body['title']) # Show namespace property body = self.namespace_properties_client.show_namespace_properties( namespace['namespace'], resource_name) self.assertEqual(resource_name, body['name']) # Update namespace property update_property_title = data_utils.rand_name('update-property') body = self.namespace_properties_client.update_namespace_properties( namespace['namespace'], resource_name, title=update_property_title, type="string", enum=enum, name=resource_name) self.assertEqual(update_property_title, body['title']) # Delete namespace property self.namespace_properties_client.delete_namespace_property( namespace['namespace'], resource_name) # List namespace properties and validate deletion namespace_property = [ namespace_property['title'] for namespace_property in self.namespace_properties_client.list_namespace_properties( namespace['namespace'])['properties']] self.assertNotIn(update_property_title, namespace_property) tempest-17.2.0/tempest/api/image/v2/test_images_metadefs_resource_types.py0000666000175100017510000000526513207044712027044 0ustar zuulzuul00000000000000# Copyright 2016 Ericsson India Global Services Private Limited # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest.lib import decorators class MetadataResourceTypesTest(base.BaseV2ImageTest): """Test the Metadata definition resource types basic functionality""" @decorators.idempotent_id('6f358a4e-5ef0-11e6-a795-080027d0d606') def test_basic_meta_def_resource_type_association(self): # Get the available resource types and use one resource_type body = self.resource_types_client.list_resource_types() resource_name = body['resource_types'][0]['name'] # Create a namespace namespace = self.create_namespace() # Create resource type association body = self.resource_types_client.create_resource_type_association( namespace['namespace'], name=resource_name) self.assertEqual(body['name'], resource_name) # NOTE(raiesmh08): Here intentionally I have not added addcleanup # method for resource type dissociation because its a metadata add and # being cleaned as soon as namespace is cleaned at test case level. # When namespace cleans, resource type association will automatically # clean without any error or dependency. # List resource type associations and validate creation rs_type_associations = [ rs_type_association['name'] for rs_type_association in self.resource_types_client.list_resource_type_association( namespace['namespace'])['resource_type_associations']] self.assertIn(resource_name, rs_type_associations) # Delete resource type association self.resource_types_client.delete_resource_type_association( namespace['namespace'], resource_name) # List resource type associations and validate deletion rs_type_associations = [ rs_type_association['name'] for rs_type_association in self.resource_types_client.list_resource_type_association( namespace['namespace'])['resource_type_associations']] self.assertNotIn(resource_name, rs_type_associations) tempest-17.2.0/tempest/api/image/v2/test_images_member.py0000666000175100017510000001161613207044712023365 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.image import base from tempest.lib import decorators class ImagesMemberTest(base.BaseV2MemberImageTest): @decorators.idempotent_id('5934c6ea-27dc-4d6e-9421-eeb5e045494a') def test_image_share_accept(self): image_id = self._create_image() member = self.image_member_client.create_image_member( image_id, member=self.alt_tenant_id) self.addCleanup(self.image_member_client.delete_image_member, image_id, self.alt_tenant_id) self.assertEqual(member['member_id'], self.alt_tenant_id) self.assertEqual(member['image_id'], image_id) self.assertEqual(member['status'], 'pending') self.assertNotIn(image_id, self._list_image_ids_as_alt()) self.alt_image_member_client.update_image_member(image_id, self.alt_tenant_id, status='accepted') self.assertIn(image_id, self._list_image_ids_as_alt()) body = self.image_member_client.list_image_members(image_id) members = body['members'] member = members[0] self.assertEqual(len(members), 1, str(members)) self.assertEqual(member['member_id'], self.alt_tenant_id) self.assertEqual(member['image_id'], image_id) self.assertEqual(member['status'], 'accepted') @decorators.idempotent_id('d9e83e5f-3524-4b38-a900-22abcb26e90e') def test_image_share_reject(self): image_id = self._create_image() member = self.image_member_client.create_image_member( image_id, member=self.alt_tenant_id) self.addCleanup(self.image_member_client.delete_image_member, image_id, self.alt_tenant_id) self.assertEqual(member['member_id'], self.alt_tenant_id) self.assertEqual(member['image_id'], image_id) self.assertEqual(member['status'], 'pending') self.assertNotIn(image_id, self._list_image_ids_as_alt()) self.alt_image_member_client.update_image_member(image_id, self.alt_tenant_id, status='rejected') self.assertNotIn(image_id, self._list_image_ids_as_alt()) @decorators.idempotent_id('a6ee18b9-4378-465e-9ad9-9a6de58a3287') def test_get_image_member(self): image_id = self._create_image() self.image_member_client.create_image_member( image_id, member=self.alt_tenant_id) self.addCleanup(self.image_member_client.delete_image_member, image_id, self.alt_tenant_id) self.alt_image_member_client.update_image_member(image_id, self.alt_tenant_id, status='accepted') self.assertIn(image_id, self._list_image_ids_as_alt()) member = self.image_member_client.show_image_member( image_id, self.alt_tenant_id) self.assertEqual(self.alt_tenant_id, member['member_id']) self.assertEqual(image_id, member['image_id']) self.assertEqual('accepted', member['status']) @decorators.idempotent_id('72989bc7-2268-48ed-af22-8821e835c914') def test_remove_image_member(self): image_id = self._create_image() self.image_member_client.create_image_member( image_id, member=self.alt_tenant_id) self.alt_image_member_client.update_image_member(image_id, self.alt_tenant_id, status='accepted') self.assertIn(image_id, self._list_image_ids_as_alt()) self.image_member_client.delete_image_member(image_id, self.alt_tenant_id) self.assertNotIn(image_id, self._list_image_ids_as_alt()) @decorators.idempotent_id('634dcc3f-f6e2-4409-b8fd-354a0bb25d83') def test_get_image_member_schema(self): body = self.schemas_client.show_schema("member") self.assertEqual("member", body['name']) @decorators.idempotent_id('6ae916ef-1052-4e11-8d36-b3ae14853cbb') def test_get_image_members_schema(self): body = self.schemas_client.show_schema("members") self.assertEqual("members", body['name']) tempest-17.2.0/tempest/api/image/v2/test_images_metadefs_namespaces.py0000666000175100017510000000657613207044712026116 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class MetadataNamespacesTest(base.BaseV2ImageTest): """Test the Metadata definition Namespaces basic functionality""" @decorators.idempotent_id('319b765e-7f3d-4b3d-8b37-3ca3876ee768') def test_basic_metadata_definition_namespaces(self): # get the available resource types and use one resource_type body = self.resource_types_client.list_resource_types() resource_name = body['resource_types'][0]['name'] name = [{'name': resource_name}] namespace_name = data_utils.rand_name('namespace') # create the metadef namespace body = self.namespaces_client.create_namespace( namespace=namespace_name, visibility='public', description='Tempest', display_name=namespace_name, resource_type_associations=name, protected=True) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self._cleanup_namespace, namespace_name) # list namespaces bodys = self.namespaces_client.list_namespaces()['namespaces'] body = [namespace['namespace'] for namespace in bodys] self.assertIn(namespace_name, body) # get namespace details body = self.namespaces_client.show_namespace(namespace_name) self.assertEqual(namespace_name, body['namespace']) self.assertEqual('public', body['visibility']) # unable to delete protected namespace self.assertRaises(lib_exc.Forbidden, self.namespaces_client.delete_namespace, namespace_name) # update the visibility to private and protected to False body = self.namespaces_client.update_namespace( namespace=namespace_name, description='Tempest', visibility='private', display_name=namespace_name, protected=False) self.assertEqual('private', body['visibility']) self.assertEqual(False, body['protected']) # now able to delete the non-protected namespace self.namespaces_client.delete_namespace(namespace_name) def _cleanup_namespace(self, namespace_name): body = self.namespaces_client.show_namespace(namespace_name) self.assertEqual(namespace_name, body['namespace']) body = self.namespaces_client.update_namespace( namespace=namespace_name, description='Tempest', visibility='private', display_name=namespace_name, protected=False) self.namespaces_client.delete_namespace(namespace_name) tempest-17.2.0/tempest/api/image/v2/test_images.py0000666000175100017510000004026113207044712022034 0ustar zuulzuul00000000000000# Copyright 2013 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 random import six import testtools from oslo_log import log as logging from tempest.api.image import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF LOG = logging.getLogger(__name__) class BasicOperationsImagesTest(base.BaseV2ImageTest): """Here we test the basic operations of images""" @decorators.attr(type='smoke') @decorators.idempotent_id('139b765e-7f3d-4b3d-8b37-3ca3876ee318') def test_register_upload_get_image_file(self): """Here we test these functionalities Register image, upload the image file, get image and get image file api's """ uuid = '00000000-1111-2222-3333-444455556666' image_name = data_utils.rand_name('image') container_format = CONF.image.container_formats[0] disk_format = CONF.image.disk_formats[0] image = self.create_image(name=image_name, container_format=container_format, disk_format=disk_format, visibility='private', ramdisk_id=uuid) self.assertIn('name', image) self.assertEqual(image_name, image['name']) self.assertIn('visibility', image) self.assertEqual('private', image['visibility']) self.assertIn('status', image) self.assertEqual('queued', image['status']) # Now try uploading an image file file_content = data_utils.random_bytes() image_file = six.BytesIO(file_content) self.client.store_image_file(image['id'], image_file) # Now try to get image details body = self.client.show_image(image['id']) self.assertEqual(image['id'], body['id']) self.assertEqual(image_name, body['name']) self.assertEqual(uuid, body['ramdisk_id']) self.assertIn('size', body) self.assertEqual(1024, body.get('size')) # Now try get image file body = self.client.show_image_file(image['id']) self.assertEqual(file_content, body.data) @decorators.attr(type='smoke') @decorators.idempotent_id('f848bb94-1c6e-45a4-8726-39e3a5b23535') def test_delete_image(self): # Deletes an image by image_id # Create image image_name = data_utils.rand_name('image') container_format = CONF.image.container_formats[0] disk_format = CONF.image.disk_formats[0] image = self.create_image(name=image_name, container_format=container_format, disk_format=disk_format, visibility='private') # Delete Image self.client.delete_image(image['id']) self.client.wait_for_resource_deletion(image['id']) # Verifying deletion images = self.client.list_images()['images'] images_id = [item['id'] for item in images] self.assertNotIn(image['id'], images_id) @decorators.attr(type='smoke') @decorators.idempotent_id('f66891a7-a35c-41a8-b590-a065c2a1caa6') def test_update_image(self): # Updates an image by image_id # Create image image_name = data_utils.rand_name('image') container_format = CONF.image.container_formats[0] disk_format = CONF.image.disk_formats[0] image = self.create_image(name=image_name, container_format=container_format, disk_format=disk_format, visibility='private') self.assertEqual('queued', image['status']) # Now try uploading an image file image_file = six.BytesIO(data_utils.random_bytes()) self.client.store_image_file(image['id'], image_file) # Update Image new_image_name = data_utils.rand_name('new-image') self.client.update_image(image['id'], [ dict(replace='/name', value=new_image_name)]) # Verifying updating body = self.client.show_image(image['id']) self.assertEqual(image['id'], body['id']) self.assertEqual(new_image_name, body['name']) @testtools.skipUnless(CONF.image_feature_enabled.deactivate_image, 'deactivate-image is not available.') @decorators.idempotent_id('951ebe01-969f-4ea9-9898-8a3f1f442ab0') def test_deactivate_reactivate_image(self): # Create image image_name = data_utils.rand_name('image') image = self.create_image(name=image_name, container_format='bare', disk_format='raw', visibility='private') # Upload an image file content = data_utils.random_bytes() image_file = six.BytesIO(content) self.client.store_image_file(image['id'], image_file) # Deactivate image self.client.deactivate_image(image['id']) body = self.client.show_image(image['id']) self.assertEqual("deactivated", body['status']) # User unable to download deactivated image self.assertRaises(lib_exc.Forbidden, self.client.show_image_file, image['id']) # Reactivate image self.client.reactivate_image(image['id']) body = self.client.show_image(image['id']) self.assertEqual("active", body['status']) # User able to download image after reactivation body = self.client.show_image_file(image['id']) self.assertEqual(content, body.data) class ListUserImagesTest(base.BaseV2ImageTest): """Here we test the listing of image information""" @classmethod def resource_setup(cls): super(ListUserImagesTest, cls).resource_setup() # We add a few images here to test the listing functionality of # the images API container_fmts = CONF.image.container_formats disk_fmts = CONF.image.disk_formats all_pairs = [(container_fmt, disk_fmt) for container_fmt in container_fmts for disk_fmt in disk_fmts] for (container_fmt, disk_fmt) in all_pairs[:6]: LOG.debug("Creating an image" "(Container format: %s, Disk format: %s).", container_fmt, disk_fmt) cls._create_standard_image(container_fmt, disk_fmt) @classmethod def _create_standard_image(cls, container_format, disk_format): """Create a new standard image and return the newly-registered image-id Note that the size of the new image is a random number between 1024 and 4096 """ size = random.randint(1024, 4096) image_file = six.BytesIO(data_utils.random_bytes(size)) tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')] image = cls.create_image(container_format=container_format, disk_format=disk_format, visibility='private', tags=tags) cls.client.store_image_file(image['id'], data=image_file) # Keep the data of one test image so it can be used to filter lists cls.test_data = image return image['id'] def _list_by_param_value_and_assert(self, params): """Perform list action with given params and validates result.""" # Retrieve the list of images that meet the filter images_list = self.client.list_images(params=params)['images'] # Validating params of fetched images msg = 'No images were found that met the filter criteria.' self.assertNotEmpty(images_list, msg) for image in images_list: for key in params: msg = "Failed to list images by %s" % key self.assertEqual(params[key], image[key], msg) def _list_sorted_by_image_size_and_assert(self, params, desc=False): """Validate an image list that has been sorted by size Perform list action with given params and validates the results are sorted by image size in either ascending or descending order. """ # Retrieve the list of images that meet the filter images_list = self.client.list_images(params=params)['images'] # Validate that the list was fetched sorted accordingly msg = 'No images were found that met the filter criteria.' self.assertNotEmpty(images_list, msg) sorted_list = [image['size'] for image in images_list] msg = 'The list of images was not sorted correctly.' self.assertEqual(sorted(sorted_list, reverse=desc), sorted_list, msg) @decorators.idempotent_id('1e341d7a-90a9-494c-b143-2cdf2aeb6aee') def test_list_no_params(self): # Simple test to see all fixture images returned images_list = self.client.list_images()['images'] image_list = [image['id'] for image in images_list] for image in self.created_images: self.assertIn(image, image_list) @decorators.idempotent_id('9959ca1d-1aa7-4b7a-a1ea-0fff0499b37e') def test_list_images_param_container_format(self): # Test to get all images with a specific container_format params = {"container_format": self.test_data['container_format']} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('4a4735a7-f22f-49b6-b0d9-66e1ef7453eb') def test_list_images_param_disk_format(self): # Test to get all images with disk_format = raw params = {"disk_format": "raw"} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('7a95bb92-d99e-4b12-9718-7bc6ab73e6d2') def test_list_images_param_visibility(self): # Test to get all images with visibility = private params = {"visibility": "private"} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('cf1b9a48-8340-480e-af7b-fe7e17690876') def test_list_images_param_size(self): # Test to get all images by size image_id = self.created_images[0] # Get image metadata image = self.client.show_image(image_id) params = {"size": image['size']} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('4ad8c157-971a-4ba8-aa84-ed61154b1e7f') def test_list_images_param_min_max_size(self): # Test to get all images with size between 2000 to 3000 image_id = self.created_images[0] # Get image metadata image = self.client.show_image(image_id) size = image['size'] params = {"size_min": size - 500, "size_max": size + 500} images_list = self.client.list_images(params=params)['images'] image_size_list = map(lambda x: x['size'], images_list) for image_size in image_size_list: self.assertGreaterEqual(image_size, params['size_min'], "Failed to get images by size_min") self.assertLessEqual(image_size, params['size_max'], "Failed to get images by size_max") @decorators.idempotent_id('7fc9e369-0f58-4d05-9aa5-0969e2d59d15') def test_list_images_param_status(self): # Test to get all active images params = {"status": "active"} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('e914a891-3cc8-4b40-ad32-e0a39ffbddbb') def test_list_images_param_limit(self): # Test to get images by limit params = {"limit": 1} images_list = self.client.list_images(params=params)['images'] self.assertEqual(len(images_list), params['limit'], "Failed to get images by limit") @decorators.idempotent_id('e9a44b91-31c8-4b40-a332-e0a39ffb4dbb') def test_list_image_param_owner(self): # Test to get images by owner image_id = self.created_images[0] # Get image metadata image = self.client.show_image(image_id) params = {"owner": image['owner']} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('55c8f5f5-bfed-409d-a6d5-4caeda985d7b') def test_list_images_param_name(self): # Test to get images by name params = {'name': self.test_data['name']} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('aa8ac4df-cff9-418b-8d0f-dd9c67b072c9') def test_list_images_param_tag(self): # Test to get images matching a tag params = {'tag': self.test_data['tags'][0]} images_list = self.client.list_images(params=params)['images'] # Validating properties of fetched images self.assertNotEmpty(images_list) for image in images_list: msg = ("The image {image_name} does not have the expected tag " "{expected_tag} among its tags: {observerd_tags}." .format(image_name=image['name'], expected_tag=self.test_data['tags'][0], observerd_tags=image['tags'])) self.assertIn(self.test_data['tags'][0], image['tags'], msg) @decorators.idempotent_id('eeadce49-04e0-43b7-aec7-52535d903e7a') def test_list_images_param_sort(self): params = {'sort': 'size:desc'} self._list_sorted_by_image_size_and_assert(params, desc=True) @decorators.idempotent_id('9faaa0c2-c3a5-43e1-8f61-61c54b409a49') def test_list_images_param_sort_key_dir(self): params = {'sort_key': 'size', 'sort_dir': 'desc'} self._list_sorted_by_image_size_and_assert(params, desc=True) @decorators.idempotent_id('622b925c-479f-4736-860d-adeaf13bc371') def test_get_image_schema(self): # Test to get image schema schema = "image" body = self.schemas_client.show_schema(schema) self.assertEqual("image", body['name']) @decorators.idempotent_id('25c8d7b2-df21-460f-87ac-93130bcdc684') def test_get_images_schema(self): # Test to get images schema schema = "images" body = self.schemas_client.show_schema(schema) self.assertEqual("images", body['name']) class ListSharedImagesTest(base.BaseV2ImageTest): """Here we test the listing of a shared image information""" credentials = ['primary', 'alt'] @classmethod def setup_clients(cls): super(ListSharedImagesTest, cls).setup_clients() cls.image_member_client = cls.os_primary.image_member_client_v2 cls.alt_img_client = cls.os_alt.image_client_v2 @decorators.idempotent_id('3fa50be4-8e38-4c02-a8db-7811bb780122') def test_list_images_param_member_status(self): # Create an image to be shared using default visibility image_file = six.BytesIO(data_utils.random_bytes(2048)) container_format = CONF.image.container_formats[0] disk_format = CONF.image.disk_formats[0] image = self.create_image(container_format=container_format, disk_format=disk_format) self.client.store_image_file(image['id'], data=image_file) # Share the image created with the alt user self.image_member_client.create_image_member( image_id=image['id'], member=self.alt_img_client.tenant_id) # As an image consumer you need to provide the member_status parameter # along with the visibility=shared parameter in order for it to show # results params = {'member_status': 'pending', 'visibility': 'shared'} fetched_images = self.alt_img_client.list_images(params)['images'] self.assertEqual(1, len(fetched_images)) self.assertEqual(image['id'], fetched_images[0]['id']) tempest-17.2.0/tempest/api/image/v2/test_versions.py0000666000175100017510000000211113207044712022427 0ustar zuulzuul00000000000000# Copyright 2017 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.api.image import base from tempest.lib import decorators class VersionsTest(base.BaseV2ImageTest): @decorators.idempotent_id('659ea30a-a17c-4317-832c-0f68ed23c31d') @decorators.attr(type='smoke') def test_list_versions(self): versions = self.versions_client.list_versions()['versions'] expected_resources = ('id', 'links', 'status') for version in versions: for res in expected_resources: self.assertIn(res, version) tempest-17.2.0/tempest/api/image/v2/test_images_member_negative.py0000666000175100017510000000376613207044712025256 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.image import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ImagesMemberNegativeTest(base.BaseV2MemberImageTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('b79efb37-820d-4cf0-b54c-308b00cf842c') def test_image_share_invalid_status(self): image_id = self._create_image() member = self.image_member_client.create_image_member( image_id, member=self.alt_tenant_id) self.assertEqual(member['status'], 'pending') self.assertRaises(lib_exc.BadRequest, self.alt_image_member_client.update_image_member, image_id, self.alt_tenant_id, status='notavalidstatus') @decorators.attr(type=['negative']) @decorators.idempotent_id('27002f74-109e-4a37-acd0-f91cd4597967') def test_image_share_owner_cannot_accept(self): image_id = self._create_image() member = self.image_member_client.create_image_member( image_id, member=self.alt_tenant_id) self.assertEqual(member['status'], 'pending') self.assertNotIn(image_id, self._list_image_ids_as_alt()) self.assertRaises(lib_exc.Forbidden, self.image_member_client.update_image_member, image_id, self.alt_tenant_id, status='accepted') self.assertNotIn(image_id, self._list_image_ids_as_alt()) tempest-17.2.0/tempest/api/image/v2/__init__.py0000666000175100017510000000000013207044712021252 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/image/v2/test_images_tags_negative.py0000666000175100017510000000345013207044712024733 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ImagesTagsNegativeTest(base.BaseV2ImageTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('8cd30f82-6f9a-4c6e-8034-c1b51fba43d9') def test_update_tags_for_non_existing_image(self): # Update tag with non existing image. tag = data_utils.rand_name('tag') non_exist_image = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.add_image_tag, non_exist_image, tag) @decorators.attr(type=['negative']) @decorators.idempotent_id('39c023a2-325a-433a-9eea-649bf1414b19') def test_delete_non_existing_tag(self): # Delete non existing tag. image = self.create_image(container_format='bare', disk_format='raw', visibility='private' ) tag = data_utils.rand_name('non-exist-tag') self.addCleanup(self.client.delete_image, image['id']) self.assertRaises(lib_exc.NotFound, self.client.delete_image_tag, image['id'], tag) tempest-17.2.0/tempest/api/image/v2/test_images_metadefs_namespace_objects.py0000666000175100017510000000673213207044712027436 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.image import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators class MetadataNamespaceObjectsTest(base.BaseV2ImageTest): """Test the Metadata definition namespace objects basic functionality""" def _create_namespace_object(self, namespace): object_name = data_utils.rand_name(self.__class__.__name__ + '-object') namespace_object = self.namespace_objects_client.\ create_namespace_object(namespace['namespace'], name=object_name) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.namespace_objects_client.delete_namespace_object, namespace['namespace'], object_name) return namespace_object @decorators.idempotent_id('b1a3775e-3b5c-4f6a-a3b4-1ba3574ae718') def test_create_update_delete_meta_namespace_objects(self): # Create a namespace namespace = self.create_namespace() # Create a namespace object body = self._create_namespace_object(namespace) # Update a namespace object up_object_name = data_utils.rand_name('update-object') body = self.namespace_objects_client.update_namespace_object( namespace['namespace'], body['name'], name=up_object_name) self.assertEqual(up_object_name, body['name']) # Delete a namespace object self.namespace_objects_client.delete_namespace_object( namespace['namespace'], up_object_name) # List namespace objects and validate deletion namespace_objects = [ namespace_object['name'] for namespace_object in self.namespace_objects_client.list_namespace_objects( namespace['namespace'])['objects']] self.assertNotIn(up_object_name, namespace_objects) @decorators.idempotent_id('a2a3615e-3b5c-3f6a-a2b1-1ba3574ae738') def test_list_meta_namespace_objects(self): # Create a namespace object namespace = self.create_namespace() meta_namespace_object = self._create_namespace_object(namespace) # List namespace objects namespace_objects = [ namespace_object['name'] for namespace_object in self.namespace_objects_client.list_namespace_objects( namespace['namespace'])['objects']] self.assertIn(meta_namespace_object['name'], namespace_objects) @decorators.idempotent_id('b1a3674e-3b4c-3f6a-a3b4-1ba3573ca768') def test_show_meta_namespace_objects(self): # Create a namespace object namespace = self.create_namespace() namespace_object = self._create_namespace_object(namespace) # Show a namespace object body = self.namespace_objects_client.show_namespace_object( namespace['namespace'], namespace_object['name']) self.assertEqual(namespace_object['name'], body['name']) tempest-17.2.0/tempest/api/image/v2/test_images_metadefs_namespace_tags.py0000666000175100017510000000730313207044712026736 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.image import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators class MetadataNamespaceTagsTest(base.BaseV2ImageTest): """Test the Metadata definition namespace tags basic functionality""" tags = [ { "name": "sample-tag1" }, { "name": "sample-tag2" }, { "name": "sample-tag3" } ] tag_list = ["sample-tag1", "sample-tag2", "sample-tag3"] def _create_namespace_tags(self, namespace): # Create a namespace namespace_tags = self.namespace_tags_client.create_namespace_tags( namespace['namespace'], tags=self.tags) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.namespace_tags_client.delete_namespace_tags, namespace['namespace']) return namespace_tags @decorators.idempotent_id('a2a3765e-3a6d-4f6d-a3a7-3cc3476aa876') def test_create_list_delete_namespace_tags(self): # Create a namespace namespace = self.create_namespace() self._create_namespace_tags(namespace) # List namespace tags body = self.namespace_tags_client.list_namespace_tags( namespace['namespace']) self.assertTrue(3, len(body['tags'])) self.assertIn(body['tags'][0]['name'], self.tag_list) self.assertIn(body['tags'][1]['name'], self.tag_list) self.assertIn(body['tags'][2]['name'], self.tag_list) # Delete all tag definitions self.namespace_tags_client.delete_namespace_tags( namespace['namespace']) body = self.namespace_tags_client.list_namespace_tags( namespace['namespace']) self.assertEmpty(body['tags']) @decorators.idempotent_id('a2a3765e-1a2c-3f6d-a3a7-3cc3466ab875') def test_create_update_delete_tag(self): # Create a namespace namespace = self.create_namespace() self._create_namespace_tags(namespace) # Create a tag tag_name = data_utils.rand_name('tag_name') self.namespace_tags_client.create_namespace_tag( namespace=namespace['namespace'], tag_name=tag_name) body = self.namespace_tags_client.show_namespace_tag( namespace['namespace'], tag_name) self.assertEqual(tag_name, body['name']) # Update tag definition update_tag_definition = data_utils.rand_name('update-tag') body = self.namespace_tags_client.update_namespace_tag( namespace['namespace'], tag_name=tag_name, name=update_tag_definition) self.assertEqual(update_tag_definition, body['name']) # Delete tag definition self.namespace_tags_client.delete_namespace_tag( namespace['namespace'], update_tag_definition) # List namespace tags and validate deletion namespace_tags = [ namespace_tag['name'] for namespace_tag in self.namespace_tags_client.list_namespace_tags( namespace['namespace'])['tags']] self.assertNotIn(update_tag_definition, namespace_tags) tempest-17.2.0/tempest/api/image/v2/test_images_metadefs_schema.py0000666000175100017510000000701313207044712025222 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest.lib import decorators class MetadataSchemaTest(base.BaseV2ImageTest): """Test to get metadata schema""" @decorators.idempotent_id('e9e44891-3cb8-3b40-a532-e0a39fea3dab') def test_get_metadata_namespace_schema(self): # Test to get namespace schema body = self.schemas_client.show_schema("metadefs/namespace") self.assertEqual("namespace", body['name']) @decorators.idempotent_id('ffe44891-678b-3ba0-a3e2-e0a3967b3aeb') def test_get_metadata_namespaces_schema(self): # Test to get namespaces schema body = self.schemas_client.show_schema("metadefs/namespaces") self.assertEqual("namespaces", body['name']) @decorators.idempotent_id('fde34891-678b-3b40-ae32-e0a3e67b6beb') def test_get_metadata_resource_type_schema(self): # Test to get resource_type schema body = self.schemas_client.show_schema("metadefs/resource_type") self.assertEqual("resource_type_association", body['name']) @decorators.idempotent_id('dfe4a891-b38b-3bf0-a3b2-e03ee67b3a3a') def test_get_metadata_resources_types_schema(self): # Test to get resource_types schema body = self.schemas_client.show_schema("metadefs/resource_types") self.assertEqual("resource_type_associations", body['name']) @decorators.idempotent_id('dff4a891-b38b-3bf0-a3b2-e03ee67b3a3b') def test_get_metadata_object_schema(self): # Test to get object schema body = self.schemas_client.show_schema("metadefs/object") self.assertEqual("object", body['name']) @decorators.idempotent_id('dee4a891-b38b-3bf0-a3b2-e03ee67b3a3c') def test_get_metadata_objects_schema(self): # Test to get objects schema body = self.schemas_client.show_schema("metadefs/objects") self.assertEqual("objects", body['name']) @decorators.idempotent_id('dae4a891-b38b-3bf0-a3b2-e03ee67b3a3d') def test_get_metadata_property_schema(self): # Test to get property schema body = self.schemas_client.show_schema("metadefs/property") self.assertEqual("property", body['name']) @decorators.idempotent_id('dce4a891-b38b-3bf0-a3b2-e03ee67b3a3e') def test_get_metadata_properties_schema(self): # Test to get properties schema body = self.schemas_client.show_schema("metadefs/properties") self.assertEqual("properties", body['name']) @decorators.idempotent_id('dde4a891-b38b-3bf0-a3b2-e03ee67b3a3e') def test_get_metadata_tag_schema(self): # Test to get tag schema body = self.schemas_client.show_schema("metadefs/tag") self.assertEqual("tag", body['name']) @decorators.idempotent_id('cde4a891-b38b-3bf0-a3b2-e03ee67b3a3a') def test_get_metadata_tags_schema(self): # Test to get tags schema body = self.schemas_client.show_schema("metadefs/tags") self.assertEqual("tags", body['name']) tempest-17.2.0/tempest/api/image/v2/test_images_tags.py0000666000175100017510000000274313207044712023055 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ImagesTagsTest(base.BaseV2ImageTest): @decorators.idempotent_id('10407036-6059-4f95-a2cd-cbbbee7ed329') def test_update_delete_tags_for_image(self): image = self.create_image(container_format='bare', disk_format='raw', visibility='private') tag = data_utils.rand_name('tag') self.addCleanup(self.client.delete_image, image['id']) # Creating image tag and verify it. self.client.add_image_tag(image['id'], tag) body = self.client.show_image(image['id']) self.assertIn(tag, body['tags']) # Deleting image tag and verify it. self.client.delete_image_tag(image['id'], tag) body = self.client.show_image(image['id']) self.assertNotIn(tag, body['tags']) tempest-17.2.0/tempest/api/network/0000775000175100017510000000000013207045130017224 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/network/test_tags.py0000666000175100017510000002017213207044712021604 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class TagsTest(base.BaseNetworkTest): """Tests the following operations in the tags API: Update all tags. Delete all tags. Check tag existence. Create a tag. List tags. Remove a tag. v2.0 of the Neutron API is assumed. The tag extension allows users to set tags on their networks. The extension supports networks only. """ @classmethod def skip_checks(cls): super(TagsTest, cls).skip_checks() if not utils.is_extension_enabled('tag', 'network'): msg = "tag extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(TagsTest, cls).resource_setup() cls.network = cls.create_network() @decorators.idempotent_id('ee76bfaf-ac94-4d74-9ecc-4bbd4c583cb1') def test_create_list_show_update_delete_tags(self): # Validate that creating a tag on a network resource works. tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag') self.tags_client.create_tag('networks', self.network['id'], tag_name) self.addCleanup(self.tags_client.delete_all_tags, 'networks', self.network['id']) self.tags_client.check_tag_existence('networks', self.network['id'], tag_name) # Validate that listing tags on a network resource works. retrieved_tags = self.tags_client.list_tags( 'networks', self.network['id'])['tags'] self.assertEqual([tag_name], retrieved_tags) # Generate 3 new tag names. replace_tags = [data_utils.rand_name( self.__class__.__name__ + '-Tag') for _ in range(3)] # Replace the current tag with the 3 new tags and validate that the # network resource has the 3 new tags. updated_tags = self.tags_client.update_all_tags( 'networks', self.network['id'], replace_tags)['tags'] self.assertEqual(3, len(updated_tags)) self.assertEqual(set(replace_tags), set(updated_tags)) # Delete the first tag and check that it has been removed. self.tags_client.delete_tag( 'networks', self.network['id'], replace_tags[0]) self.assertRaises(lib_exc.NotFound, self.tags_client.check_tag_existence, 'networks', self.network['id'], replace_tags[0]) for i in range(1, 3): self.tags_client.check_tag_existence( 'networks', self.network['id'], replace_tags[i]) # Delete all the remaining tags and check that they have been removed. self.tags_client.delete_all_tags('networks', self.network['id']) for i in range(1, 3): self.assertRaises(lib_exc.NotFound, self.tags_client.check_tag_existence, 'networks', self.network['id'], replace_tags[i]) class TagsExtTest(base.BaseNetworkTest): """Tests the following operations in the tags API: Update all tags. Delete all tags. Check tag existence. Create a tag. List tags. Remove a tag. v2.0 of the Neutron API is assumed. The tag-ext extension allows users to set tags on the following resources: subnets, ports, routers and subnetpools. """ # NOTE(felipemonteiro): The supported resource names are plural. Use # the singular case for the corresponding class resource object. SUPPORTED_RESOURCES = ['subnets', 'ports', 'routers', 'subnetpools'] @classmethod def skip_checks(cls): super(TagsExtTest, cls).skip_checks() if not utils.is_extension_enabled('tag-ext', 'network'): msg = "tag-ext extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(TagsExtTest, cls).resource_setup() cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) cls.port = cls.create_port(cls.network) cls.router = cls.create_router() subnetpool_name = data_utils.rand_name(cls.__name__ + '-Subnetpool') prefix = CONF.network.default_network cls.subnetpool = cls.subnetpools_client.create_subnetpool( name=subnetpool_name, prefixes=prefix)['subnetpool'] cls.addClassResourceCleanup(cls.subnetpools_client.delete_subnetpool, cls.subnetpool['id']) def _create_tags_for_each_resource(self): # Create a tag for each resource in `SUPPORTED_RESOURCES` and return # the list of tag names. tag_names = [] for resource in self.SUPPORTED_RESOURCES: tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag') tag_names.append(tag_name) resource_object = getattr(self, resource[:-1]) self.tags_client.create_tag(resource, resource_object['id'], tag_name) self.addCleanup(self.tags_client.delete_all_tags, resource, resource_object['id']) return tag_names @decorators.idempotent_id('c6231efa-9a89-4adf-b050-2a3156b8a1d9') def test_create_check_list_and_delete_tags(self): tag_names = self._create_tags_for_each_resource() for i, resource in enumerate(self.SUPPORTED_RESOURCES): # Ensure that a tag was created for each resource. resource_object = getattr(self, resource[:-1]) retrieved_tags = self.tags_client.list_tags( resource, resource_object['id'])['tags'] self.assertEqual(1, len(retrieved_tags)) self.assertEqual(tag_names[i], retrieved_tags[0]) # Check that the expected tag exists for each resource. self.tags_client.check_tag_existence( resource, resource_object['id'], tag_names[i]) # Delete the tag and ensure it was deleted. self.tags_client.delete_tag( resource, resource_object['id'], tag_names[i]) retrieved_tags = self.tags_client.list_tags( resource, resource_object['id'])['tags'] self.assertEmpty(retrieved_tags) @decorators.idempotent_id('663a90f5-f334-4b44-afe0-c5fc1d408791') def test_update_and_delete_all_tags(self): self._create_tags_for_each_resource() for resource in self.SUPPORTED_RESOURCES: # Generate 3 new tag names. replace_tags = [data_utils.rand_name( self.__class__.__name__ + '-Tag') for _ in range(3)] # Replace the current tag with the 3 new tags and validate that the # current resource has the 3 new tags. resource_object = getattr(self, resource[:-1]) updated_tags = self.tags_client.update_all_tags( resource, resource_object['id'], replace_tags)['tags'] self.assertEqual(3, len(updated_tags)) self.assertEqual(set(replace_tags), set(updated_tags)) # Delete all the tags and check that they have been removed. self.tags_client.delete_all_tags(resource, resource_object['id']) for i in range(3): self.assertRaises( lib_exc.NotFound, self.tags_client.check_tag_existence, resource, resource_object['id'], replace_tags[i]) tempest-17.2.0/tempest/api/network/test_networks_negative.py0000666000175100017510000001024313207044712024402 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # 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 tempest.api.network import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class NetworksNegativeTestJSON(base.BaseNetworkTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('9293e937-824d-42d2-8d5b-e985ea67002a') def test_show_non_existent_network(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.networks_client.show_network, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('d746b40c-5e09-4043-99f7-cba1be8b70df') def test_show_non_existent_subnet(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.subnets_client.show_subnet, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('a954861d-cbfd-44e8-b0a9-7fab111f235d') def test_show_non_existent_port(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.ports_client.show_port, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('98bfe4e3-574e-4012-8b17-b2647063de87') def test_update_non_existent_network(self): non_exist_id = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.networks_client.update_network, non_exist_id, name="new_name") @decorators.attr(type=['negative']) @decorators.idempotent_id('03795047-4a94-4120-a0a1-bd376e36fd4e') def test_delete_non_existent_network(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.networks_client.delete_network, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('1cc47884-ac52-4415-a31c-e7ce5474a868') def test_update_non_existent_subnet(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.subnets_client.update_subnet, non_exist_id, name='new_name') @decorators.attr(type=['negative']) @decorators.idempotent_id('a176c859-99fb-42ec-a208-8a85b552a239') def test_delete_non_existent_subnet(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.subnets_client.delete_subnet, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('13d3b106-47e6-4b9b-8d53-dae947f092fe') def test_create_port_on_non_existent_network(self): non_exist_net_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.ports_client.create_port, network_id=non_exist_net_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('cf8eef21-4351-4f53-adcd-cc5cb1e76b92') def test_update_non_existent_port(self): non_exist_port_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.ports_client.update_port, non_exist_port_id, name='new_name') @decorators.attr(type=['negative']) @decorators.idempotent_id('49ec2bbd-ac2e-46fd-8054-798e679ff894') def test_delete_non_existent_port(self): non_exist_port_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.ports_client.delete_port, non_exist_port_id) tempest-17.2.0/tempest/api/network/test_security_groups.py0000666000175100017510000002536713207044712024127 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base_security_groups as base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class SecGroupTest(base.BaseSecGroupTest): @classmethod def skip_checks(cls): super(SecGroupTest, cls).skip_checks() if not utils.is_extension_enabled('security-group', 'network'): msg = "security-group extension not enabled." raise cls.skipException(msg) def _create_verify_security_group_rule(self, sg_id, direction, ethertype, protocol, port_range_min, port_range_max, remote_group_id=None, remote_ip_prefix=None): # Create Security Group rule with the input params and validate # that SG rule is created with the same parameters. sec_group_rules_client = self.security_group_rules_client rule_create_body = sec_group_rules_client.create_security_group_rule( security_group_id=sg_id, direction=direction, ethertype=ethertype, protocol=protocol, port_range_min=port_range_min, port_range_max=port_range_max, remote_group_id=remote_group_id, remote_ip_prefix=remote_ip_prefix ) sec_group_rule = rule_create_body['security_group_rule'] self.addCleanup(self._delete_security_group_rule, sec_group_rule['id']) expected = {'direction': direction, 'protocol': protocol, 'ethertype': ethertype, 'port_range_min': port_range_min, 'port_range_max': port_range_max, 'remote_group_id': remote_group_id, 'remote_ip_prefix': remote_ip_prefix} for key, value in expected.items(): self.assertEqual(value, sec_group_rule[key], "Field %s of the created security group " "rule does not match with %s." % (key, value)) @decorators.attr(type='smoke') @decorators.idempotent_id('e30abd17-fef9-4739-8617-dc26da88e686') def test_list_security_groups(self): # Verify the security group belonging to project exist in list body = self.security_groups_client.list_security_groups() security_groups = body['security_groups'] found = None for n in security_groups: if (n['name'] == 'default'): found = n['id'] msg = "Security-group list doesn't contain default security-group" self.assertIsNotNone(found, msg) @decorators.attr(type='smoke') @decorators.idempotent_id('bfd128e5-3c92-44b6-9d66-7fe29d22c802') def test_create_list_update_show_delete_security_group(self): group_create_body, _ = self._create_security_group() # List security groups and verify if created group is there in response list_body = self.security_groups_client.list_security_groups() secgroup_list = list() for secgroup in list_body['security_groups']: secgroup_list.append(secgroup['id']) self.assertIn(group_create_body['security_group']['id'], secgroup_list) # Update the security group new_name = data_utils.rand_name('security-') new_description = data_utils.rand_name('security-description') update_body = self.security_groups_client.update_security_group( group_create_body['security_group']['id'], name=new_name, description=new_description) # Verify if security group is updated self.assertEqual(update_body['security_group']['name'], new_name) self.assertEqual(update_body['security_group']['description'], new_description) # Show details of the updated security group show_body = self.security_groups_client.show_security_group( group_create_body['security_group']['id']) self.assertEqual(show_body['security_group']['name'], new_name) self.assertEqual(show_body['security_group']['description'], new_description) @decorators.attr(type='smoke') @decorators.idempotent_id('cfb99e0e-7410-4a3d-8a0c-959a63ee77e9') def test_create_show_delete_security_group_rule(self): group_create_body, _ = self._create_security_group() # Create rules for each protocol protocols = ['tcp', 'udp', 'icmp'] client = self.security_group_rules_client for protocol in protocols: rule_create_body = client.create_security_group_rule( security_group_id=group_create_body['security_group']['id'], protocol=protocol, direction='ingress', ethertype=self.ethertype ) # Show details of the created security rule show_rule_body = client.show_security_group_rule( rule_create_body['security_group_rule']['id'] ) create_dict = rule_create_body['security_group_rule'] for key, value in create_dict.items(): self.assertEqual(value, show_rule_body['security_group_rule'][key], "%s does not match." % key) # List rules and verify created rule is in response rule_list_body = ( self.security_group_rules_client.list_security_group_rules()) rule_list = [rule['id'] for rule in rule_list_body['security_group_rules']] self.assertIn(rule_create_body['security_group_rule']['id'], rule_list) @decorators.idempotent_id('87dfbcf9-1849-43ea-b1e4-efa3eeae9f71') def test_create_security_group_rule_with_additional_args(self): """Verify security group rule with additional arguments works. direction:ingress, ethertype:[IPv4/IPv6], protocol:tcp, port_range_min:77, port_range_max:77 """ group_create_body, _ = self._create_security_group() sg_id = group_create_body['security_group']['id'] direction = 'ingress' protocol = 'tcp' port_range_min = 77 port_range_max = 77 self._create_verify_security_group_rule(sg_id, direction, self.ethertype, protocol, port_range_min, port_range_max) @decorators.idempotent_id('c9463db8-b44d-4f52-b6c0-8dbda99f26ce') def test_create_security_group_rule_with_icmp_type_code(self): """Verify security group rule for icmp protocol works. Specify icmp type (port_range_min) and icmp code (port_range_max) with different values. A separate testcase is added for icmp protocol as icmp validation would be different from tcp/udp. """ group_create_body, _ = self._create_security_group() sg_id = group_create_body['security_group']['id'] direction = 'ingress' protocol = 'icmp' icmp_type_codes = [(3, 2), (3, 0), (8, 0), (0, 0), (11, None)] for icmp_type, icmp_code in icmp_type_codes: self._create_verify_security_group_rule(sg_id, direction, self.ethertype, protocol, icmp_type, icmp_code) @decorators.idempotent_id('c2ed2deb-7a0c-44d8-8b4c-a5825b5c310b') def test_create_security_group_rule_with_remote_group_id(self): # Verify creating security group rule with remote_group_id works sg1_body, _ = self._create_security_group() sg2_body, _ = self._create_security_group() sg_id = sg1_body['security_group']['id'] direction = 'ingress' protocol = 'udp' port_range_min = 50 port_range_max = 55 remote_id = sg2_body['security_group']['id'] self._create_verify_security_group_rule(sg_id, direction, self.ethertype, protocol, port_range_min, port_range_max, remote_group_id=remote_id) @decorators.idempotent_id('16459776-5da2-4634-bce4-4b55ee3ec188') def test_create_security_group_rule_with_remote_ip_prefix(self): # Verify creating security group rule with remote_ip_prefix works sg1_body, _ = self._create_security_group() sg_id = sg1_body['security_group']['id'] direction = 'ingress' protocol = 'tcp' port_range_min = 76 port_range_max = 77 ip_prefix = str(self.cidr) self._create_verify_security_group_rule(sg_id, direction, self.ethertype, protocol, port_range_min, port_range_max, remote_ip_prefix=ip_prefix) @decorators.idempotent_id('0a307599-6655-4220-bebc-fd70c64f2290') def test_create_security_group_rule_with_protocol_integer_value(self): # Verify creating security group rule with the # protocol as integer value # arguments : "protocol": 17 group_create_body, _ = self._create_security_group() direction = 'ingress' protocol = 17 security_group_id = group_create_body['security_group']['id'] client = self.security_group_rules_client rule_create_body = client.create_security_group_rule( security_group_id=security_group_id, direction=direction, protocol=protocol ) sec_group_rule = rule_create_body['security_group_rule'] self.assertEqual(sec_group_rule['direction'], direction) self.assertEqual(int(sec_group_rule['protocol']), protocol) class SecGroupIPv6Test(SecGroupTest): _ip_version = 6 tempest-17.2.0/tempest/api/network/admin/0000775000175100017510000000000013207045130020314 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/network/admin/test_external_network_extension.py0000666000175100017510000001441313207044712027426 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.api.network import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class ExternalNetworksTestJSON(base.BaseAdminNetworkTest): @classmethod def resource_setup(cls): super(ExternalNetworksTestJSON, cls).resource_setup() cls.network = cls.create_network() def _create_network(self, external=True): post_body = {'name': data_utils.rand_name('network-')} if external: post_body['router:external'] = external body = self.admin_networks_client.create_network(**post_body) network = body['network'] self.addCleanup( self.admin_networks_client.delete_network, network['id']) return network @decorators.idempotent_id('462be770-b310-4df9-9c42-773217e4c8b1') def test_create_external_network(self): # Create a network as an admin user specifying the # external network extension attribute ext_network = self._create_network() # Verifies router:external parameter self.assertIsNotNone(ext_network['id']) self.assertTrue(ext_network['router:external']) @decorators.idempotent_id('4db5417a-e11c-474d-a361-af00ebef57c5') def test_update_external_network(self): # Update a network as an admin user specifying the # external network extension attribute network = self._create_network(external=False) self.assertFalse(network.get('router:external', False)) update_body = {'router:external': True} body = self.admin_networks_client.update_network(network['id'], **update_body) updated_network = body['network'] # Verify that router:external parameter was updated self.assertTrue(updated_network['router:external']) @decorators.idempotent_id('39be4c9b-a57e-4ff9-b7c7-b218e209dfcc') def test_list_external_networks(self): # Create external_net external_network = self._create_network() # List networks as a normal user and confirm the external # network extension attribute is returned for those networks # that were created as external body = self.networks_client.list_networks() networks_list = [net['id'] for net in body['networks']] self.assertIn(external_network['id'], networks_list) self.assertIn(self.network['id'], networks_list) for net in body['networks']: if net['id'] == self.network['id']: self.assertFalse(net['router:external']) elif net['id'] == external_network['id']: self.assertTrue(net['router:external']) @decorators.idempotent_id('2ac50ab2-7ebd-4e27-b3ce-a9e399faaea2') def test_show_external_networks_attribute(self): # Create external_net external_network = self._create_network() # Show an external network as a normal user and confirm the # external network extension attribute is returned. body = self.networks_client.show_network(external_network['id']) show_ext_net = body['network'] self.assertEqual(external_network['name'], show_ext_net['name']) self.assertEqual(external_network['id'], show_ext_net['id']) self.assertTrue(show_ext_net['router:external']) body = self.networks_client.show_network(self.network['id']) show_net = body['network'] # Verify with show that router:external is False for network self.assertEqual(self.network['name'], show_net['name']) self.assertEqual(self.network['id'], show_net['id']) self.assertFalse(show_net['router:external']) @decorators.idempotent_id('82068503-2cf2-4ed4-b3be-ecb89432e4bb') @testtools.skipUnless(CONF.network_feature_enabled.floating_ips, 'Floating ips are not availabled') def test_delete_external_networks_with_floating_ip(self): # Verifies external network can be deleted while still holding # (unassociated) floating IPs body = self.admin_networks_client.create_network( **{'router:external': True}) external_network = body['network'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_networks_client.delete_network, external_network['id']) subnet = self.create_subnet( external_network, client=self.admin_subnets_client, enable_dhcp=False) body = self.admin_floating_ips_client.create_floatingip( floating_network_id=external_network['id']) created_floating_ip = body['floatingip'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_floating_ips_client.delete_floatingip, created_floating_ip['id']) floatingip_list = self.admin_floating_ips_client.list_floatingips( network=external_network['id']) self.assertIn(created_floating_ip['id'], (f['id'] for f in floatingip_list['floatingips'])) self.admin_networks_client.delete_network(external_network['id']) # Verifies floating ip is deleted floatingip_list = self.admin_floating_ips_client.list_floatingips() self.assertNotIn(created_floating_ip['id'], (f['id'] for f in floatingip_list['floatingips'])) # Verifies subnet is deleted subnet_list = self.admin_subnets_client.list_subnets() self.assertNotIn(subnet['id'], (s['id'] for s in subnet_list)) # Removes subnet from the cleanup list self.subnets.remove(subnet) tempest-17.2.0/tempest/api/network/admin/test_external_networks_negative.py0000666000175100017510000000455013207044712027400 0ustar zuulzuul00000000000000# Copyright 2014 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 from tempest.api.network import base from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ExternalNetworksAdminNegativeTestJSON(base.BaseAdminNetworkTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('d402ae6c-0be0-4d8e-833b-a738895d98d0') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_create_port_with_precreated_floatingip_as_fixed_ip(self): # NOTE: External networks can be used to create both floating-ip as # well as instance-ip. So, creating an instance-ip with a value of a # pre-created floating-ip should be denied. # create a floating ip body = self.admin_floating_ips_client.create_floatingip( floating_network_id=CONF.network.public_network_id) created_floating_ip = body['floatingip'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_floating_ips_client.delete_floatingip, created_floating_ip['id']) floating_ip_address = created_floating_ip['floating_ip_address'] self.assertIsNotNone(floating_ip_address) # use the same value of floatingip as fixed-ip to create_port() fixed_ips = [{'ip_address': floating_ip_address}] # create a port which will internally create an instance-ip self.assertRaises(lib_exc.Conflict, self.admin_ports_client.create_port, network_id=CONF.network.public_network_id, fixed_ips=fixed_ips) tempest-17.2.0/tempest/api/network/admin/test_l3_agent_scheduler.py0000666000175100017510000000617013207044712025472 0ustar zuulzuul00000000000000# 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 tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF AGENT_TYPE = 'L3 agent' AGENT_MODES = ( 'legacy', 'dvr_snat' ) class L3AgentSchedulerTestJSON(base.BaseAdminNetworkTest): """Tests the following operations in the Neutron API: List routers that the given L3 agent is hosting. List L3 agents hosting the given router. Add and Remove Router to L3 agent v2.0 of the Neutron API is assumed. The l3_agent_scheduler extension is required for these tests. """ @classmethod def skip_checks(cls): super(L3AgentSchedulerTestJSON, cls).skip_checks() if not utils.is_extension_enabled('l3_agent_scheduler', 'network'): msg = "L3 Agent Scheduler Extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(L3AgentSchedulerTestJSON, cls).resource_setup() agents = cls.admin_agents_client.list_agents( agent_type=AGENT_TYPE)['agents'] for agent in agents: if agent['configurations']['agent_mode'] in AGENT_MODES: cls.agent = agent break else: msg = "L3 Agent Scheduler enabled in conf, but L3 Agent not found" raise exceptions.InvalidConfiguration(msg) cls.router = cls.create_router() @decorators.idempotent_id('b7ce6e89-e837-4ded-9b78-9ed3c9c6a45a') def test_list_routers_on_l3_agent(self): self.admin_agents_client.list_routers_on_l3_agent(self.agent['id']) @decorators.idempotent_id('9464e5e7-8625-49c3-8fd1-89c52be59d66') def test_add_list_remove_router_on_l3_agent(self): l3_agent_ids = list() self.admin_agents_client.create_router_on_l3_agent( self.agent['id'], router_id=self.router['id']) body = ( 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) self.assertEqual('L3 agent', agent['agent_type']) self.assertIn(self.agent['id'], l3_agent_ids) body = self.admin_agents_client.delete_router_from_l3_agent( self.agent['id'], self.router['id']) # NOTE(afazekas): The deletion not asserted, because neutron # is not forbidden to reschedule the router to the same agent tempest-17.2.0/tempest/api/network/admin/test_quotas.py0000666000175100017510000000677613207044712023270 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base from tempest.common import identity from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators class QuotasTest(base.BaseAdminNetworkTest): """Tests the following operations in the Neutron API: list quotas for projects who have non-default quota values show quotas for a specified project update quotas for a specified project reset quotas to default values for a specified project v2.0 of the API is assumed. It is also assumed that the per-project quota extension API is configured in /etc/neutron/neutron.conf as follows: quota_driver = neutron.db.quota_db.DbQuotaDriver """ @classmethod def skip_checks(cls): super(QuotasTest, cls).skip_checks() if not utils.is_extension_enabled('quotas', 'network'): msg = "quotas extension not enabled." raise cls.skipException(msg) def _check_quotas(self, new_quotas): # Add a project to conduct the test project = data_utils.rand_name('test_project_') description = data_utils.rand_name('desc_') project = identity.identity_utils(self.os_admin).create_project( name=project, description=description) project_id = project['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_project, project_id) # Change quotas for project quota_set = self.admin_quotas_client.update_quotas( project_id, **new_quotas)['quota'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.admin_quotas_client.reset_quotas, project_id) for key, value in new_quotas.items(): self.assertEqual(value, quota_set[key]) # Confirm our project is listed among projects with non default quotas non_default_quotas = self.admin_quotas_client.list_quotas() found = False for qs in non_default_quotas['quotas']: if qs['tenant_id'] == project_id: found = True self.assertTrue(found) # Confirm from API quotas were changed as requested for project quota_set = self.admin_quotas_client.show_quotas(project_id) quota_set = quota_set['quota'] for key, value in new_quotas.items(): self.assertEqual(value, quota_set[key]) # Reset quotas to default and confirm self.admin_quotas_client.reset_quotas(project_id) non_default_quotas = self.admin_quotas_client.list_quotas() for q in non_default_quotas['quotas']: self.assertNotEqual(project_id, q['tenant_id']) @decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb') def test_quotas(self): new_quotas = {'network': 0, 'port': 0} self._check_quotas(new_quotas) tempest-17.2.0/tempest/api/network/admin/test_negative_quotas.py0000666000175100017510000000506313207044712025136 0ustar zuulzuul00000000000000# Copyright 2015 Cloudwatt # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common import utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class QuotasNegativeTest(base.BaseAdminNetworkTest): """Tests the following operations in the Neutron API: set network quota and exceed this quota v2.0 of the API is assumed. It is also assumed that the per-project quota extension API is configured in /etc/neutron/neutron.conf as follows: quota_driver = neutron.db.quota_db.DbQuotaDriver """ force_tenant_isolation = True @classmethod def skip_checks(cls): super(QuotasNegativeTest, cls).skip_checks() if not utils.is_extension_enabled('quotas', 'network'): msg = "quotas extension not enabled." raise cls.skipException(msg) @decorators.attr(type=['negative']) @decorators.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf') def test_network_quota_exceeding(self): # Set the network quota to two self.admin_quotas_client.update_quotas(self.networks_client.tenant_id, network=2) self.addCleanup(self.admin_quotas_client.reset_quotas, self.networks_client.tenant_id) # Create two networks n1 = self.networks_client.create_network() self.addCleanup(self.networks_client.delete_network, n1['network']['id']) n2 = self.networks_client.create_network() self.addCleanup(self.networks_client.delete_network, n2['network']['id']) # Try to create a third network while the quota is two with self.assertRaisesRegex( lib_exc.Conflict, "Quota exceeded for resources: \['network'\].*"): n3 = self.networks_client.create_network() self.addCleanup(self.networks_client.delete_network, n3['network']['id']) tempest-17.2.0/tempest/api/network/admin/__init__.py0000666000175100017510000000000013207044712022422 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/network/admin/test_metering_extensions.py0000666000175100017510000002001613207044712026024 0ustar zuulzuul00000000000000# Copyright (C) 2014 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.network import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators class MeteringTestJSON(base.BaseAdminNetworkTest): """Tests the following operations in the Neutron API: List, Show, Create, Delete Metering labels List, Show, Create, Delete Metering labels rules """ @classmethod def skip_checks(cls): super(MeteringTestJSON, cls).skip_checks() if not utils.is_extension_enabled('metering', 'network'): msg = "metering extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(MeteringTestJSON, cls).resource_setup() description = "metering label created by tempest" name = data_utils.rand_name("metering-label") cls.metering_label = cls.create_metering_label(name, description) remote_ip_prefix = ("10.0.0.0/24" if cls._ip_version == 4 else "fd02::/64") direction = "ingress" cls.metering_label_rule = cls.create_metering_label_rule( remote_ip_prefix, direction, metering_label_id=cls.metering_label['id']) @classmethod def create_metering_label(cls, name, description): """Wrapper utility that returns a test metering label.""" body = cls.admin_metering_labels_client.create_metering_label( description=description, name=name) metering_label = body['metering_label'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.admin_metering_labels_client.delete_metering_label, metering_label['id']) return metering_label @classmethod def create_metering_label_rule(cls, remote_ip_prefix, direction, metering_label_id): """Wrapper utility that returns a test metering label rule.""" client = cls.admin_metering_label_rules_client body = client.create_metering_label_rule( remote_ip_prefix=remote_ip_prefix, direction=direction, metering_label_id=metering_label_id) metering_label_rule = body['metering_label_rule'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, client.delete_metering_label_rule, metering_label_rule['id']) return metering_label_rule def _delete_metering_label(self, metering_label_id): # Deletes a label and verifies if it is deleted or not self.admin_metering_labels_client.delete_metering_label( metering_label_id) # Asserting that the label is not found in list after deletion labels = self.admin_metering_labels_client.list_metering_labels( id=metering_label_id) self.assertEmpty(labels['metering_labels']) def _delete_metering_label_rule(self, metering_label_rule_id): client = self.admin_metering_label_rules_client # Deletes a rule and verifies if it is deleted or not client.delete_metering_label_rule(metering_label_rule_id) # Asserting that the rule is not found in list after deletion rules = client.list_metering_label_rules(id=metering_label_rule_id) self.assertEqual(len(rules['metering_label_rules']), 0) @decorators.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612') def test_list_metering_labels(self): # Verify label filtering body = self.admin_metering_labels_client.list_metering_labels(id=33) metering_labels = body['metering_labels'] self.assertEmpty(metering_labels) @decorators.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50') def test_create_delete_metering_label_with_filters(self): # Creates a label name = data_utils.rand_name('metering-label-') description = "label created by tempest" body = self.admin_metering_labels_client.create_metering_label( name=name, description=description) metering_label = body['metering_label'] self.addCleanup(self._delete_metering_label, metering_label['id']) # Assert whether created labels are found in labels list or fail # if created labels are not found in labels list labels = (self.admin_metering_labels_client.list_metering_labels( id=metering_label['id'])) self.assertEqual(len(labels['metering_labels']), 1) @decorators.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968') def test_show_metering_label(self): # Verifies the details of a label body = self.admin_metering_labels_client.show_metering_label( self.metering_label['id']) metering_label = body['metering_label'] self.assertEqual(self.metering_label['id'], metering_label['id']) self.assertEqual(self.metering_label['tenant_id'], metering_label['tenant_id']) self.assertEqual(self.metering_label['name'], metering_label['name']) self.assertEqual(self.metering_label['description'], metering_label['description']) @decorators.idempotent_id('cc832399-6681-493b-9d79-0202831a1281') def test_list_metering_label_rules(self): client = self.admin_metering_label_rules_client # Verify rule filtering body = client.list_metering_label_rules(id=33) metering_label_rules = body['metering_label_rules'] self.assertEmpty(metering_label_rules) @decorators.idempotent_id('f4d547cd-3aee-408f-bf36-454f8825e045') def test_create_delete_metering_label_rule_with_filters(self): # Creates a rule remote_ip_prefix = ("10.0.1.0/24" if self._ip_version == 4 else "fd03::/64") client = self.admin_metering_label_rules_client body = (client.create_metering_label_rule( remote_ip_prefix=remote_ip_prefix, direction="ingress", metering_label_id=self.metering_label['id'])) metering_label_rule = body['metering_label_rule'] self.addCleanup(self._delete_metering_label_rule, metering_label_rule['id']) # Assert whether created rules are found in rules list or fail # if created rules are not found in rules list rules = client.list_metering_label_rules(id=metering_label_rule['id']) self.assertEqual(len(rules['metering_label_rules']), 1) @decorators.idempotent_id('b7354489-96ea-41f3-9452-bace120fb4a7') def test_show_metering_label_rule(self): # Verifies the details of a rule client = self.admin_metering_label_rules_client body = (client.show_metering_label_rule( self.metering_label_rule['id'])) metering_label_rule = body['metering_label_rule'] self.assertEqual(self.metering_label_rule['id'], metering_label_rule['id']) self.assertEqual(self.metering_label_rule['remote_ip_prefix'], metering_label_rule['remote_ip_prefix']) self.assertEqual(self.metering_label_rule['direction'], metering_label_rule['direction']) self.assertEqual(self.metering_label_rule['metering_label_id'], metering_label_rule['metering_label_id']) self.assertFalse(metering_label_rule['excluded']) class MeteringIpV6TestJSON(MeteringTestJSON): _ip_version = 6 tempest-17.2.0/tempest/api/network/admin/test_routers_negative.py0000666000175100017510000000470313207044712025325 0ustar zuulzuul00000000000000# 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 testtools from tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class RoutersAdminNegativeTest(base.BaseAdminNetworkTest): @classmethod def skip_checks(cls): super(RoutersAdminNegativeTest, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) @decorators.attr(type=['negative']) @decorators.idempotent_id('7101cc02-058a-11e7-93e1-fa163e4fa634') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_router_set_gateway_used_ip_returns_409(self): # At first create a address from public_network_id port = self.admin_ports_client.create_port( network_id=CONF.network.public_network_id)['port'] self.addCleanup(self.admin_ports_client.delete_port, port_id=port['id']) # Add used ip and subnet_id in external_fixed_ips fixed_ip = { 'subnet_id': port['fixed_ips'][0]['subnet_id'], 'ip_address': port['fixed_ips'][0]['ip_address'] } external_gateway_info = { 'network_id': CONF.network.public_network_id, 'external_fixed_ips': [fixed_ip] } # Create a router and set gateway to used ip self.assertRaises(lib_exc.Conflict, self.admin_routers_client.create_router, external_gateway_info=external_gateway_info) class RoutersAdminNegativeIpV6Test(RoutersAdminNegativeTest): _ip_version = 6 tempest-17.2.0/tempest/api/network/admin/test_dhcp_agent_scheduler.py0000666000175100017510000001011413207044712026063 0ustar zuulzuul00000000000000# 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 tempest.api.network import base from tempest.common import utils from tempest.lib import decorators class DHCPAgentSchedulersTestJSON(base.BaseAdminNetworkTest): @classmethod def skip_checks(cls): super(DHCPAgentSchedulersTestJSON, cls).skip_checks() if not utils.is_extension_enabled('dhcp_agent_scheduler', 'network'): msg = "dhcp_agent_scheduler extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(DHCPAgentSchedulersTestJSON, cls).resource_setup() # Create a network and make sure it will be hosted by a # dhcp agent: this is done by creating a regular port cls.network = cls.create_network() cls.create_subnet(cls.network) cls.port = cls.create_port(cls.network) @decorators.idempotent_id('5032b1fe-eb42-4a64-8f3b-6e189d8b5c7d') def test_list_dhcp_agent_hosting_network(self): self.admin_networks_client.list_dhcp_agents_on_hosting_network( self.network['id']) @decorators.idempotent_id('30c48f98-e45d-4ffb-841c-b8aad57c7587') def test_list_networks_hosted_by_one_dhcp(self): body = self.admin_networks_client.list_dhcp_agents_on_hosting_network( self.network['id']) agents = body['agents'] self.assertNotEmpty(agents, "no dhcp agent") agent = agents[0] self.assertTrue(self._check_network_in_dhcp_agent( self.network['id'], agent)) def _check_network_in_dhcp_agent(self, network_id, agent): network_ids = [] body = self.admin_agents_client.list_networks_hosted_by_one_dhcp_agent( agent['id']) networks = body['networks'] for network in networks: network_ids.append(network['id']) return network_id in network_ids @decorators.idempotent_id('a0856713-6549-470c-a656-e97c8df9a14d') def test_add_remove_network_from_dhcp_agent(self): # The agent is now bound to the network, we can free the port self.ports_client.delete_port(self.port['id']) self.ports.remove(self.port) agent = dict() agent['agent_type'] = None body = self.admin_agents_client.list_agents() agents = body['agents'] for a in agents: if a['agent_type'] == 'DHCP agent': agent = a break self.assertEqual(agent['agent_type'], 'DHCP agent', 'Could not find ' 'DHCP agent in agent list though dhcp_agent_scheduler' ' is enabled.') network = self.create_network() network_id = network['id'] if self._check_network_in_dhcp_agent(network_id, agent): self._remove_network_from_dhcp_agent(network_id, agent) self._add_dhcp_agent_to_network(network_id, agent) else: self._add_dhcp_agent_to_network(network_id, agent) self._remove_network_from_dhcp_agent(network_id, agent) def _remove_network_from_dhcp_agent(self, network_id, agent): self.admin_agents_client.delete_network_from_dhcp_agent( agent_id=agent['id'], network_id=network_id) self.assertFalse(self._check_network_in_dhcp_agent( network_id, agent)) def _add_dhcp_agent_to_network(self, network_id, agent): self.admin_agents_client.add_dhcp_agent_to_network( agent['id'], network_id=network_id) self.assertTrue(self._check_network_in_dhcp_agent( network_id, agent)) tempest-17.2.0/tempest/api/network/admin/test_ports.py0000666000175100017510000001023313207044712023102 0ustar zuulzuul00000000000000# Copyright 2014 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 socket from tempest.api.network import base from tempest import config from tempest.lib import decorators CONF = config.CONF class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest): @classmethod def resource_setup(cls): super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup() cls.network = cls.create_network() cls.host_id = socket.gethostname() @decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b') def test_create_port_binding_ext_attr(self): post_body = {"network_id": self.network['id'], "binding:host_id": self.host_id} body = self.admin_ports_client.create_port(**post_body) port = body['port'] self.addCleanup(self.admin_ports_client.delete_port, port['id']) host_id = port['binding:host_id'] self.assertIsNotNone(host_id) self.assertEqual(self.host_id, host_id) @decorators.idempotent_id('6f6c412c-711f-444d-8502-0ac30fbf5dd5') def test_update_port_binding_ext_attr(self): post_body = {"network_id": self.network['id']} body = self.admin_ports_client.create_port(**post_body) port = body['port'] self.addCleanup(self.admin_ports_client.delete_port, port['id']) update_body = {"binding:host_id": self.host_id} body = self.admin_ports_client.update_port(port['id'], **update_body) updated_port = body['port'] host_id = updated_port['binding:host_id'] self.assertIsNotNone(host_id) self.assertEqual(self.host_id, host_id) @decorators.idempotent_id('1c82a44a-6c6e-48ff-89e1-abe7eaf8f9f8') def test_list_ports_binding_ext_attr(self): # Create a new port post_body = {"network_id": self.network['id']} body = self.admin_ports_client.create_port(**post_body) port = body['port'] self.addCleanup(self.admin_ports_client.delete_port, port['id']) # Update the port's binding attributes so that is now 'bound' # to a host update_body = {"binding:host_id": self.host_id} self.admin_ports_client.update_port(port['id'], **update_body) # List all ports, ensure new port is part of list and its binding # attributes are set and accurate body = self.admin_ports_client.list_ports() ports_list = body['ports'] pids_list = [p['id'] for p in ports_list] self.assertIn(port['id'], pids_list) listed_port = [p for p in ports_list if p['id'] == port['id']] self.assertEqual(1, len(listed_port), 'Multiple ports listed with id %s in ports listing: ' '%s' % (port['id'], ports_list)) self.assertEqual(self.host_id, listed_port[0]['binding:host_id']) @decorators.idempotent_id('b54ac0ff-35fc-4c79-9ca3-c7dbd4ea4f13') def test_show_port_binding_ext_attr(self): body = self.admin_ports_client.create_port( network_id=self.network['id']) port = body['port'] self.addCleanup(self.admin_ports_client.delete_port, port['id']) body = self.admin_ports_client.show_port(port['id']) show_port = body['port'] self.assertEqual(port['binding:host_id'], show_port['binding:host_id']) self.assertEqual(port['binding:vif_type'], show_port['binding:vif_type']) self.assertEqual(port['binding:vif_details'], show_port['binding:vif_details']) class PortsAdminExtendedAttrsIpV6TestJSON(PortsAdminExtendedAttrsTestJSON): _ip_version = 6 tempest-17.2.0/tempest/api/network/admin/test_agent_management.py0000666000175100017510000000735013207044712025233 0ustar zuulzuul00000000000000# 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 tempest.api.network import base from tempest.common import tempest_fixtures as fixtures from tempest.common import utils from tempest.lib import decorators class AgentManagementTestJSON(base.BaseAdminNetworkTest): @classmethod def skip_checks(cls): super(AgentManagementTestJSON, cls).skip_checks() if not utils.is_extension_enabled('agent', 'network'): msg = "agent extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(AgentManagementTestJSON, cls).resource_setup() body = cls.admin_agents_client.list_agents() agents = body['agents'] cls.agent = agents[0] @decorators.idempotent_id('9c80f04d-11f3-44a4-8738-ed2f879b0ff4') def test_list_agent(self): body = self.admin_agents_client.list_agents() agents = body['agents'] # Hearthbeats must be excluded from comparison self.agent.pop('heartbeat_timestamp', None) self.agent.pop('configurations', None) for agent in agents: agent.pop('heartbeat_timestamp', None) agent.pop('configurations', None) self.assertIn(self.agent, agents) @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6') def test_list_agents_non_admin(self): body = self.agents_client.list_agents() self.assertEmpty(body["agents"]) @decorators.idempotent_id('869bc8e8-0fda-4a30-9b71-f8a7cf58ca9f') def test_show_agent(self): body = self.admin_agents_client.show_agent(self.agent['id']) agent = body['agent'] self.assertEqual(agent['id'], self.agent['id']) @decorators.idempotent_id('371dfc5b-55b9-4cb5-ac82-c40eadaac941') def test_update_agent_status(self): origin_status = self.agent['admin_state_up'] # Try to update the 'admin_state_up' to the original # one to avoid the negative effect. agent_status = {'admin_state_up': origin_status} body = self.admin_agents_client.update_agent(agent_id=self.agent['id'], agent=agent_status) updated_status = body['agent']['admin_state_up'] self.assertEqual(origin_status, updated_status) @decorators.idempotent_id('68a94a14-1243-46e6-83bf-157627e31556') def test_update_agent_description(self): self.useFixture(fixtures.LockFixture('agent_description')) description = 'description for update agent.' agent_description = {'description': description} body = self.admin_agents_client.update_agent(agent_id=self.agent['id'], agent=agent_description) self.addCleanup(self._restore_agent) updated_description = body['agent']['description'] self.assertEqual(updated_description, description) def _restore_agent(self): """Restore the agent description after update test""" description = self.agent['description'] or '' origin_agent = {'description': description} self.admin_agents_client.update_agent(agent_id=self.agent['id'], agent=origin_agent) tempest-17.2.0/tempest/api/network/admin/test_routers.py0000666000175100017510000002307713207044712023450 0ustar zuulzuul00000000000000# 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 testtools from tempest.api.network import base from tempest.common import identity from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class RoutersAdminTest(base.BaseAdminNetworkTest): # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest # as some router operations, such as enabling or disabling SNAT # require admin credentials by default def _cleanup_router(self, router): self.delete_router(router) self.routers.remove(router) def _create_router(self, name=None, admin_state_up=False, external_network_id=None, enable_snat=None): # associate a cleanup with created routers to avoid quota limits router = self.create_router(name, admin_state_up, external_network_id, enable_snat) self.addCleanup(self._cleanup_router, router) return router @classmethod def skip_checks(cls): super(RoutersAdminTest, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) @decorators.idempotent_id('e54dd3a3-4352-4921-b09d-44369ae17397') def test_create_router_setting_project_id(self): # Test creating router from admin user setting project_id. project = data_utils.rand_name('test_tenant_') description = data_utils.rand_name('desc_') project = identity.identity_utils(self.os_admin).create_project( name=project, description=description) project_id = project['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_project, project_id) name = data_utils.rand_name('router-') create_body = self.admin_routers_client.create_router( name=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']) @decorators.idempotent_id('847257cc-6afd-4154-b8fb-af49f5670ce8') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_create_router_with_default_snat_value(self): # Create a router with default snat rule router = self._create_router( external_network_id=CONF.network.public_network_id) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': True}) @decorators.idempotent_id('ea74068d-09e9-4fd7-8995-9b6a1ace920f') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_create_router_with_snat_explicit(self): name = data_utils.rand_name('snat-router') # Create a router enabling snat attributes enable_snat_states = [False, True] for enable_snat in enable_snat_states: external_gateway_info = { 'network_id': CONF.network.public_network_id, 'enable_snat': enable_snat} create_body = self.admin_routers_client.create_router( name=name, external_gateway_info=external_gateway_info) 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'], exp_ext_gw_info=external_gateway_info) def _verify_router_gateway(self, router_id, exp_ext_gw_info=None): 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) return # Verify only keys passed in exp_ext_gw_info for k, v in exp_ext_gw_info.items(): self.assertEqual(v, actual_ext_gw_info[k]) def _verify_gateway_port(self, router_id): list_body = self.admin_ports_client.list_ports( network_id=CONF.network.public_network_id, device_id=router_id) self.assertEqual(len(list_body['ports']), 1) gw_port = list_body['ports'][0] fixed_ips = gw_port['fixed_ips'] self.assertNotEmpty(fixed_ips) # Assert that all of the IPs from the router gateway port # are allocated from a valid public subnet. public_net_body = self.admin_networks_client.show_network( CONF.network.public_network_id) public_subnet_ids = public_net_body['network']['subnets'] for fixed_ip in fixed_ips: subnet_id = fixed_ip['subnet_id'] self.assertIn(subnet_id, public_subnet_ids) @decorators.idempotent_id('6cc285d8-46bf-4f36-9b1a-783e3008ba79') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_update_router_set_gateway(self): router = self._create_router() self.routers_client.update_router( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id}) # Verify operation - router self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id}) self._verify_gateway_port(router['id']) @decorators.idempotent_id('b386c111-3b21-466d-880c-5e72b01e1a33') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_update_router_set_gateway_with_snat_explicit(self): router = self._create_router() self.admin_routers_client.update_router( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id, 'enable_snat': True}) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': True}) self._verify_gateway_port(router['id']) @decorators.idempotent_id('96536bc7-8262-4fb2-9967-5c46940fa279') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_update_router_set_gateway_without_snat(self): router = self._create_router() self.admin_routers_client.update_router( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_gateway_port(router['id']) @decorators.idempotent_id('ad81b7ee-4f81-407b-a19c-17e623f763e8') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_update_router_unset_gateway(self): router = self._create_router( external_network_id=CONF.network.public_network_id) 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( network_id=CONF.network.public_network_id, device_id=router['id']) self.assertFalse(list_body['ports']) @decorators.idempotent_id('f2faf994-97f4-410b-a831-9bc977b64374') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_update_router_reset_gateway_without_snat(self): router = self._create_router( external_network_id=CONF.network.public_network_id) self.admin_routers_client.update_router( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_gateway_port(router['id']) class RoutersIpV6AdminTest(RoutersAdminTest): _ip_version = 6 tempest-17.2.0/tempest/api/network/admin/test_routers_dvr.py0000666000175100017510000001276213207044712024322 0ustar zuulzuul00000000000000# Copyright 2015 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 from tempest.api.network import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class RoutersTestDVR(base.BaseAdminNetworkTest): @classmethod def skip_checks(cls): super(RoutersTestDVR, cls).skip_checks() for ext in ['router', 'dvr']: if not utils.is_extension_enabled(ext, 'network'): msg = "%s extension not enabled." % ext raise cls.skipException(msg) # The check above will pass if api_extensions=all, which does # not mean DVR extension itself is present. # Instead, we have to check whether DVR is actually present by using # admin credentials to create router with distributed=True attribute # and checking for BadRequest exception and that the resulting router # has a distributed attribute. @classmethod def resource_setup(cls): super(RoutersTestDVR, cls).resource_setup() name = data_utils.rand_name('pretest-check') router = cls.admin_routers_client.create_router(name=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) @decorators.idempotent_id('08a2a0a8-f1e4-4b34-8e30-e522e836c44e') def test_distributed_router_creation(self): """Test distributed router creation Test uses administrative credentials to creates a DVR (Distributed Virtual Routing) router using the distributed=True. Acceptance The router is created and the "distributed" attribute is set to True """ name = data_utils.rand_name('router') router = self.admin_routers_client.create_router(name=name, distributed=True) self.addCleanup(self.admin_routers_client.delete_router, router['router']['id']) self.assertTrue(router['router']['distributed']) @decorators.idempotent_id('8a0a72b4-7290-4677-afeb-b4ffe37bc352') def test_centralized_router_creation(self): """Test centralized router creation Test uses administrative credentials to creates a CVR (Centralized Virtual Routing) router using the distributed=False. Acceptance The router is created and the "distributed" attribute is set to False, thus making it a "Centralized Virtual Router" as opposed to a "Distributed Virtual Router" """ name = data_utils.rand_name('router') router = self.admin_routers_client.create_router(name=name, distributed=False) self.addCleanup(self.admin_routers_client.delete_router, router['router']['id']) self.assertFalse(router['router']['distributed']) @decorators.idempotent_id('acd43596-c1fb-439d-ada8-31ad48ae3c2e') @testtools.skipUnless(utils.is_extension_enabled('l3-ha', 'network'), 'HA routers are not available.') def test_centralized_router_update_to_dvr(self): """Test centralized router update Test uses administrative credentials to creates a CVR (Centralized Virtual Routing) router using the distributed=False. Then it will "update" the router distributed attribute to True Acceptance The router is created and the "distributed" attribute is set to False. Once the router is updated, the distributed attribute will be set to True """ name = data_utils.rand_name('router') tenant_id = self.routers_client.tenant_id # router needs to be in admin state down in order to be upgraded to DVR # l3ha routers are not upgradable to dvr, make it explicitly non ha router = self.admin_routers_client.create_router(name=name, distributed=False, admin_state_up=False, ha=False, tenant_id=tenant_id) router_id = router['router']['id'] self.addCleanup(self.admin_routers_client.delete_router, router_id) self.assertFalse(router['router']['distributed']) router = self.admin_routers_client.update_router( router_id, distributed=True) self.assertTrue(router['router']['distributed']) show_body = self.admin_routers_client.show_router(router_id) self.assertTrue(show_body['router']['distributed']) show_body = self.routers_client.show_router(router_id) self.assertNotIn('distributed', show_body['router']) tempest-17.2.0/tempest/api/network/admin/test_floating_ips_admin_actions.py0000666000175100017510000001356113207044712027310 0ustar zuulzuul00000000000000# Copyright 2014 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 tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF class FloatingIPAdminTestJSON(base.BaseAdminNetworkTest): force_tenant_isolation = True credentials = ['primary', 'alt', 'admin'] @classmethod def skip_checks(cls): super(FloatingIPAdminTestJSON, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) if not CONF.network.public_network_id: msg = "The public_network_id option must be specified." raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def setup_clients(cls): super(FloatingIPAdminTestJSON, cls).setup_clients() cls.alt_floating_ips_client = cls.os_alt.floating_ips_client @classmethod def resource_setup(cls): super(FloatingIPAdminTestJSON, cls).resource_setup() cls.ext_net_id = CONF.network.public_network_id cls.floating_ip = cls.create_floatingip(cls.ext_net_id) cls.network = cls.create_network() subnet = cls.create_subnet(cls.network) router = cls.create_router(external_network_id=cls.ext_net_id) cls.create_router_interface(router['id'], subnet['id']) cls.port = cls.create_port(cls.network) @decorators.idempotent_id('64f2100b-5471-4ded-b46c-ddeeeb4f231b') def test_list_floating_ips_from_admin_and_nonadmin(self): # Create floating ip from admin user floating_ip_admin = self.admin_floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id) self.addCleanup(self.admin_floating_ips_client.delete_floatingip, floating_ip_admin['floatingip']['id']) # Create floating ip from alt user body = self.alt_floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id) floating_ip_alt = body['floatingip'] self.addCleanup(self.alt_floating_ips_client.delete_floatingip, floating_ip_alt['id']) # List floating ips from admin body = self.admin_floating_ips_client.list_floatingips() floating_ip_ids_admin = [f['id'] for f in body['floatingips']] # Check that admin sees all floating ips self.assertIn(self.floating_ip['id'], floating_ip_ids_admin) self.assertIn(floating_ip_admin['floatingip']['id'], floating_ip_ids_admin) self.assertIn(floating_ip_alt['id'], floating_ip_ids_admin) # List floating ips from nonadmin body = self.floating_ips_client.list_floatingips() floating_ip_ids = [f['id'] for f in body['floatingips']] # Check that nonadmin user doesn't see floating ip created from admin # and floating ip that is created in another project (alt user) self.assertIn(self.floating_ip['id'], floating_ip_ids) self.assertNotIn(floating_ip_admin['floatingip']['id'], floating_ip_ids) self.assertNotIn(floating_ip_alt['id'], floating_ip_ids) @decorators.idempotent_id('32727cc3-abe2-4485-a16e-48f2d54c14f2') def test_create_list_show_floating_ip_with_tenant_id_by_admin(self): # Creates a floating IP body = self.admin_floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id, tenant_id=self.network['tenant_id'], port_id=self.port['id']) created_floating_ip = body['floatingip'] self.addCleanup(self.floating_ips_client.delete_floatingip, created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['tenant_id']) self.assertIsNotNone(created_floating_ip['floating_ip_address']) self.assertEqual(created_floating_ip['port_id'], self.port['id']) self.assertEqual(created_floating_ip['floating_network_id'], self.ext_net_id) port = self.port['fixed_ips'] self.assertEqual(created_floating_ip['fixed_ip_address'], port[0]['ip_address']) # Verifies the details of a floating_ip floating_ip = self.admin_floating_ips_client.show_floatingip( created_floating_ip['id']) shown_floating_ip = floating_ip['floatingip'] self.assertEqual(shown_floating_ip['id'], created_floating_ip['id']) self.assertEqual(shown_floating_ip['floating_network_id'], self.ext_net_id) self.assertEqual(shown_floating_ip['tenant_id'], self.network['tenant_id']) self.assertEqual(shown_floating_ip['floating_ip_address'], created_floating_ip['floating_ip_address']) self.assertEqual(shown_floating_ip['port_id'], self.port['id']) # Verify the floating ip exists in the list of all floating_ips floating_ips = self.admin_floating_ips_client.list_floatingips() floatingip_id_list = [f['id'] for f in floating_ips['floatingips']] self.assertIn(created_floating_ip['id'], floatingip_id_list) tempest-17.2.0/tempest/api/network/test_extensions.py0000666000175100017510000000604113207044712023044 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base from tempest.common import utils from tempest.lib import decorators class ExtensionsTestJSON(base.BaseNetworkTest): """Tests the following operations in the Neutron API: List all available extensions v2.0 of the Neutron API is assumed. It is also assumed that api-extensions option is defined in the [network-feature-enabled] section of etc/tempest.conf. """ @decorators.attr(type='smoke') @decorators.idempotent_id('ef28c7e6-e646-4979-9d67-deb207bc5564') def test_list_show_extensions(self): # List available extensions for the project expected_alias = ['security-group', 'l3_agent_scheduler', 'ext-gw-mode', 'binding', 'quotas', 'agent', 'dhcp_agent_scheduler', 'provider', 'router', 'extraroute', 'external-net', 'allowed-address-pairs', 'extra_dhcp_opt', 'metering', 'dvr'] expected_alias = [ext for ext in expected_alias if utils.is_extension_enabled(ext, 'network')] actual_alias = list() extensions = self.network_extensions_client.list_extensions() list_extensions = extensions['extensions'] # Show and verify the details of the available extensions for ext in list_extensions: ext_name = ext['name'] ext_alias = ext['alias'] actual_alias.append(ext['alias']) ext_details = self.network_extensions_client.show_extension( ext_alias) ext_details = ext_details['extension'] self.assertIsNotNone(ext_details) self.assertIn('updated', ext_details.keys()) self.assertIn('name', ext_details.keys()) self.assertIn('description', ext_details.keys()) self.assertIn('links', ext_details.keys()) self.assertIn('alias', ext_details.keys()) self.assertEqual(ext_details['name'], ext_name) self.assertEqual(ext_details['alias'], ext_alias) self.assertEqual(ext_details, ext) # Verify if expected extensions are present in the actual list # of extensions returned, but only for those that have been # enabled via configuration for e in expected_alias: if utils.is_extension_enabled(e, 'network'): self.assertIn(e, actual_alias) tempest-17.2.0/tempest/api/network/test_versions.py0000666000175100017510000000304213207044712022513 0ustar zuulzuul00000000000000# Copyright 2016 VMware, 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.network import base from tempest.lib import decorators class NetworksApiDiscovery(base.BaseNetworkTest): @decorators.attr(type='smoke') @decorators.idempotent_id('cac8a836-c2e0-4304-b556-cd299c7281d1') def test_api_version_resources(self): """Test that GET / returns expected resources. The versions document returned by Neutron returns a few other resources other than just available API versions: it also states the status of each API version and provides links to schema. """ result = self.network_versions_client.list_versions() expected_versions = ('v2.0') expected_resources = ('id', 'links', 'status') received_list = result.values() for item in received_list: for version in item: for resource in expected_resources: self.assertIn(resource, version) self.assertIn(version['id'], expected_versions) tempest-17.2.0/tempest/api/network/test_service_providers.py0000666000175100017510000000223513207044712024403 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.api.network import base from tempest.common import utils from tempest.lib import decorators class ServiceProvidersTest(base.BaseNetworkTest): @decorators.idempotent_id('2cbbeea9-f010-40f6-8df5-4eaa0c918ea6') @testtools.skipUnless( utils.is_extension_enabled('service-type', 'network'), 'service-type extension not enabled.') def test_service_providers_list(self): body = self.service_providers_client.list_service_providers() self.assertIn('service_providers', body) self.assertIsInstance(body['service_providers'], list) tempest-17.2.0/tempest/api/network/__init__.py0000666000175100017510000000000013207044712021332 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/network/test_security_groups_negative.py0000666000175100017510000002601713207044712026002 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base_security_groups as base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class NegativeSecGroupTest(base.BaseSecGroupTest): @classmethod def skip_checks(cls): super(NegativeSecGroupTest, cls).skip_checks() if not utils.is_extension_enabled('security-group', 'network'): msg = "security-group extension not enabled." raise cls.skipException(msg) @decorators.attr(type=['negative']) @decorators.idempotent_id('424fd5c3-9ddc-486a-b45f-39bf0c820fc6') def test_show_non_existent_security_group(self): non_exist_id = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.security_groups_client.show_security_group, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('4c094c09-000b-4e41-8100-9617600c02a6') def test_show_non_existent_security_group_rule(self): non_exist_id = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.security_group_rules_client.show_security_group_rule, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('1f1bb89d-5664-4956-9fcd-83ee0fa603df') def test_delete_non_existent_security_group(self): non_exist_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.security_groups_client.delete_security_group, non_exist_id ) @decorators.attr(type=['negative']) @decorators.idempotent_id('981bdc22-ce48-41ed-900a-73148b583958') def test_create_security_group_rule_with_bad_protocol(self): group_create_body, _ = self._create_security_group() # Create rule with bad protocol name pname = 'bad_protocol_name' self.assertRaises( lib_exc.BadRequest, self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol=pname, direction='ingress', ethertype=self.ethertype) @decorators.attr(type=['negative']) @decorators.idempotent_id('5f8daf69-3c5f-4aaa-88c9-db1d66f68679') def test_create_security_group_rule_with_bad_remote_ip_prefix(self): group_create_body, _ = self._create_security_group() # Create rule with bad remote_ip_prefix prefix = ['192.168.1./24', '192.168.1.1/33', 'bad_prefix', '256'] for remote_ip_prefix in prefix: self.assertRaises( lib_exc.BadRequest, self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='tcp', direction='ingress', ethertype=self.ethertype, remote_ip_prefix=remote_ip_prefix) @decorators.attr(type=['negative']) @decorators.idempotent_id('4bf786fd-2f02-443c-9716-5b98e159a49a') def test_create_security_group_rule_with_non_existent_remote_groupid(self): group_create_body, _ = self._create_security_group() non_exist_id = data_utils.rand_uuid() # Create rule with non existent remote_group_id group_ids = ['bad_group_id', non_exist_id] for remote_group_id in group_ids: self.assertRaises( lib_exc.NotFound, self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='tcp', direction='ingress', ethertype=self.ethertype, remote_group_id=remote_group_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('b5c4b247-6b02-435b-b088-d10d45650881') def test_create_security_group_rule_with_remote_ip_and_group(self): sg1_body, _ = self._create_security_group() sg2_body, _ = self._create_security_group() # Create rule specifying both remote_ip_prefix and remote_group_id prefix = str(self.cidr) self.assertRaises( lib_exc.BadRequest, self.security_group_rules_client.create_security_group_rule, security_group_id=sg1_body['security_group']['id'], protocol='tcp', direction='ingress', ethertype=self.ethertype, remote_ip_prefix=prefix, remote_group_id=sg2_body['security_group']['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('5666968c-fff3-40d6-9efc-df1c8bd01abb') def test_create_security_group_rule_with_bad_ethertype(self): group_create_body, _ = self._create_security_group() # Create rule with bad ethertype ethertype = 'bad_ethertype' self.assertRaises( lib_exc.BadRequest, self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='udp', direction='ingress', ethertype=ethertype) @decorators.attr(type=['negative']) @decorators.idempotent_id('0d9c7791-f2ad-4e2f-ac73-abf2373b0d2d') def test_create_security_group_rule_with_invalid_ports(self): group_create_body, _ = self._create_security_group() # Create rule for tcp protocol with invalid ports states = [(-16, 80, 'Invalid value for port -16'), (80, 79, 'port_range_min must be <= port_range_max'), (80, 65536, 'Invalid value for port 65536'), (None, 6, 'port_range_min must be <= port_range_max'), (-16, 65536, 'Invalid value for port')] for pmin, pmax, msg in states: ex = self.assertRaises( lib_exc.BadRequest, self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='tcp', port_range_min=pmin, port_range_max=pmax, direction='ingress', ethertype=self.ethertype) self.assertIn(msg, str(ex)) # Create rule for icmp protocol with invalid ports states = [(1, 256, 'Invalid value for ICMP code'), (-1, 25, 'Invalid value'), (None, 6, 'ICMP type (port-range-min) is missing'), (300, 1, 'Invalid value for ICMP type')] for pmin, pmax, msg in states: ex = self.assertRaises( lib_exc.BadRequest, self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='icmp', port_range_min=pmin, port_range_max=pmax, direction='ingress', ethertype=self.ethertype) self.assertIn(msg, str(ex)) @decorators.attr(type=['negative']) @decorators.idempotent_id('2323061e-9fbf-4eb0-b547-7e8fafc90849') def test_create_additional_default_security_group_fails(self): # Create security group named 'default', it should be failed. name = 'default' self.assertRaises(lib_exc.Conflict, self.security_groups_client.create_security_group, name=name) @decorators.attr(type=['negative']) @decorators.idempotent_id('966e2b96-023a-11e7-a9e4-fa163e4fa634') def test_create_security_group_update_name_default(self): # Update security group name to 'default', it should be failed. group_create_body, _ = self._create_security_group() self.assertRaises(lib_exc.Conflict, self.security_groups_client.update_security_group, group_create_body['security_group']['id'], name="default") @decorators.attr(type=['negative']) @decorators.idempotent_id('8fde898f-ce88-493b-adc9-4e4692879fc5') def test_create_duplicate_security_group_rule_fails(self): # Create duplicate security group rule, it should fail. body, _ = self._create_security_group() min_port = 66 max_port = 67 # Create a rule with valid params self.security_group_rules_client.create_security_group_rule( security_group_id=body['security_group']['id'], direction='ingress', ethertype=self.ethertype, protocol='tcp', port_range_min=min_port, port_range_max=max_port ) # Try creating the same security group rule, it should fail self.assertRaises( lib_exc.Conflict, self.security_group_rules_client.create_security_group_rule, security_group_id=body['security_group']['id'], protocol='tcp', direction='ingress', ethertype=self.ethertype, port_range_min=min_port, port_range_max=max_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('be308db6-a7cf-4d5c-9baf-71bafd73f35e') def test_create_security_group_rule_with_non_existent_security_group(self): # Create security group rules with not existing security group. non_existent_sg = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.security_group_rules_client.create_security_group_rule, security_group_id=non_existent_sg, direction='ingress', ethertype=self.ethertype) class NegativeSecGroupIPv6Test(NegativeSecGroupTest): _ip_version = 6 @decorators.attr(type=['negative']) @decorators.idempotent_id('7607439c-af73-499e-bf64-f687fd12a842') def test_create_security_group_rule_wrong_ip_prefix_version(self): group_create_body, _ = self._create_security_group() # Create rule with bad remote_ip_prefix pairs = ({'ethertype': 'IPv6', 'ip_prefix': CONF.network.project_network_cidr}, {'ethertype': 'IPv4', 'ip_prefix': CONF.network.project_network_v6_cidr}) for pair in pairs: self.assertRaisesRegex( lib_exc.BadRequest, "Conflicting value ethertype", self.security_group_rules_client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='tcp', direction='ingress', ethertype=pair['ethertype'], remote_ip_prefix=pair['ip_prefix']) tempest-17.2.0/tempest/api/network/test_allowed_address_pair.py0000666000175100017510000001270513207044712025020 0ustar zuulzuul00000000000000# Copyright 2014 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 six from tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF class AllowedAddressPairTestJSON(base.BaseNetworkTest): """Tests the Neutron Allowed Address Pair API extension The following API operations are tested with this extension: create port list ports update port show port v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network-feature-enabled] section of etc/tempest.conf api_extensions """ @classmethod def skip_checks(cls): super(AllowedAddressPairTestJSON, cls).skip_checks() if not utils.is_extension_enabled('allowed-address-pairs', 'network'): msg = "Allowed Address Pairs extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(AllowedAddressPairTestJSON, cls).resource_setup() cls.network = cls.create_network() cls.create_subnet(cls.network) port = cls.create_port(cls.network) cls.ip_address = port['fixed_ips'][0]['ip_address'] cls.mac_address = port['mac_address'] @decorators.idempotent_id('86c3529b-1231-40de-803c-00e40882f043') def test_create_list_port_with_address_pair(self): # Create port with allowed address pair attribute allowed_address_pairs = [{'ip_address': self.ip_address, 'mac_address': self.mac_address}] body = self.ports_client.create_port( network_id=self.network['id'], allowed_address_pairs=allowed_address_pairs) port_id = body['port']['id'] self.addCleanup(self.ports_client.delete_port, port_id) # Confirm port was created with allowed address pair attribute body = self.ports_client.list_ports() ports = body['ports'] port = [p for p in ports if p['id'] == port_id] msg = 'Created port not found in list of ports returned by Neutron' self.assertTrue(port, msg) self._confirm_allowed_address_pair(port[0], self.ip_address) def _update_port_with_address(self, address, mac_address=None, **kwargs): # Create a port without allowed address pair body = self.ports_client.create_port(network_id=self.network['id']) port_id = body['port']['id'] self.addCleanup(self.ports_client.delete_port, port_id) if mac_address is None: mac_address = self.mac_address # Update allowed address pair attribute of port allowed_address_pairs = [{'ip_address': address, 'mac_address': mac_address}] if kwargs: allowed_address_pairs.append(kwargs['allowed_address_pairs']) body = self.ports_client.update_port( port_id, allowed_address_pairs=allowed_address_pairs) allowed_address_pair = body['port']['allowed_address_pairs'] six.assertCountEqual(self, allowed_address_pair, allowed_address_pairs) @decorators.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4') def test_update_port_with_address_pair(self): # Update port with allowed address pair self._update_port_with_address(self.ip_address) @decorators.idempotent_id('4d6d178f-34f6-4bff-a01c-0a2f8fe909e4') def test_update_port_with_cidr_address_pair(self): # Update allowed address pair with cidr self._update_port_with_address(str(self.cidr)) @decorators.idempotent_id('b3f20091-6cd5-472b-8487-3516137df933') def test_update_port_with_multiple_ip_mac_address_pair(self): # Create an ip _address and mac_address through port create resp = self.ports_client.create_port(network_id=self.network['id']) newportid = resp['port']['id'] self.addCleanup(self.ports_client.delete_port, newportid) ipaddress = resp['port']['fixed_ips'][0]['ip_address'] macaddress = resp['port']['mac_address'] # Update allowed address pair port with multiple ip and mac allowed_address_pairs = {'ip_address': ipaddress, 'mac_address': macaddress} self._update_port_with_address( self.ip_address, self.mac_address, allowed_address_pairs=allowed_address_pairs) def _confirm_allowed_address_pair(self, port, ip): msg = 'Port allowed address pairs should not be empty' self.assertTrue(port['allowed_address_pairs'], msg) ip_address = port['allowed_address_pairs'][0]['ip_address'] mac_address = port['allowed_address_pairs'][0]['mac_address'] self.assertEqual(ip_address, ip) self.assertEqual(mac_address, self.mac_address) class AllowedAddressPairIpV6TestJSON(AllowedAddressPairTestJSON): _ip_version = 6 tempest-17.2.0/tempest/api/network/base.py0000666000175100017510000002474213207044725020534 0ustar zuulzuul00000000000000# 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 netaddr from tempest import config from tempest import exceptions from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import exceptions as lib_exc import tempest.test CONF = config.CONF class BaseNetworkTest(tempest.test.BaseTestCase): """Base class for the Neutron tests. Per the Neutron API Guide, API v1.x was removed from the source code tree (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html) Therefore, v2.x of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: project_network_cidr with a block of cidr's from which smaller blocks can be allocated for project networks project_network_mask_bits with the mask bits to be used to partition the block defined by project-network_cidr Finally, it is assumed that the following option is defined in the [service_available] section of etc/tempest.conf neutron as True """ force_tenant_isolation = False credentials = ['primary'] # Default to ipv4. _ip_version = 4 @classmethod def skip_checks(cls): super(BaseNetworkTest, cls).skip_checks() if not CONF.service_available.neutron: raise cls.skipException("Neutron support is required") if cls._ip_version == 6 and not CONF.network_feature_enabled.ipv6: raise cls.skipException("IPv6 Tests are disabled.") @classmethod def setup_credentials(cls): # Create no network resources for these test. cls.set_network_resources() super(BaseNetworkTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(BaseNetworkTest, cls).setup_clients() cls.agents_client = cls.os_primary.network_agents_client cls.network_extensions_client =\ cls.os_primary.network_extensions_client cls.networks_client = cls.os_primary.networks_client cls.routers_client = cls.os_primary.routers_client cls.subnetpools_client = cls.os_primary.subnetpools_client cls.subnets_client = cls.os_primary.subnets_client cls.ports_client = cls.os_primary.ports_client cls.quotas_client = cls.os_primary.network_quotas_client cls.floating_ips_client = cls.os_primary.floating_ips_client cls.security_groups_client = cls.os_primary.security_groups_client cls.security_group_rules_client = ( cls.os_primary.security_group_rules_client) cls.network_versions_client = cls.os_primary.network_versions_client cls.service_providers_client = cls.os_primary.service_providers_client cls.tags_client = cls.os_primary.tags_client @classmethod def resource_setup(cls): super(BaseNetworkTest, cls).resource_setup() cls.networks = [] cls.subnets = [] cls.ports = [] cls.routers = [] cls.floating_ips = [] cls.ethertype = "IPv" + str(cls._ip_version) if cls._ip_version == 4: cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr) cls.mask_bits = CONF.network.project_network_mask_bits elif cls._ip_version == 6: cls.cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr) cls.mask_bits = CONF.network.project_network_v6_mask_bits @classmethod def resource_cleanup(cls): if CONF.service_available.neutron: # Clean up floating IPs for floating_ip in cls.floating_ips: test_utils.call_and_ignore_notfound_exc( cls.floating_ips_client.delete_floatingip, floating_ip['id']) # Clean up ports for port in cls.ports: test_utils.call_and_ignore_notfound_exc( cls.ports_client.delete_port, port['id']) # Clean up routers for router in cls.routers: test_utils.call_and_ignore_notfound_exc( cls.delete_router, router) # Clean up subnets for subnet in cls.subnets: test_utils.call_and_ignore_notfound_exc( cls.subnets_client.delete_subnet, subnet['id']) # Clean up networks for network in cls.networks: test_utils.call_and_ignore_notfound_exc( cls.networks_client.delete_network, network['id']) super(BaseNetworkTest, cls).resource_cleanup() @classmethod def create_network(cls, network_name=None, **kwargs): """Wrapper utility that returns a test network.""" network_name = network_name or data_utils.rand_name( cls.__name__ + '-test-network') body = cls.networks_client.create_network(name=network_name, **kwargs) network = body['network'] cls.networks.append(network) return network @classmethod def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None, ip_version=None, client=None, **kwargs): """Wrapper utility that returns a test subnet.""" # allow tests to use admin client if not client: client = cls.subnets_client # The cidr and mask_bits depend on the ip version. ip_version = ip_version if ip_version is not None else cls._ip_version gateway_not_set = gateway == '' if ip_version == 4: cidr = cidr or netaddr.IPNetwork(CONF.network.project_network_cidr) mask_bits = mask_bits or CONF.network.project_network_mask_bits elif ip_version == 6: cidr = (cidr or netaddr.IPNetwork(CONF.network.project_network_v6_cidr)) mask_bits = mask_bits or CONF.network.project_network_v6_mask_bits # Find a cidr that is not in use yet and create a subnet with it for subnet_cidr in cidr.subnet(mask_bits): if gateway_not_set: gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1) else: gateway_ip = gateway try: body = client.create_subnet( network_id=network['id'], cidr=str(subnet_cidr), ip_version=ip_version, gateway_ip=gateway_ip, **kwargs) break except lib_exc.BadRequest as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise else: message = 'Available CIDR for subnet creation could not be found' raise exceptions.BuildErrorException(message) subnet = body['subnet'] cls.subnets.append(subnet) return subnet @classmethod def create_port(cls, network, **kwargs): """Wrapper utility that returns a test port.""" body = cls.ports_client.create_port(network_id=network['id'], **kwargs) port = body['port'] cls.ports.append(port) return port @classmethod def update_port(cls, port, **kwargs): """Wrapper utility that updates a test port.""" body = cls.ports_client.update_port(port['id'], **kwargs) return body['port'] @classmethod def create_router(cls, router_name=None, admin_state_up=False, external_network_id=None, enable_snat=None, **kwargs): router_name = router_name or data_utils.rand_name( cls.__name__ + "-router") ext_gw_info = {} if external_network_id: ext_gw_info['network_id'] = external_network_id if enable_snat is not None: ext_gw_info['enable_snat'] = enable_snat body = cls.routers_client.create_router( name=router_name, external_gateway_info=ext_gw_info, admin_state_up=admin_state_up, **kwargs) router = body['router'] cls.routers.append(router) return router @classmethod def create_floatingip(cls, external_network_id): """Wrapper utility that returns a test floating IP.""" body = cls.floating_ips_client.create_floatingip( floating_network_id=external_network_id) fip = body['floatingip'] cls.floating_ips.append(fip) return fip @classmethod def create_router_interface(cls, router_id, subnet_id): """Wrapper utility that returns a router interface.""" interface = cls.routers_client.add_router_interface( router_id, subnet_id=subnet_id) return interface @classmethod def delete_router(cls, router): body = cls.ports_client.list_ports(device_id=router['id']) interfaces = body['ports'] for i in interfaces: test_utils.call_and_ignore_notfound_exc( cls.routers_client.remove_router_interface, router['id'], subnet_id=i['fixed_ips'][0]['subnet_id']) cls.routers_client.delete_router(router['id']) class BaseAdminNetworkTest(BaseNetworkTest): credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(BaseAdminNetworkTest, cls).setup_clients() cls.admin_agents_client = cls.os_admin.network_agents_client cls.admin_networks_client = cls.os_admin.networks_client cls.admin_routers_client = cls.os_admin.routers_client cls.admin_subnets_client = cls.os_admin.subnets_client cls.admin_ports_client = cls.os_admin.ports_client cls.admin_quotas_client = cls.os_admin.network_quotas_client cls.admin_floating_ips_client = cls.os_admin.floating_ips_client cls.admin_metering_labels_client = cls.os_admin.metering_labels_client cls.admin_metering_label_rules_client = ( cls.os_admin.metering_label_rules_client) tempest-17.2.0/tempest/api/network/base_security_groups.py0000666000175100017510000000422413207044712024047 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base from tempest.lib.common.utils import data_utils class BaseSecGroupTest(base.BaseNetworkTest): def _create_security_group(self): # Create a security group name = data_utils.rand_name('secgroup-') group_create_body = ( self.security_groups_client.create_security_group(name=name)) self.addCleanup(self._delete_security_group, group_create_body['security_group']['id']) self.assertEqual(group_create_body['security_group']['name'], name) return group_create_body, name def _delete_security_group(self, secgroup_id): self.security_groups_client.delete_security_group(secgroup_id) # Asserting that the security group is not found in the list # after deletion list_body = self.security_groups_client.list_security_groups() secgroup_list = list() for secgroup in list_body['security_groups']: secgroup_list.append(secgroup['id']) self.assertNotIn(secgroup_id, secgroup_list) def _delete_security_group_rule(self, rule_id): self.security_group_rules_client.delete_security_group_rule(rule_id) # Asserting that the security group is not found in the list # after deletion list_body = ( self.security_group_rules_client.list_security_group_rules()) rules_list = list() for rule in list_body['security_group_rules']: rules_list.append(rule['id']) self.assertNotIn(rule_id, rules_list) tempest-17.2.0/tempest/api/network/test_routers_negative.py0000666000175100017510000001303413207044712024232 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class RoutersNegativeTest(base.BaseNetworkTest): @classmethod def skip_checks(cls): super(RoutersNegativeTest, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(RoutersNegativeTest, cls).resource_setup() cls.router = cls.create_router() cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) @decorators.attr(type=['negative']) @decorators.idempotent_id('37a94fc0-a834-45b9-bd23-9a81d2fd1e22') def test_router_add_gateway_invalid_network_returns_404(self): self.assertRaises(lib_exc.NotFound, self.routers_client.update_router, self.router['id'], external_gateway_info={ 'network_id': self.router['id']}) @decorators.attr(type=['negative']) @decorators.idempotent_id('11836a18-0b15-4327-a50b-f0d9dc66bddd') def test_router_add_gateway_net_not_external_returns_400(self): alt_network = self.create_network() sub_cidr = self.cidr.next() self.create_subnet(alt_network, cidr=sub_cidr) self.assertRaises(lib_exc.BadRequest, self.routers_client.update_router, self.router['id'], external_gateway_info={ 'network_id': alt_network['id']}) @decorators.attr(type=['negative']) @decorators.idempotent_id('957751a3-3c68-4fa2-93b6-eb52ea10db6e') def test_add_router_interfaces_on_overlapping_subnets_returns_400(self): network01 = self.create_network( network_name=data_utils.rand_name('router-network01-')) network02 = self.create_network( network_name=data_utils.rand_name('router-network02-')) subnet01 = self.create_subnet(network01) subnet02 = self.create_subnet(network02) interface = self.routers_client.add_router_interface( self.router['id'], subnet_id=subnet01['id']) self.addCleanup(self.routers_client.remove_router_interface, self.router['id'], subnet_id=subnet01['id']) self.assertEqual(subnet01['id'], interface['subnet_id']) self.assertRaises(lib_exc.BadRequest, self.routers_client.add_router_interface, self.router['id'], subnet_id=subnet02['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('04df80f9-224d-47f5-837a-bf23e33d1c20') def test_router_remove_interface_in_use_returns_409(self): self.routers_client.add_router_interface(self.router['id'], subnet_id=self.subnet['id']) self.assertRaises(lib_exc.Conflict, self.routers_client.delete_router, self.router['id']) @decorators.attr(type=['negative']) @decorators.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.routers_client.show_router, router) @decorators.attr(type=['negative']) @decorators.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.routers_client.update_router, router, name="new_name") @decorators.attr(type=['negative']) @decorators.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.routers_client.delete_router, router) class RoutersNegativeIpV6Test(RoutersNegativeTest): _ip_version = 6 class DvrRoutersNegativeTest(base.BaseNetworkTest): @classmethod def skip_checks(cls): super(DvrRoutersNegativeTest, cls).skip_checks() if not utils.is_extension_enabled('dvr', 'network'): msg = "DVR extension not enabled." raise cls.skipException(msg) @decorators.attr(type=['negative']) @decorators.idempotent_id('4990b055-8fc7-48ab-bba7-aa28beaad0b9') def test_router_create_tenant_distributed_returns_forbidden(self): self.assertRaises(lib_exc.Forbidden, self.create_router, distributed=True) tempest-17.2.0/tempest/api/network/test_ports.py0000666000175100017510000003776213207044712022032 0ustar zuulzuul00000000000000# Copyright 2014 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 netaddr import testtools from tempest.api.network import base_security_groups as sec_base from tempest.common import custom_matchers from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class PortsTestJSON(sec_base.BaseSecGroupTest): """Test the following operations for ports: port create port delete port list port show port update """ @classmethod def resource_setup(cls): super(PortsTestJSON, cls).resource_setup() cls.network = cls.create_network() cls.port = cls.create_port(cls.network) def _delete_port(self, port_id): self.ports_client.delete_port(port_id) body = self.ports_client.list_ports() ports_list = body['ports'] self.assertFalse(port_id in [n['id'] for n in ports_list]) @decorators.attr(type='smoke') @decorators.idempotent_id('c72c1c0c-2193-4aca-aaa4-b1442640f51c') def test_create_update_delete_port(self): # Verify port creation body = self.ports_client.create_port(network_id=self.network['id']) port = body['port'] # Schedule port deletion with verification upon test completion self.addCleanup(self._delete_port, port['id']) self.assertTrue(port['admin_state_up']) # Verify port update new_name = "New_Port" body = self.ports_client.update_port(port['id'], name=new_name, admin_state_up=False) updated_port = body['port'] self.assertEqual(updated_port['name'], new_name) self.assertFalse(updated_port['admin_state_up']) @decorators.idempotent_id('67f1b811-f8db-43e2-86bd-72c074d4a42c') def test_create_bulk_port(self): network1 = self.network network2 = self.create_network() network_list = [network1['id'], network2['id']] port_list = [{'network_id': net_id} for net_id in network_list] body = self.ports_client.create_bulk_ports(ports=port_list) created_ports = body['ports'] port1 = created_ports[0] port2 = created_ports[1] self.addCleanup(self._delete_port, port1['id']) self.addCleanup(self._delete_port, port2['id']) self.assertEqual(port1['network_id'], network1['id']) self.assertEqual(port2['network_id'], network2['id']) self.assertTrue(port1['admin_state_up']) self.assertTrue(port2['admin_state_up']) @decorators.attr(type='smoke') @decorators.idempotent_id('0435f278-40ae-48cb-a404-b8a087bc09b1') def test_create_port_in_allowed_allocation_pools(self): network = self.create_network() net_id = network['id'] address = self.cidr address.prefixlen = self.mask_bits if ((address.version == 4 and address.prefixlen >= 30) or (address.version == 6 and address.prefixlen >= 126)): msg = ("Subnet %s isn't large enough for the test" % address.cidr) raise exceptions.InvalidConfiguration(msg) allocation_pools = {'allocation_pools': [{'start': str(address[2]), 'end': str(address[-2])}]} subnet = self.create_subnet(network, cidr=address, mask_bits=address.prefixlen, **allocation_pools) self.addCleanup(self.subnets_client.delete_subnet, subnet['id']) body = self.ports_client.create_port(network_id=net_id) self.addCleanup(self.ports_client.delete_port, body['port']['id']) port = body['port'] ip_address = port['fixed_ips'][0]['ip_address'] start_ip_address = allocation_pools['allocation_pools'][0]['start'] end_ip_address = allocation_pools['allocation_pools'][0]['end'] ip_range = netaddr.IPRange(start_ip_address, end_ip_address) self.assertIn(ip_address, ip_range) @decorators.attr(type='smoke') @decorators.idempotent_id('c9a685bd-e83f-499c-939f-9f7863ca259f') def test_show_port(self): # Verify the details of port body = self.ports_client.show_port(self.port['id']) port = body['port'] self.assertIn('id', port) # NOTE(rfolco): created_at and updated_at may get inconsistent values # due to possible delay between POST request and resource creation. # TODO(rfolco): Neutron Bug #1365341 is fixed, can remove the key # extra_dhcp_opts in the O release (K/L gate jobs still need it). self.assertThat(self.port, custom_matchers.MatchesDictExceptForKeys (port, excluded_keys=['extra_dhcp_opts', 'created_at', 'updated_at'])) @decorators.idempotent_id('45fcdaf2-dab0-4c13-ac6c-fcddfb579dbd') def test_show_port_fields(self): # Verify specific fields of a port fields = ['id', 'mac_address'] body = self.ports_client.show_port(self.port['id'], fields=fields) port = body['port'] self.assertEqual(sorted(port.keys()), sorted(fields)) for field_name in fields: self.assertEqual(port[field_name], self.port[field_name]) @decorators.attr(type='smoke') @decorators.idempotent_id('cf95b358-3e92-4a29-a148-52445e1ac50e') def test_list_ports(self): # Verify the port exists in the list of all ports body = self.ports_client.list_ports() ports = [port['id'] for port in body['ports'] if port['id'] == self.port['id']] self.assertNotEmpty(ports, "Created port not found in the list") @decorators.idempotent_id('e7fe260b-1e79-4dd3-86d9-bec6a7959fc5') def test_port_list_filter_by_ip(self): # Create network and subnet network = self.create_network() subnet = self.create_subnet(network) self.addCleanup(self.subnets_client.delete_subnet, subnet['id']) # Create two ports port_1 = self.ports_client.create_port(network_id=network['id']) self.addCleanup(self.ports_client.delete_port, port_1['port']['id']) port_2 = self.ports_client.create_port(network_id=network['id']) self.addCleanup(self.ports_client.delete_port, port_2['port']['id']) # List ports filtered by fixed_ips port_1_fixed_ip = port_1['port']['fixed_ips'][0]['ip_address'] fixed_ips = 'ip_address=' + port_1_fixed_ip port_list = self.ports_client.list_ports(fixed_ips=fixed_ips) # Check that we got the desired port ports = port_list['ports'] tenant_ids = set([port['tenant_id'] for port in ports]) self.assertEqual(len(tenant_ids), 1, 'Ports from multiple tenants are in the list resp') port_ids = [port['id'] for port in ports] fixed_ips = [port['fixed_ips'] for port in ports] port_ips = [] for addr in fixed_ips: port_ips.extend([port['ip_address'] for port in addr]) port_net_ids = [port['network_id'] for port in ports] self.assertIn(port_1['port']['id'], port_ids) self.assertIn(port_1_fixed_ip, port_ips) self.assertIn(network['id'], port_net_ids) @decorators.idempotent_id('5ad01ed0-0e6e-4c5d-8194-232801b15c72') def test_port_list_filter_by_router_id(self): # Create a router network = self.create_network() self.addCleanup(self.networks_client.delete_network, network['id']) subnet = self.create_subnet(network) self.addCleanup(self.subnets_client.delete_subnet, subnet['id']) router = self.create_router() 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.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'] self.assertEqual(len(ports), 1) self.assertEqual(ports[0]['id'], port['port']['id']) self.assertEqual(ports[0]['device_id'], router['id']) @decorators.idempotent_id('ff7f117f-f034-4e0e-abff-ccef05c454b4') def test_list_ports_fields(self): # Verify specific fields of ports fields = ['id', 'mac_address'] body = self.ports_client.list_ports(fields=fields) ports = body['ports'] self.assertNotEmpty(ports, "Port list returned is empty") # Asserting the fields returned are correct for port in ports: self.assertEqual(sorted(fields), sorted(port.keys())) @decorators.idempotent_id('63aeadd4-3b49-427f-a3b1-19ca81f06270') def test_create_update_port_with_second_ip(self): # Create a network with two subnets network = self.create_network() self.addCleanup(self.networks_client.delete_network, network['id']) subnet_1 = self.create_subnet(network) self.addCleanup(self.subnets_client.delete_subnet, subnet_1['id']) subnet_2 = self.create_subnet(network) self.addCleanup(self.subnets_client.delete_subnet, subnet_2['id']) fixed_ip_1 = [{'subnet_id': subnet_1['id']}] fixed_ip_2 = [{'subnet_id': subnet_2['id']}] fixed_ips = fixed_ip_1 + fixed_ip_2 # Create a port with multiple IP addresses port = self.create_port(network, fixed_ips=fixed_ips) self.addCleanup(self.ports_client.delete_port, port['id']) self.assertEqual(2, len(port['fixed_ips'])) check_fixed_ips = [subnet_1['id'], subnet_2['id']] for item in port['fixed_ips']: self.assertIn(item['subnet_id'], check_fixed_ips) # Update the port to return to a single IP address port = self.update_port(port, fixed_ips=fixed_ip_1) self.assertEqual(1, len(port['fixed_ips'])) # Update the port with a second IP address from second subnet port = self.update_port(port, fixed_ips=fixed_ips) self.assertEqual(2, len(port['fixed_ips'])) def _update_port_with_security_groups(self, security_groups_names): subnet_1 = self.create_subnet(self.network) self.addCleanup(self.subnets_client.delete_subnet, subnet_1['id']) fixed_ip_1 = [{'subnet_id': subnet_1['id']}] security_groups_list = list() sec_grps_client = self.security_groups_client for name in security_groups_names: group_create_body = sec_grps_client.create_security_group( name=name) self.addCleanup(self.security_groups_client.delete_security_group, group_create_body['security_group']['id']) security_groups_list.append(group_create_body['security_group'] ['id']) # Create a port sec_grp_name = data_utils.rand_name('secgroup') security_group = sec_grps_client.create_security_group( name=sec_grp_name) self.addCleanup(self.security_groups_client.delete_security_group, security_group['security_group']['id']) post_body = { "name": data_utils.rand_name('port-'), "security_groups": [security_group['security_group']['id']], "network_id": self.network['id'], "admin_state_up": True, "fixed_ips": fixed_ip_1} body = self.ports_client.create_port(**post_body) self.addCleanup(self.ports_client.delete_port, body['port']['id']) port = body['port'] # Update the port with security groups subnet_2 = self.create_subnet(self.network) fixed_ip_2 = [{'subnet_id': subnet_2['id']}] update_body = {"name": data_utils.rand_name('port-'), "admin_state_up": False, "fixed_ips": fixed_ip_2, "security_groups": security_groups_list} body = self.ports_client.update_port(port['id'], **update_body) port_show = body['port'] # Verify the security groups and other attributes updated to port exclude_keys = set(port_show).symmetric_difference(update_body) exclude_keys.add('fixed_ips') exclude_keys.add('security_groups') self.assertThat(port_show, custom_matchers.MatchesDictExceptForKeys( update_body, exclude_keys)) self.assertEqual(fixed_ip_2[0]['subnet_id'], port_show['fixed_ips'][0]['subnet_id']) for security_group in security_groups_list: self.assertIn(security_group, port_show['security_groups']) @decorators.idempotent_id('58091b66-4ff4-4cc1-a549-05d60c7acd1a') @testtools.skipUnless( utils.is_extension_enabled('security-group', 'network'), 'security-group extension not enabled.') def test_update_port_with_security_group_and_extra_attributes(self): self._update_port_with_security_groups( [data_utils.rand_name('secgroup')]) @decorators.idempotent_id('edf6766d-3d40-4621-bc6e-2521a44c257d') @testtools.skipUnless( utils.is_extension_enabled('security-group', 'network'), 'security-group extension not enabled.') def test_update_port_with_two_security_groups_and_extra_attributes(self): self._update_port_with_security_groups( [data_utils.rand_name('secgroup'), data_utils.rand_name('secgroup')]) @decorators.idempotent_id('13e95171-6cbd-489c-9d7c-3f9c58215c18') def test_create_show_delete_port_user_defined_mac(self): # Create a port for a legal mac body = self.ports_client.create_port(network_id=self.network['id']) old_port = body['port'] free_mac_address = old_port['mac_address'] self.ports_client.delete_port(old_port['id']) # Create a new port with user defined mac body = self.ports_client.create_port(network_id=self.network['id'], mac_address=free_mac_address) self.addCleanup(self.ports_client.delete_port, body['port']['id']) port = body['port'] body = self.ports_client.show_port(port['id']) show_port = body['port'] self.assertEqual(free_mac_address, show_port['mac_address']) @decorators.attr(type='smoke') @decorators.idempotent_id('4179dcb9-1382-4ced-84fe-1b91c54f5735') @testtools.skipUnless( utils.is_extension_enabled('security-group', 'network'), 'security-group extension not enabled.') def test_create_port_with_no_securitygroups(self): network = self.create_network() self.addCleanup(self.networks_client.delete_network, network['id']) subnet = self.create_subnet(network) self.addCleanup(self.subnets_client.delete_subnet, subnet['id']) port = self.create_port(network, security_groups=[]) self.addCleanup(self.ports_client.delete_port, port['id']) self.assertIsNotNone(port['security_groups']) self.assertEmpty(port['security_groups']) class PortsIpV6TestJSON(PortsTestJSON): _ip_version = 6 tempest-17.2.0/tempest/api/network/test_extra_dhcp_options.py0000666000175100017510000000775013207044712024551 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ExtraDHCPOptionsTestJSON(base.BaseNetworkTest): """Tests the following operations with the Extra DHCP Options: port create port list port show port update v2.0 of the Neutron API is assumed. It is also assumed that the Extra DHCP Options extension is enabled in the [network-feature-enabled] section of etc/tempest.conf """ @classmethod def skip_checks(cls): super(ExtraDHCPOptionsTestJSON, cls).skip_checks() if not utils.is_extension_enabled('extra_dhcp_opt', 'network'): msg = "Extra DHCP Options extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(ExtraDHCPOptionsTestJSON, cls).resource_setup() cls.network = cls.create_network() cls.create_subnet(cls.network) cls.port = cls.create_port(cls.network) ip_tftp = ('123.123.123.123' if cls._ip_version == 4 else '2015::dead') ip_server = ('123.123.123.45' if cls._ip_version == 4 else '2015::badd') cls.extra_dhcp_opts = [ {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'}, {'opt_value': ip_tftp, 'opt_name': 'tftp-server'}, {'opt_value': ip_server, 'opt_name': 'server-ip-address'} ] @decorators.idempotent_id('d2c17063-3767-4a24-be4f-a23dbfa133c9') def test_create_list_port_with_extra_dhcp_options(self): # Create a port with Extra DHCP Options body = self.ports_client.create_port( network_id=self.network['id'], extra_dhcp_opts=self.extra_dhcp_opts) port_id = body['port']['id'] self.addCleanup(self.ports_client.delete_port, port_id) # Confirm port created has Extra DHCP Options body = self.ports_client.list_ports() ports = body['ports'] port = [p for p in ports if p['id'] == port_id] self.assertTrue(port) self._confirm_extra_dhcp_options(port[0], self.extra_dhcp_opts) @decorators.idempotent_id('9a6aebf4-86ee-4f47-b07a-7f7232c55607') def test_update_show_port_with_extra_dhcp_options(self): # Update port with extra dhcp options name = data_utils.rand_name('new-port-name') self.ports_client.update_port( self.port['id'], name=name, extra_dhcp_opts=self.extra_dhcp_opts) # Confirm extra dhcp options were added to the port body = self.ports_client.show_port(self.port['id']) self._confirm_extra_dhcp_options(body['port'], self.extra_dhcp_opts) def _confirm_extra_dhcp_options(self, port, extra_dhcp_opts): retrieved = port['extra_dhcp_opts'] self.assertEqual(len(retrieved), len(extra_dhcp_opts)) for retrieved_option in retrieved: for option in extra_dhcp_opts: if (retrieved_option['opt_value'] == option['opt_value'] and retrieved_option['opt_name'] == option['opt_name']): break else: self.fail('Extra DHCP option not found in port %s' % str(retrieved_option)) class ExtraDHCPOptionsIpV6TestJSON(ExtraDHCPOptionsTestJSON): _ip_version = 6 tempest-17.2.0/tempest/api/network/test_networks.py0000666000175100017510000007231513207044712022530 0ustar zuulzuul00000000000000# 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 netaddr import six import testtools from tempest.api.network import base from tempest.common import custom_matchers from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class BaseNetworkTestResources(base.BaseNetworkTest): @classmethod def resource_setup(cls): super(BaseNetworkTestResources, cls).resource_setup() cls.network = cls.create_network() cls.subnet = cls._create_subnet_with_last_subnet_block(cls.network) cls._subnet_data = {6: {'gateway': str(cls._get_gateway_from_tempest_conf(6)), 'allocation_pools': cls._get_allocation_pools_from_gateway(6), 'dns_nameservers': ['2001:4860:4860::8844', '2001:4860:4860::8888'], 'host_routes': [{'destination': '2001::/64', 'nexthop': '2003::1'}], 'new_host_routes': [{'destination': '2001::/64', 'nexthop': '2005::1'}], 'new_dns_nameservers': ['2001:4860:4860::7744', '2001:4860:4860::7888']}, 4: {'gateway': str(cls._get_gateway_from_tempest_conf(4)), 'allocation_pools': cls._get_allocation_pools_from_gateway(4), 'dns_nameservers': ['8.8.4.4', '8.8.8.8'], 'host_routes': [{'destination': '10.20.0.0/32', 'nexthop': '10.100.1.1'}], 'new_host_routes': [{'destination': '10.20.0.0/32', 'nexthop': '10.100.1.2'}], 'new_dns_nameservers': ['7.8.8.8', '7.8.4.4']}} @classmethod def _create_subnet_with_last_subnet_block(cls, network): # Derive last subnet CIDR block from project CIDR and # create the subnet with that derived CIDR subnet_cidr = list(cls.cidr.subnet(cls.mask_bits))[-1] gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1) return cls.create_subnet(network, gateway=gateway_ip, cidr=subnet_cidr, mask_bits=cls.mask_bits) @classmethod def _get_gateway_from_tempest_conf(cls, ip_version): """Return first subnet gateway for configured CIDR """ if ip_version == 4: cidr = netaddr.IPNetwork(CONF.network.project_network_cidr) mask_bits = CONF.network.project_network_mask_bits elif ip_version == 6: cidr = netaddr.IPNetwork(CONF.network.project_network_v6_cidr) mask_bits = CONF.network.project_network_v6_mask_bits if mask_bits >= cidr.prefixlen: return netaddr.IPAddress(cidr) + 1 else: for subnet in cidr.subnet(mask_bits): return netaddr.IPAddress(subnet) + 1 @classmethod def _get_allocation_pools_from_gateway(cls, ip_version): """Return allocation range for subnet of given gateway""" gateway = cls._get_gateway_from_tempest_conf(ip_version) return [{'start': str(gateway + 2), 'end': str(gateway + 6)}] def subnet_dict(self, include_keys): # Return a subnet dict which has include_keys and their corresponding # value from self._subnet_data return dict((key, self._subnet_data[self._ip_version][key]) for key in include_keys) def _compare_resource_attrs(self, actual, expected): exclude_keys = set(actual).symmetric_difference(expected) self.assertThat(actual, custom_matchers.MatchesDictExceptForKeys( expected, exclude_keys)) def _delete_network(self, network): # Deleting network also deletes its subnets if exists self.networks_client.delete_network(network['id']) if network in self.networks: self.networks.remove(network) for subnet in self.subnets: if subnet['network_id'] == network['id']: self.subnets.remove(subnet) def _create_verify_delete_subnet(self, cidr=None, mask_bits=None, **kwargs): network = self.create_network() net_id = network['id'] gateway = kwargs.pop('gateway', None) subnet = self.create_subnet(network, gateway, cidr, mask_bits, **kwargs) compare_args_full = dict(gateway_ip=gateway, cidr=cidr, mask_bits=mask_bits, **kwargs) compare_args = dict((k, v) for k, v in compare_args_full.items() if v is not None) if 'dns_nameservers' in set(subnet).intersection(compare_args): self.assertEqual(sorted(compare_args['dns_nameservers']), sorted(subnet['dns_nameservers'])) del subnet['dns_nameservers'], compare_args['dns_nameservers'] self._compare_resource_attrs(subnet, compare_args) self.networks_client.delete_network(net_id) self.networks.pop() self.subnets.pop() class NetworksTest(BaseNetworkTestResources): """Tests the following operations in the Neutron API: create a network for a project list project's networks show a project network details create a subnet for a project list project's subnets show a project subnet details network update subnet update delete a network also deletes its subnets list external networks All subnet tests are run once with ipv4 and once with ipv6. v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: project_network_cidr with a block of cidr's from which smaller blocks can be allocated for project ipv4 subnets project_network_v6_cidr is the equivalent for ipv6 subnets project_network_mask_bits with the mask bits to be used to partition the block defined by project_network_cidr project_network_v6_mask_bits is the equivalent for ipv6 subnets """ @decorators.attr(type='smoke') @decorators.idempotent_id('0e269138-0da6-4efc-a46d-578161e7b221') def test_create_update_delete_network_subnet(self): # Create a network network = self.create_network() self.addCleanup(self._delete_network, network) net_id = network['id'] self.assertEqual('ACTIVE', network['status']) # Verify network update new_name = "New_network" body = self.networks_client.update_network(net_id, name=new_name) updated_net = body['network'] self.assertEqual(updated_net['name'], new_name) # Find a cidr that is not in use yet and create a subnet with it subnet = self.create_subnet(network) subnet_id = subnet['id'] # Verify subnet update new_name = "New_subnet" body = self.subnets_client.update_subnet(subnet_id, name=new_name) updated_subnet = body['subnet'] self.assertEqual(updated_subnet['name'], new_name) @decorators.attr(type='smoke') @decorators.idempotent_id('2bf13842-c93f-4a69-83ed-717d2ec3b44e') def test_show_network(self): # Verify the details of a network body = self.networks_client.show_network(self.network['id']) network = body['network'] for key in ['id', 'name']: self.assertEqual(network[key], self.network[key]) @decorators.idempotent_id('867819bb-c4b6-45f7-acf9-90edcf70aa5e') def test_show_network_fields(self): # Verify specific fields of a network fields = ['id', 'name'] if utils.is_extension_enabled('net-mtu', 'network'): fields.append('mtu') body = self.networks_client.show_network(self.network['id'], fields=fields) network = body['network'] self.assertEqual(sorted(network.keys()), sorted(fields)) for field_name in fields: self.assertEqual(network[field_name], self.network[field_name]) self.assertNotIn('tenant_id', network) self.assertNotIn('project_id', network) @decorators.attr(type='smoke') @decorators.idempotent_id('f7ffdeda-e200-4a7a-bcbe-05716e86bf43') def test_list_networks(self): # Verify the network exists in the list of all networks body = self.networks_client.list_networks() networks = [network['id'] for network in body['networks'] if network['id'] == self.network['id']] self.assertNotEmpty(networks, "Created network not found in the list") @decorators.idempotent_id('6ae6d24f-9194-4869-9c85-c313cb20e080') def test_list_networks_fields(self): # Verify specific fields of the networks fields = ['id', 'name'] if utils.is_extension_enabled('net-mtu', 'network'): fields.append('mtu') body = self.networks_client.list_networks(fields=fields) networks = body['networks'] self.assertNotEmpty(networks, "Network list returned is empty") for network in networks: self.assertEqual(sorted(network.keys()), sorted(fields)) @decorators.attr(type='smoke') @decorators.idempotent_id('bd635d81-6030-4dd1-b3b9-31ba0cfdf6cc') def test_show_subnet(self): # Verify the details of a subnet body = self.subnets_client.show_subnet(self.subnet['id']) subnet = body['subnet'] self.assertNotEmpty(subnet, "Subnet returned has no fields") for key in ['id', 'cidr']: self.assertIn(key, subnet) self.assertEqual(subnet[key], self.subnet[key]) @decorators.idempotent_id('270fff0b-8bfc-411f-a184-1e8fd35286f0') def test_show_subnet_fields(self): # Verify specific fields of a subnet fields = ['id', 'network_id'] body = self.subnets_client.show_subnet(self.subnet['id'], fields=fields) subnet = body['subnet'] self.assertEqual(sorted(subnet.keys()), sorted(fields)) for field_name in fields: self.assertEqual(subnet[field_name], self.subnet[field_name]) @decorators.attr(type='smoke') @decorators.idempotent_id('db68ba48-f4ea-49e9-81d1-e367f6d0b20a') def test_list_subnets(self): # Verify the subnet exists in the list of all subnets body = self.subnets_client.list_subnets() subnets = [subnet['id'] for subnet in body['subnets'] if subnet['id'] == self.subnet['id']] self.assertNotEmpty(subnets, "Created subnet not found in the list") @decorators.idempotent_id('842589e3-9663-46b0-85e4-7f01273b0412') def test_list_subnets_fields(self): # Verify specific fields of subnets fields = ['id', 'network_id'] body = self.subnets_client.list_subnets(fields=fields) subnets = body['subnets'] self.assertNotEmpty(subnets, "Subnet list returned is empty") for subnet in subnets: self.assertEqual(sorted(subnet.keys()), sorted(fields)) @decorators.idempotent_id('f04f61a9-b7f3-4194-90b2-9bcf660d1bfe') def test_delete_network_with_subnet(self): # Creates a network network = self.create_network() net_id = network['id'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self._delete_network, network) # Find a cidr that is not in use yet and create a subnet with it subnet = self.create_subnet(network) subnet_id = subnet['id'] # Delete network while the subnet still exists self.networks_client.delete_network(net_id) # Verify that the subnet got automatically deleted. self.assertRaises(lib_exc.NotFound, self.subnets_client.show_subnet, subnet_id) @decorators.idempotent_id('d2d596e2-8e76-47a9-ac51-d4648009f4d3') def test_create_delete_subnet_without_gateway(self): self._create_verify_delete_subnet() @decorators.idempotent_id('9393b468-186d-496d-aa36-732348cd76e7') def test_create_delete_subnet_with_gw(self): self._create_verify_delete_subnet( **self.subnet_dict(['gateway'])) @decorators.idempotent_id('bec949c4-3147-4ba6-af5f-cd2306118404') def test_create_delete_subnet_with_allocation_pools(self): self._create_verify_delete_subnet( **self.subnet_dict(['allocation_pools'])) @decorators.idempotent_id('8217a149-0c6c-4cfb-93db-0486f707d13f') def test_create_delete_subnet_with_gw_and_allocation_pools(self): self._create_verify_delete_subnet(**self.subnet_dict( ['gateway', 'allocation_pools'])) @decorators.idempotent_id('d830de0a-be47-468f-8f02-1fd996118289') def test_create_delete_subnet_with_host_routes_and_dns_nameservers(self): self._create_verify_delete_subnet( **self.subnet_dict(['host_routes', 'dns_nameservers'])) @decorators.idempotent_id('94ce038d-ff0a-4a4c-a56b-09da3ca0b55d') def test_create_delete_subnet_with_dhcp_enabled(self): self._create_verify_delete_subnet(enable_dhcp=True) @decorators.idempotent_id('3d3852eb-3009-49ec-97ac-5ce83b73010a') def test_update_subnet_gw_dns_host_routes_dhcp(self): network = self.create_network() self.addCleanup(self._delete_network, network) subnet = self.create_subnet( network, **self.subnet_dict(['gateway', 'host_routes', 'dns_nameservers', 'allocation_pools'])) subnet_id = subnet['id'] new_gateway = str(netaddr.IPAddress( self._subnet_data[self._ip_version]['gateway']) + 1) # Verify subnet update new_host_routes = self._subnet_data[self._ip_version][ 'new_host_routes'] new_dns_nameservers = self._subnet_data[self._ip_version][ 'new_dns_nameservers'] kwargs = {'host_routes': new_host_routes, 'dns_nameservers': new_dns_nameservers, 'gateway_ip': new_gateway, 'enable_dhcp': True} new_name = "New_subnet" body = self.subnets_client.update_subnet(subnet_id, name=new_name, **kwargs) updated_subnet = body['subnet'] kwargs['name'] = new_name self.assertEqual(sorted(updated_subnet['dns_nameservers']), sorted(kwargs['dns_nameservers'])) del subnet['dns_nameservers'], kwargs['dns_nameservers'] self._compare_resource_attrs(updated_subnet, kwargs) @decorators.idempotent_id('a4d9ec4c-0306-4111-a75c-db01a709030b') def test_create_delete_subnet_all_attributes(self): self._create_verify_delete_subnet( enable_dhcp=True, **self.subnet_dict(['gateway', 'host_routes', 'dns_nameservers'])) @decorators.attr(type='smoke') @decorators.idempotent_id('af774677-42a9-4e4b-bb58-16fe6a5bc1ec') @utils.requires_ext(extension='external-net', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_external_network_visibility(self): public_network_id = CONF.network.public_network_id # find external network matching public_network_id body = self.networks_client.list_networks(**{'router:external': True}) external_network = next((network for network in body['networks'] if network['id'] == public_network_id), None) self.assertIsNotNone(external_network, "Public network %s not found " "in external network list" % public_network_id) nonexternal = [net for net in body['networks'] if not net['router:external']] self.assertEmpty(nonexternal, "Found non-external networks" " in filtered list (%s)." % nonexternal) # only check the public network ID because the other networks may # belong to other tests and their state may have changed during this # test body = self.subnets_client.list_subnets(network_id=public_network_id) # check subnet visibility of external_network if external_network['shared']: self.assertNotEmpty(body['subnets'], "Subnets should be visible " "for shared public network %s" % public_network_id) else: self.assertEmpty(body['subnets'], "Subnets should not be visible " "for non-shared public " "network %s" % public_network_id) @decorators.idempotent_id('c72c1c0c-2193-4aca-ccc4-b1442640bbbb') @utils.requires_ext(extension="standard-attr-description", service="network") def test_create_update_network_description(self): body = self.create_network(description='d1') self.assertEqual('d1', body['description']) net_id = body['id'] body = self.networks_client.list_networks(id=net_id)['networks'][0] self.assertEqual('d1', body['description']) body = self.networks_client.update_network(body['id'], description='d2') self.assertEqual('d2', body['network']['description']) body = self.networks_client.list_networks(id=net_id)['networks'][0] self.assertEqual('d2', body['description']) class BulkNetworkOpsTest(base.BaseNetworkTest): """Tests the following operations in the Neutron API: bulk network creation bulk subnet creation bulk port creation list project's networks v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: project_network_cidr with a block of cidr's from which smaller blocks can be allocated for project networks project_network_mask_bits with the mask bits to be used to partition the block defined by project-network_cidr """ def _delete_networks(self, created_networks): for n in created_networks: self.networks_client.delete_network(n['id']) # Asserting that the networks are not found in the list after deletion body = self.networks_client.list_networks() networks_list = [network['id'] for network in body['networks']] for n in created_networks: self.assertNotIn(n['id'], networks_list) def _delete_subnets(self, created_subnets): for n in created_subnets: self.subnets_client.delete_subnet(n['id']) # Asserting that the subnets are not found in the list after deletion body = self.subnets_client.list_subnets() subnets_list = [subnet['id'] for subnet in body['subnets']] for n in created_subnets: self.assertNotIn(n['id'], subnets_list) def _delete_ports(self, created_ports): for n in created_ports: self.ports_client.delete_port(n['id']) # Asserting that the ports are not found in the list after deletion body = self.ports_client.list_ports() ports_list = [port['id'] for port in body['ports']] for n in created_ports: self.assertNotIn(n['id'], ports_list) @decorators.attr(type='smoke') @decorators.idempotent_id('d4f9024d-1e28-4fc1-a6b1-25dbc6fa11e2') def test_bulk_create_delete_network(self): # Creates 2 networks in one request network_list = [{'name': data_utils.rand_name('network-')}, {'name': data_utils.rand_name('network-')}] 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 body = self.networks_client.list_networks() networks_list = [network['id'] for network in body['networks']] for n in created_networks: self.assertIsNotNone(n['id']) self.assertIn(n['id'], networks_list) @decorators.attr(type='smoke') @decorators.idempotent_id('8936533b-c0aa-4f29-8e53-6cc873aec489') def test_bulk_create_delete_subnet(self): networks = [self.create_network(), self.create_network()] # Creates 2 subnets in one request cidrs = [subnet_cidr for subnet_cidr in self.cidr.subnet(self.mask_bits)] names = [data_utils.rand_name('subnet-') for i in range(len(networks))] subnets_list = [] for i in range(len(names)): p1 = { 'network_id': networks[i]['id'], 'cidr': str(cidrs[(i)]), 'name': names[i], 'ip_version': self._ip_version } subnets_list.append(p1) del subnets_list[1]['name'] 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 body = self.subnets_client.list_subnets() subnets_list = [subnet['id'] for subnet in body['subnets']] for n in created_subnets: self.assertIsNotNone(n['id']) self.assertIn(n['id'], subnets_list) @decorators.attr(type='smoke') @decorators.idempotent_id('48037ff2-e889-4c3b-b86a-8e3f34d2d060') def test_bulk_create_delete_port(self): networks = [self.create_network(), self.create_network()] # Creates 2 ports in one request names = [data_utils.rand_name('port-') for i in range(len(networks))] port_list = [] state = [True, False] for i in range(len(names)): p1 = { 'network_id': networks[i]['id'], 'name': names[i], 'admin_state_up': state[i], } port_list.append(p1) del port_list[1]['name'] 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 body = self.ports_client.list_ports() ports_list = [port['id'] for port in body['ports']] for n in created_ports: self.assertIsNotNone(n['id']) self.assertIn(n['id'], ports_list) class BulkNetworkOpsIpV6Test(BulkNetworkOpsTest): _ip_version = 6 class NetworksIpV6Test(NetworksTest): _ip_version = 6 @decorators.idempotent_id('e41a4888-65a6-418c-a095-f7c2ef4ad59a') def test_create_delete_subnet_with_gw(self): net = netaddr.IPNetwork(CONF.network.project_network_v6_cidr) gateway = str(netaddr.IPAddress(net.first + 2)) network = self.create_network() subnet = self.create_subnet(network, gateway) # Verifies Subnet GW in IPv6 self.assertEqual(subnet['gateway_ip'], gateway) @decorators.idempotent_id('ebb4fd95-524f-46af-83c1-0305b239338f') def test_create_delete_subnet_with_default_gw(self): net = netaddr.IPNetwork(CONF.network.project_network_v6_cidr) gateway_ip = str(netaddr.IPAddress(net.first + 1)) network = self.create_network() subnet = self.create_subnet(network) # Verifies Subnet GW in IPv6 self.assertEqual(subnet['gateway_ip'], gateway_ip) @decorators.idempotent_id('a9653883-b2a4-469b-8c3c-4518430a7e55') def test_create_list_subnet_with_no_gw64_one_network(self): network = self.create_network() ipv6_gateway = self.subnet_dict(['gateway'])['gateway'] subnet1 = self.create_subnet(network, ip_version=6, gateway=ipv6_gateway) self.assertEqual(netaddr.IPNetwork(subnet1['cidr']).version, 6, 'The created subnet is not IPv6') subnet2 = self.create_subnet(network, gateway=None, ip_version=4) self.assertEqual(netaddr.IPNetwork(subnet2['cidr']).version, 4, 'The created subnet is not IPv4') # Verifies Subnet GW is set in IPv6 self.assertEqual(subnet1['gateway_ip'], ipv6_gateway) # Verifies Subnet GW is None in IPv4 self.assertIsNone(subnet2['gateway_ip']) # Verifies all 2 subnets in the same network body = self.subnets_client.list_subnets() subnets = [sub['id'] for sub in body['subnets'] if sub['network_id'] == network['id']] test_subnet_ids = [sub['id'] for sub in (subnet1, subnet2)] six.assertCountEqual(self, subnets, test_subnet_ids, 'Subnet are not in the same network') class NetworksIpV6TestAttrs(BaseNetworkTestResources): _ip_version = 6 @classmethod def skip_checks(cls): super(NetworksIpV6TestAttrs, cls).skip_checks() if not CONF.network_feature_enabled.ipv6_subnet_attributes: raise cls.skipException("IPv6 extended attributes for " "subnets not available") @decorators.idempotent_id('da40cd1b-a833-4354-9a85-cd9b8a3b74ca') def test_create_delete_subnet_with_v6_attributes_stateful(self): self._create_verify_delete_subnet( gateway=self._subnet_data[self._ip_version]['gateway'], ipv6_ra_mode='dhcpv6-stateful', ipv6_address_mode='dhcpv6-stateful') @decorators.idempotent_id('176b030f-a923-4040-a755-9dc94329e60c') def test_create_delete_subnet_with_v6_attributes_slaac(self): self._create_verify_delete_subnet( ipv6_ra_mode='slaac', ipv6_address_mode='slaac') @decorators.idempotent_id('7d410310-8c86-4902-adf9-865d08e31adb') def test_create_delete_subnet_with_v6_attributes_stateless(self): self._create_verify_delete_subnet( ipv6_ra_mode='dhcpv6-stateless', ipv6_address_mode='dhcpv6-stateless') def _test_delete_subnet_with_ports(self, mode): """Create subnet and delete it with existing ports""" slaac_network = self.create_network() subnet_slaac = self.create_subnet(slaac_network, **{'ipv6_ra_mode': mode, 'ipv6_address_mode': mode}) port = self.create_port(slaac_network) self.assertIsNotNone(port['fixed_ips'][0]['ip_address']) self.subnets_client.delete_subnet(subnet_slaac['id']) self.subnets.pop() subnets = self.subnets_client.list_subnets() subnet_ids = [subnet['id'] for subnet in subnets['subnets']] self.assertNotIn(subnet_slaac['id'], subnet_ids, "Subnet wasn't deleted") self.assertRaisesRegex( lib_exc.Conflict, "There are one or more ports still in use on the network", self.networks_client.delete_network, slaac_network['id']) @decorators.idempotent_id('88554555-ebf8-41ef-9300-4926d45e06e9') def test_create_delete_slaac_subnet_with_ports(self): """Test deleting subnet with SLAAC ports Create subnet with SLAAC, create ports in network and then you shall be able to delete subnet without port deletion. But you still can not delete the network. """ self._test_delete_subnet_with_ports("slaac") @decorators.idempotent_id('2de6ab5a-fcf0-4144-9813-f91a940291f1') def test_create_delete_stateless_subnet_with_ports(self): """Test deleting subnet with DHCPv6 stateless ports Create subnet with DHCPv6 stateless, create ports in network and then you shall be able to delete subnet without port deletion. But you still can not delete the network. """ self._test_delete_subnet_with_ports("dhcpv6-stateless") tempest-17.2.0/tempest/api/network/test_floating_ips.py0000666000175100017510000002521313207044712023325 0ustar zuulzuul00000000000000# 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. from tempest.api.network import base from tempest.common import utils from tempest.common.utils import net_utils from tempest import config from tempest.lib import decorators CONF = config.CONF class FloatingIPTestJSON(base.BaseNetworkTest): """Tests the following operations in the Neutron API: Create a Floating IP Update a Floating IP Delete a Floating IP List all Floating IPs Show Floating IP details Associate a Floating IP with a port and then delete that port Associate a Floating IP with a port and then with a port on another router v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: public_network_id which is the id for the external network present """ @classmethod def skip_checks(cls): super(FloatingIPTestJSON, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) if not CONF.network.public_network_id: msg = "The public_network_id option must be specified." raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def resource_setup(cls): super(FloatingIPTestJSON, cls).resource_setup() cls.ext_net_id = CONF.network.public_network_id # Create network, subnet, router and add interface cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network, enable_dhcp=False) cls.router = cls.create_router(external_network_id=cls.ext_net_id) cls.create_router_interface(cls.router['id'], cls.subnet['id']) # Create two ports one each for Creation and Updating of floatingIP for i in range(2): cls.create_port(cls.network) @decorators.attr(type='smoke') @decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e8718') def test_create_list_show_update_delete_floating_ip(self): # Creates a floating IP body = self.floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id, port_id=self.ports[0]['id']) created_floating_ip = body['floatingip'] self.addCleanup(self.floating_ips_client.delete_floatingip, created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['tenant_id']) self.assertIsNotNone(created_floating_ip['floating_ip_address']) self.assertEqual(created_floating_ip['port_id'], self.ports[0]['id']) self.assertEqual(created_floating_ip['floating_network_id'], self.ext_net_id) self.assertIn(created_floating_ip['fixed_ip_address'], [ip['ip_address'] for ip in self.ports[0]['fixed_ips']]) # Verifies the details of a floating_ip floating_ip = self.floating_ips_client.show_floatingip( created_floating_ip['id']) shown_floating_ip = floating_ip['floatingip'] self.assertEqual(shown_floating_ip['id'], created_floating_ip['id']) self.assertEqual(shown_floating_ip['floating_network_id'], self.ext_net_id) self.assertEqual(shown_floating_ip['tenant_id'], created_floating_ip['tenant_id']) self.assertEqual(shown_floating_ip['floating_ip_address'], created_floating_ip['floating_ip_address']) self.assertEqual(shown_floating_ip['port_id'], self.ports[0]['id']) # Verify the floating ip exists in the list of all floating_ips floating_ips = self.floating_ips_client.list_floatingips() floatingip_id_list = list() for f in floating_ips['floatingips']: floatingip_id_list.append(f['id']) self.assertIn(created_floating_ip['id'], floatingip_id_list) # Associate floating IP to the other port floating_ip = self.floating_ips_client.update_floatingip( created_floating_ip['id'], port_id=self.ports[1]['id']) updated_floating_ip = floating_ip['floatingip'] self.assertEqual(updated_floating_ip['port_id'], self.ports[1]['id']) self.assertEqual(updated_floating_ip['fixed_ip_address'], self.ports[1]['fixed_ips'][0]['ip_address']) self.assertEqual(updated_floating_ip['router_id'], self.router['id']) # Disassociate floating IP from the port floating_ip = self.floating_ips_client.update_floatingip( created_floating_ip['id'], port_id=None) updated_floating_ip = floating_ip['floatingip'] self.assertIsNone(updated_floating_ip['port_id']) self.assertIsNone(updated_floating_ip['fixed_ip_address']) self.assertIsNone(updated_floating_ip['router_id']) @decorators.idempotent_id('e1f6bffd-442f-4668-b30e-df13f2705e77') def test_floating_ip_delete_port(self): # Create a floating IP body = self.floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id) created_floating_ip = body['floatingip'] self.addCleanup(self.floating_ips_client.delete_floatingip, created_floating_ip['id']) # Create a port port = self.ports_client.create_port(network_id=self.network['id']) created_port = port['port'] floating_ip = self.floating_ips_client.update_floatingip( created_floating_ip['id'], port_id=created_port['id']) # Delete port self.ports_client.delete_port(created_port['id']) # Verifies the details of the floating_ip floating_ip = self.floating_ips_client.show_floatingip( created_floating_ip['id']) shown_floating_ip = floating_ip['floatingip'] # Confirm the fields are back to None self.assertEqual(shown_floating_ip['id'], created_floating_ip['id']) self.assertIsNone(shown_floating_ip['port_id']) self.assertIsNone(shown_floating_ip['fixed_ip_address']) self.assertIsNone(shown_floating_ip['router_id']) @decorators.idempotent_id('1bb2f731-fe5a-4b8c-8409-799ade1bed4d') def test_floating_ip_update_different_router(self): # Associate a floating IP to a port on a router body = self.floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id, port_id=self.ports[1]['id']) created_floating_ip = body['floatingip'] self.addCleanup(self.floating_ips_client.delete_floatingip, created_floating_ip['id']) self.assertEqual(created_floating_ip['router_id'], self.router['id']) network2 = self.create_network() subnet2 = self.create_subnet(network2) router2 = self.create_router(external_network_id=self.ext_net_id) self.create_router_interface(router2['id'], subnet2['id']) port_other_router = self.create_port(network2) # Associate floating IP to the other port on another router floating_ip = self.floating_ips_client.update_floatingip( created_floating_ip['id'], port_id=port_other_router['id']) updated_floating_ip = floating_ip['floatingip'] self.assertEqual(updated_floating_ip['router_id'], router2['id']) self.assertEqual(updated_floating_ip['port_id'], port_other_router['id']) self.assertIsNotNone(updated_floating_ip['fixed_ip_address']) @decorators.attr(type='smoke') @decorators.idempotent_id('36de4bd0-f09c-43e3-a8e1-1decc1ffd3a5') def test_create_floating_ip_specifying_a_fixed_ip_address(self): body = self.floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id, port_id=self.ports[1]['id'], fixed_ip_address=self.ports[1]['fixed_ips'][0]['ip_address']) created_floating_ip = body['floatingip'] self.addCleanup(self.floating_ips_client.delete_floatingip, created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['id']) self.assertEqual(created_floating_ip['fixed_ip_address'], self.ports[1]['fixed_ips'][0]['ip_address']) floating_ip = self.floating_ips_client.update_floatingip( created_floating_ip['id'], port_id=None) self.assertIsNone(floating_ip['floatingip']['port_id']) @decorators.idempotent_id('45c4c683-ea97-41ef-9c51-5e9802f2f3d7') def test_create_update_floatingip_with_port_multiple_ip_address(self): # Find out ips that can be used for tests list_ips = net_utils.get_unused_ip_addresses( self.ports_client, self.subnets_client, self.subnet['network_id'], self.subnet['id'], 2) fixed_ips = [{'ip_address': list_ips[0]}, {'ip_address': list_ips[1]}] # Create port body = self.ports_client.create_port(network_id=self.network['id'], fixed_ips=fixed_ips) port = body['port'] self.addCleanup(self.ports_client.delete_port, port['id']) # Create floating ip body = self.floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id, port_id=port['id'], fixed_ip_address=list_ips[0]) floating_ip = body['floatingip'] self.addCleanup(self.floating_ips_client.delete_floatingip, floating_ip['id']) self.assertIsNotNone(floating_ip['id']) self.assertEqual(floating_ip['fixed_ip_address'], list_ips[0]) # Update floating ip body = self.floating_ips_client.update_floatingip( floating_ip['id'], port_id=port['id'], fixed_ip_address=list_ips[1]) update_floating_ip = body['floatingip'] self.assertEqual(update_floating_ip['fixed_ip_address'], list_ips[1]) tempest-17.2.0/tempest/api/network/test_routers.py0000666000175100017510000003204413207044712022352 0ustar zuulzuul00000000000000# 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 netaddr import testtools from tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class RoutersTest(base.BaseNetworkTest): def _cleanup_router(self, router): self.delete_router(router) self.routers.remove(router) def _create_router(self, name=None, admin_state_up=False, external_network_id=None, enable_snat=None): # associate a cleanup with created routers to avoid quota limits router = self.create_router(name, admin_state_up, external_network_id, enable_snat) self.addCleanup(self._cleanup_router, router) return router def _add_router_interface_with_subnet_id(self, router_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.routers_client.remove_router_interface(router_id, subnet_id=subnet_id) self.assertEqual(subnet_id, body['subnet_id']) @classmethod def skip_checks(cls): super(RoutersTest, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) @decorators.attr(type='smoke') @decorators.idempotent_id('f64403e2-8483-4b34-8ccd-b09a87bcc68c') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_create_show_list_update_delete_router(self): # Create a router name = data_utils.rand_name(self.__class__.__name__ + '-router') router = self._create_router( name=name, admin_state_up=False, external_network_id=CONF.network.public_network_id) self.assertEqual(router['name'], name) self.assertEqual(router['admin_state_up'], False) self.assertEqual( router['external_gateway_info']['network_id'], CONF.network.public_network_id) # Show details of the created router router_show = self.routers_client.show_router( router['id'])['router'] self.assertEqual(router_show['name'], router['name']) self.assertEqual( router_show['external_gateway_info']['network_id'], CONF.network.public_network_id) # List routers and verify if created router is there in response routers = self.routers_client.list_routers()['routers'] self.assertIn(router['id'], map(lambda x: x['id'], routers)) # Update the name of router and verify if it is updated updated_name = 'updated' + router['name'] router_update = self.routers_client.update_router( router['id'], name=updated_name)['router'] self.assertEqual(router_update['name'], updated_name) router_show = self.routers_client.show_router( router['id'])['router'] self.assertEqual(router_show['name'], updated_name) @decorators.attr(type='smoke') @decorators.idempotent_id('b42e6e39-2e37-49cc-a6f4-8467e940900a') def test_add_remove_router_interface_with_subnet_id(self): network = self.create_network() subnet = self.create_subnet(network) router = self._create_router() # Add router interface with 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()) self.assertIn('port_id', interface.keys()) # Verify router id is equal to device id in port details show_port_body = self.ports_client.show_port( interface['port_id']) self.assertEqual(show_port_body['port']['device_id'], router['id']) @decorators.attr(type='smoke') @decorators.idempotent_id('2b7d2f37-6748-4d78-92e5-1d590234f0d5') def test_add_remove_router_interface_with_port_id(self): network = self.create_network() self.create_subnet(network) router = self._create_router() port_body = self.ports_client.create_port( network_id=network['id']) # add router interface to port created above interface = self.routers_client.add_router_interface( router['id'], port_id=port_body['port']['id']) self.addCleanup(self.routers_client.remove_router_interface, router['id'], port_id=port_body['port']['id']) self.assertIn('subnet_id', interface.keys()) self.assertIn('port_id', interface.keys()) # Verify router id is equal to device id in port details show_port_body = self.ports_client.show_port( interface['port_id']) self.assertEqual(show_port_body['port']['device_id'], router['id']) @decorators.idempotent_id('cbe42f84-04c2-11e7-8adb-fa163e4fa634') @utils.requires_ext(extension='ext-gw-mode', service='network') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @decorators.skip_because(bug='1676207') def test_create_router_set_gateway_with_fixed_ip(self): # Don't know public_network_address, so at first create address # from public_network and delete port = self.admin_ports_client.create_port( network_id=CONF.network.public_network_id)['port'] self.admin_ports_client.delete_port(port_id=port['id']) fixed_ip = { 'subnet_id': port['fixed_ips'][0]['subnet_id'], 'ip_address': port['fixed_ips'][0]['ip_address'] } external_gateway_info = { 'network_id': CONF.network.public_network_id, 'external_fixed_ips': [fixed_ip] } # Create a router and set gateway to fixed_ip router = self.admin_routers_client.create_router( external_gateway_info=external_gateway_info)['router'] self.addCleanup(self.admin_routers_client.delete_router, router_id=router['id']) # Examine router's gateway is equal to fixed_ip self.assertEqual(router['external_gateway_info'][ 'external_fixed_ips'][0]['ip_address'], fixed_ip['ip_address']) @decorators.idempotent_id('c86ac3a8-50bd-4b00-a6b8-62af84a0765c') @utils.requires_ext(extension='extraroute', service='network') def test_update_delete_extra_route(self): # Create different cidr for each subnet to avoid cidr duplicate # The cidr starts from project_cidr next_cidr = self.cidr # Prepare to build several routes test_routes = [] routes_num = 4 # Create a router router = self._create_router(admin_state_up=True) self.addCleanup( self._delete_extra_routes, router['id']) # Update router extra route, second ip of the range is # used as next hop for i in range(routes_num): network = self.create_network() subnet = self.create_subnet(network, cidr=next_cidr) next_cidr = next_cidr.next() # Add router interface with subnet id self.create_router_interface(router['id'], subnet['id']) cidr = netaddr.IPNetwork(subnet['cidr']) next_hop = str(cidr[2]) destination = str(subnet['cidr']) test_routes.append( {'nexthop': next_hop, 'destination': destination} ) test_routes.sort(key=lambda x: x['destination']) extra_route = self.routers_client.update_router( 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'])) routes = extra_route['router']['routes'] routes.sort(key=lambda x: x['destination']) # Assert the nexthops & destination for i in range(routes_num): self.assertEqual(test_routes[i]['destination'], routes[i]['destination']) self.assertEqual(test_routes[i]['nexthop'], routes[i]['nexthop']) routes = show_body['router']['routes'] routes.sort(key=lambda x: x['destination']) for i in range(routes_num): self.assertEqual(test_routes[i]['destination'], routes[i]['destination']) self.assertEqual(test_routes[i]['nexthop'], routes[i]['nexthop']) self._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.routers_client.update_router(router_id, routes=None) @decorators.idempotent_id('a8902683-c788-4246-95c7-ad9c6d63a4d9') def test_update_router_admin_state(self): router = self._create_router() self.assertFalse(router['admin_state_up']) # Update router admin state update_body = self.routers_client.update_router(router['id'], admin_state_up=True) self.assertTrue(update_body['router']['admin_state_up']) show_body = self.routers_client.show_router(router['id']) self.assertTrue(show_body['router']['admin_state_up']) @decorators.attr(type='smoke') @decorators.idempotent_id('802c73c9-c937-4cef-824b-2191e24a6aab') def test_add_multiple_router_interfaces(self): network01 = self.create_network( network_name=data_utils.rand_name('router-network01-')) network02 = self.create_network( network_name=data_utils.rand_name('router-network02-')) subnet01 = self.create_subnet(network01) sub02_cidr = self.cidr.next() subnet02 = self.create_subnet(network02, cidr=sub02_cidr) router = self._create_router() interface01 = self._add_router_interface_with_subnet_id(router['id'], subnet01['id']) self._verify_router_interface(router['id'], subnet01['id'], interface01['port_id']) interface02 = self._add_router_interface_with_subnet_id(router['id'], subnet02['id']) self._verify_router_interface(router['id'], subnet02['id'], interface02['port_id']) @decorators.idempotent_id('96522edf-b4b5-45d9-8443-fa11c26e6eff') def test_router_interface_port_update_with_fixed_ip(self): network = self.create_network() subnet = self.create_subnet(network) router = self._create_router() fixed_ip = [{'subnet_id': subnet['id']}] interface = self._add_router_interface_with_subnet_id(router['id'], subnet['id']) self.assertIn('port_id', interface) self.assertIn('subnet_id', interface) port = self.ports_client.show_port(interface['port_id']) self.assertEqual(port['port']['id'], interface['port_id']) router_port = self.ports_client.update_port(port['port']['id'], fixed_ips=fixed_ip) self.assertEqual(subnet['id'], router_port['port']['fixed_ips'][0]['subnet_id']) def _verify_router_interface(self, router_id, subnet_id, port_id): show_port_body = self.ports_client.show_port(port_id) interface_port = show_port_body['port'] self.assertEqual(router_id, interface_port['device_id']) self.assertEqual(subnet_id, interface_port['fixed_ips'][0]['subnet_id']) class RoutersIpV6Test(RoutersTest): _ip_version = 6 tempest-17.2.0/tempest/api/network/test_dhcp_ipv6.py0000666000175100017510000004400313207044712022527 0ustar zuulzuul00000000000000# Copyright 2014 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 random import netaddr from oslo_utils import netutils from tempest.api.network import base from tempest.common.utils import net_info from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class NetworksTestDHCPv6(base.BaseNetworkTest): _ip_version = 6 """Test DHCPv6 specific features using SLAAC, stateless and stateful settings for subnets. Also it shall check dual-stack functionality (IPv4 + IPv6 together). The tests include: generating of SLAAC EUI-64 address in subnets with various settings receiving SLAAC addresses in combinations of various subnets receiving stateful IPv6 addresses addressing in subnets with router """ @classmethod def skip_checks(cls): super(NetworksTestDHCPv6, cls).skip_checks() msg = None if not CONF.network_feature_enabled.ipv6: msg = "IPv6 is not enabled" elif not CONF.network_feature_enabled.ipv6_subnet_attributes: msg = "DHCPv6 attributes are not enabled." if msg: raise cls.skipException(msg) @classmethod def resource_setup(cls): super(NetworksTestDHCPv6, cls).resource_setup() cls.network = cls.create_network() def _remove_from_list_by_index(self, things_list, elem): for index, i in enumerate(things_list): if i['id'] == elem['id']: break del things_list[index] def _clean_network(self): body = self.ports_client.list_ports() ports = body['ports'] for port in ports: if (net_info.is_router_interface_port(port) 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']) self._remove_from_list_by_index(self.ports, port) body = self.subnets_client.list_subnets() subnets = body['subnets'] for subnet in subnets: 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.routers_client.list_routers() routers = body['routers'] for router in routers: if router['id'] in [r['id'] for r in self.routers]: self.routers_client.delete_router(router['id']) self._remove_from_list_by_index(self.routers, router) def _get_ips_from_subnet(self, **kwargs): subnet = self.create_subnet(self.network, **kwargs) port_mac = data_utils.rand_mac_address() port = self.create_port(self.network, mac_address=port_mac) real_ip = next(iter(port['fixed_ips']), None)['ip_address'] eui_ip = str(netutils.get_ipv6_addr_by_EUI64( subnet['cidr'], port_mac)) return real_ip, eui_ip @decorators.idempotent_id('e5517e62-6f16-430d-a672-f80875493d4c') def test_dhcpv6_stateless_eui64(self): # NOTE: When subnets configured with RAs SLAAC (AOM=100) and DHCP # stateless (AOM=110) both for radvd and dnsmasq, port shall receive # IP address calculated from its MAC. for ra_mode, add_mode in ( ('slaac', 'slaac'), ('dhcpv6-stateless', 'dhcpv6-stateless'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} real_ip, eui_ip = self._get_ips_from_subnet(**kwargs) self._clean_network() self.assertEqual(eui_ip, real_ip, ('Real port IP is %s, but shall be %s when ' 'ipv6_ra_mode=%s and ipv6_address_mode=%s') % ( real_ip, eui_ip, ra_mode, add_mode)) @decorators.idempotent_id('ae2f4a5d-03ff-4c42-a3b0-ce2fcb7ea832') def test_dhcpv6_stateless_no_ra(self): # NOTE: When subnets configured with dnsmasq SLAAC and DHCP stateless # and there is no radvd, port shall receive IP address calculated # from its MAC and mask of subnet. for ra_mode, add_mode in ( (None, 'slaac'), (None, 'dhcpv6-stateless'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} kwargs = dict((k, v) for k, v in kwargs.items() if v) real_ip, eui_ip = self._get_ips_from_subnet(**kwargs) self._clean_network() self.assertEqual(eui_ip, real_ip, ('Real port IP %s shall be equal to EUI-64 %s' 'when ipv6_ra_mode=%s,ipv6_address_mode=%s') % ( real_ip, eui_ip, ra_mode if ra_mode else "Off", add_mode if add_mode else "Off")) @decorators.idempotent_id('81f18ef6-95b5-4584-9966-10d480b7496a') def test_dhcpv6_invalid_options(self): """Different configurations for radvd and dnsmasq are not allowed""" for ra_mode, add_mode in ( ('dhcpv6-stateless', 'dhcpv6-stateful'), ('dhcpv6-stateless', 'slaac'), ('slaac', 'dhcpv6-stateful'), ('dhcpv6-stateful', 'dhcpv6-stateless'), ('dhcpv6-stateful', 'slaac'), ('slaac', 'dhcpv6-stateless'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} self.assertRaises(lib_exc.BadRequest, self.create_subnet, self.network, **kwargs) @decorators.idempotent_id('21635b6f-165a-4d42-bf49-7d195e47342f') def test_dhcpv6_stateless_no_ra_no_dhcp(self): # NOTE: If no radvd option and no dnsmasq option is configured # port shall receive IP from fixed IPs list of subnet. real_ip, eui_ip = self._get_ips_from_subnet() self._clean_network() self.assertNotEqual(eui_ip, real_ip, ('Real port IP %s equal to EUI-64 %s when ' 'ipv6_ra_mode=Off and ipv6_address_mode=Off,' 'but shall be taken from fixed IPs') % ( real_ip, eui_ip)) @decorators.idempotent_id('4544adf7-bb5f-4bdc-b769-b3e77026cef2') def test_dhcpv6_two_subnets(self): # NOTE: When one IPv6 subnet configured with dnsmasq SLAAC or DHCP # stateless and other IPv6 is with DHCP stateful, port shall receive # EUI-64 IP addresses from first subnet and DHCP address from second # one. Order of subnet creating should be unimportant. for order in ("slaac_first", "dhcp_first"): for ra_mode, add_mode in ( ('slaac', 'slaac'), ('dhcpv6-stateless', 'dhcpv6-stateless'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} kwargs_dhcp = {'ipv6_address_mode': 'dhcpv6-stateful'} if order == "slaac_first": subnet_slaac = self.create_subnet(self.network, **kwargs) subnet_dhcp = self.create_subnet( self.network, **kwargs_dhcp) else: subnet_dhcp = self.create_subnet( self.network, **kwargs_dhcp) subnet_slaac = self.create_subnet(self.network, **kwargs) port_mac = data_utils.rand_mac_address() eui_ip = str(netutils.get_ipv6_addr_by_EUI64( subnet_slaac['cidr'], port_mac)) port = self.create_port(self.network, mac_address=port_mac) real_ips = dict([(k['subnet_id'], k['ip_address']) for k in port['fixed_ips']]) real_dhcp_ip, real_eui_ip = [real_ips[sub['id']] for sub in [subnet_dhcp, subnet_slaac]] self.ports_client.delete_port(port['id']) self.ports.pop() body = self.ports_client.list_ports() ports_id_list = [i['id'] for i in body['ports']] self.assertNotIn(port['id'], ports_id_list) self._clean_network() self.assertEqual(real_eui_ip, eui_ip, 'Real IP is {0}, but shall be {1}'.format( real_eui_ip, eui_ip)) msg = ('Real IP address is {0} and it is NOT on ' 'subnet {1}'.format(real_dhcp_ip, subnet_dhcp['cidr'])) self.assertIn(netaddr.IPAddress(real_dhcp_ip), netaddr.IPNetwork(subnet_dhcp['cidr']), msg) @decorators.idempotent_id('4256c61d-c538-41ea-9147-3c450c36669e') def test_dhcpv6_64_subnets(self): # NOTE: When one IPv6 subnet configured with dnsmasq SLAAC or DHCP # stateless and other IPv4 is with DHCP of IPv4, port shall receive # EUI-64 IP addresses from first subnet and IPv4 DHCP address from # second one. Order of subnet creating should be unimportant. for order in ("slaac_first", "dhcp_first"): for ra_mode, add_mode in ( ('slaac', 'slaac'), ('dhcpv6-stateless', 'dhcpv6-stateless'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} if order == "slaac_first": subnet_slaac = self.create_subnet(self.network, **kwargs) subnet_dhcp = self.create_subnet( self.network, ip_version=4) else: subnet_dhcp = self.create_subnet( self.network, ip_version=4) subnet_slaac = self.create_subnet(self.network, **kwargs) port_mac = data_utils.rand_mac_address() eui_ip = str(netutils.get_ipv6_addr_by_EUI64( subnet_slaac['cidr'], port_mac)) port = self.create_port(self.network, mac_address=port_mac) real_ips = dict([(k['subnet_id'], k['ip_address']) for k in port['fixed_ips']]) real_dhcp_ip, real_eui_ip = [real_ips[sub['id']] for sub in [subnet_dhcp, subnet_slaac]] self._clean_network() self.assertEqual(real_eui_ip, eui_ip, 'Real IP is {0}, but shall be {1}'.format( real_eui_ip, eui_ip)) msg = ('Real IP address is {0} and it is NOT on ' 'subnet {1}'.format(real_dhcp_ip, subnet_dhcp['cidr'])) self.assertIn(netaddr.IPAddress(real_dhcp_ip), netaddr.IPNetwork(subnet_dhcp['cidr']), msg) @decorators.idempotent_id('4ab211a0-276f-4552-9070-51e27f58fecf') def test_dhcp_stateful(self): # NOTE: With all options below, DHCPv6 shall allocate address from # subnet pool to port. for ra_mode, add_mode in ( ('dhcpv6-stateful', 'dhcpv6-stateful'), ('dhcpv6-stateful', None), (None, 'dhcpv6-stateful'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} kwargs = dict((k, v) for k, v in kwargs.items() if v) subnet = self.create_subnet(self.network, **kwargs) port = self.create_port(self.network) port_ip = next(iter(port['fixed_ips']), None)['ip_address'] self._clean_network() msg = ('Real IP address is {0} and it is NOT on ' 'subnet {1}'.format(port_ip, subnet['cidr'])) self.assertIn(netaddr.IPAddress(port_ip), netaddr.IPNetwork(subnet['cidr']), msg) @decorators.idempotent_id('51a5e97f-f02e-4e4e-9a17-a69811d300e3') def test_dhcp_stateful_fixedips(self): # NOTE: With all options below, port shall be able to get # requested IP from fixed IP range not depending on # DHCP stateful (not SLAAC!) settings configured. for ra_mode, add_mode in ( ('dhcpv6-stateful', 'dhcpv6-stateful'), ('dhcpv6-stateful', None), (None, 'dhcpv6-stateful'), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} kwargs = dict((k, v) for k, v in kwargs.items() if v) subnet = self.create_subnet(self.network, **kwargs) ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"], subnet["allocation_pools"][0]["end"]) ip = netaddr.IPAddress(random.randrange(ip_range.first, ip_range.last)).format() port = self.create_port(self.network, fixed_ips=[{'subnet_id': subnet['id'], 'ip_address': ip}]) port_ip = next(iter(port['fixed_ips']), None)['ip_address'] self._clean_network() self.assertEqual(port_ip, ip, ("Port IP %s is not as fixed IP from " "port create request: %s") % ( port_ip, ip)) @decorators.idempotent_id('98244d88-d990-4570-91d4-6b25d70d08af') def test_dhcp_stateful_fixedips_outrange(self): # NOTE: When port gets IP address from fixed IP range it # shall be checked if it's from subnets range. kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful', 'ipv6_address_mode': 'dhcpv6-stateful'} subnet = self.create_subnet(self.network, **kwargs) ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"], subnet["allocation_pools"][0]["end"]) ip = netaddr.IPAddress(random.randrange( ip_range.last + 1, ip_range.last + 10)).format() self.assertRaises(lib_exc.BadRequest, self.create_port, self.network, fixed_ips=[{'subnet_id': subnet['id'], 'ip_address': ip}]) @decorators.idempotent_id('57b8302b-cba9-4fbb-8835-9168df029051') def test_dhcp_stateful_fixedips_duplicate(self): # NOTE: When port gets IP address from fixed IP range it # shall be checked if it's not duplicate. kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful', 'ipv6_address_mode': 'dhcpv6-stateful'} subnet = self.create_subnet(self.network, **kwargs) ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"], subnet["allocation_pools"][0]["end"]) ip = netaddr.IPAddress(random.randrange( ip_range.first, ip_range.last)).format() self.create_port(self.network, fixed_ips=[ {'subnet_id': subnet['id'], 'ip_address': ip}]) self.assertRaisesRegex(lib_exc.Conflict, "IpAddressAlreadyAllocated|IpAddressInUse", self.create_port, self.network, fixed_ips=[{'subnet_id': subnet['id'], 'ip_address': ip}]) def _create_subnet_router(self, kwargs): subnet = self.create_subnet(self.network, **kwargs) router = self.create_router(admin_state_up=True) port = self.create_router_interface(router['id'], subnet['id']) body = self.ports_client.show_port(port['port_id']) return subnet, body['port'] @decorators.idempotent_id('e98f65db-68f4-4330-9fea-abd8c5192d4d') def test_dhcp_stateful_router(self): # NOTE: With all options below the router interface shall # receive DHCPv6 IP address from allocation pool. for ra_mode, add_mode in ( ('dhcpv6-stateful', 'dhcpv6-stateful'), ('dhcpv6-stateful', None), ): kwargs = {'ipv6_ra_mode': ra_mode, 'ipv6_address_mode': add_mode} kwargs = dict((k, v) for k, v in kwargs.items() if v) subnet, port = self._create_subnet_router(kwargs) port_ip = next(iter(port['fixed_ips']), None)['ip_address'] self._clean_network() self.assertEqual(port_ip, subnet['gateway_ip'], ("Port IP %s is not as first IP from " "subnets allocation pool: %s") % ( port_ip, subnet['gateway_ip'])) def tearDown(self): self._clean_network() super(NetworksTestDHCPv6, self).tearDown() tempest-17.2.0/tempest/api/network/test_subnetpools_extensions.py0000666000175100017510000000614613207044712025507 0ustar zuulzuul00000000000000# Copyright 2015 GlobalLogic. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class SubnetPoolsTestJSON(base.BaseNetworkTest): """Tests the following operations in the subnetpools API: Create a subnet pool. Update a subnet pool. Delete a subnet pool. Lists subnet pool. Show subnet pool details. 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: """ @classmethod def skip_checks(cls): super(SubnetPoolsTestJSON, cls).skip_checks() if not utils.is_extension_enabled('subnet_allocation', 'network'): msg = "subnet_allocation extension not enabled." raise cls.skipException(msg) @decorators.attr(type='smoke') @decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e9811') def test_create_list_show_update_delete_subnetpools(self): subnetpool_name = data_utils.rand_name('subnetpools') # create subnet pool prefix = CONF.network.default_network body = self.subnetpools_client.create_subnetpool(name=subnetpool_name, prefixes=prefix) subnetpool_id = body["subnetpool"]["id"] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.subnetpools_client.delete_subnetpool, subnetpool_id) self.assertEqual(subnetpool_name, body["subnetpool"]["name"]) # get detail about subnet pool body = self.subnetpools_client.show_subnetpool(subnetpool_id) self.assertEqual(subnetpool_name, body["subnetpool"]["name"]) # update the subnet pool subnetpool_name = data_utils.rand_name('subnetpools_update') body = self.subnetpools_client.update_subnetpool(subnetpool_id, name=subnetpool_name) self.assertEqual(subnetpool_name, body["subnetpool"]["name"]) # delete subnet pool body = self.subnetpools_client.delete_subnetpool(subnetpool_id) self.assertRaises(lib_exc.NotFound, self.subnetpools_client.show_subnetpool, subnetpool_id) tempest-17.2.0/tempest/api/network/test_floating_ips_negative.py0000666000175100017510000000760613207044712025215 0ustar zuulzuul00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # Copyright 2014 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 tempest.api.network import base from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class FloatingIPNegativeTestJSON(base.BaseNetworkTest): """Test the following negative operations for floating ips: Create floatingip with a port that is unreachable to external network Create floatingip in private network Associate floatingip with port that is unreachable to external network """ @classmethod def skip_checks(cls): super(FloatingIPNegativeTestJSON, cls).skip_checks() if not utils.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) if not CONF.network.public_network_id: msg = "The public_network_id option must be specified." raise cls.skipException(msg) if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def resource_setup(cls): super(FloatingIPNegativeTestJSON, cls).resource_setup() cls.ext_net_id = CONF.network.public_network_id # Create a network with a subnet connected to a router. cls.network = cls.create_network() subnet = cls.create_subnet(cls.network) router = cls.create_router() cls.create_router_interface(router['id'], subnet['id']) cls.port = cls.create_port(cls.network) @decorators.attr(type=['negative']) @decorators.idempotent_id('22996ea8-4a81-4b27-b6e1-fa5df92fa5e8') def test_create_floatingip_with_port_ext_net_unreachable(self): self.assertRaises( lib_exc.NotFound, self.floating_ips_client.create_floatingip, floating_network_id=self.ext_net_id, port_id=self.port['id'], fixed_ip_address=self.port['fixed_ips'][0] ['ip_address']) @decorators.attr(type=['negative']) @decorators.idempotent_id('50b9aeb4-9f0b-48ee-aa31-fa955a48ff54') def test_create_floatingip_in_private_network(self): self.assertRaises(lib_exc.BadRequest, self.floating_ips_client.create_floatingip, floating_network_id=self.network['id'], port_id=self.port['id'], fixed_ip_address=self.port['fixed_ips'][0] ['ip_address']) @decorators.attr(type=['negative']) @decorators.idempotent_id('6b3b8797-6d43-4191-985c-c48b773eb429') def test_associate_floatingip_port_ext_net_unreachable(self): # Create floating ip body = self.floating_ips_client.create_floatingip( floating_network_id=self.ext_net_id) floating_ip = body['floatingip'] self.addCleanup( self.floating_ips_client.delete_floatingip, floating_ip['id']) # Associate floating IP to the other port self.assertRaises( lib_exc.NotFound, self.floating_ips_client.update_floatingip, floating_ip['id'], port_id=self.port['id'], fixed_ip_address=self.port['fixed_ips'][0]['ip_address']) tempest-17.2.0/tempest/api/identity/0000775000175100017510000000000013207045130017364 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/0000775000175100017510000000000013207045130020454 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/__init__.py0000666000175100017510000000000013207044712022562 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/v3/0000775000175100017510000000000013207045130021004 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/v3/test_policies.py0000666000175100017510000000621213207044712024234 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class PoliciesTestJSON(base.BaseIdentityV3AdminTest): def _delete_policy(self, policy_id): self.policies_client.delete_policy(policy_id) @decorators.idempotent_id('1a0ad286-2d06-4123-ab0d-728893a76201') def test_list_policies(self): # Test to list policies policy_ids = list() fetched_ids = list() for _ in range(3): blob = data_utils.rand_name('BlobName') policy_type = data_utils.rand_name('PolicyType') policy = self.policies_client.create_policy( blob=blob, type=policy_type)['policy'] # Delete the Policy at the end of this method self.addCleanup(self._delete_policy, policy['id']) policy_ids.append(policy['id']) # List and Verify Policies body = self.policies_client.list_policies()['policies'] for p in body: fetched_ids.append(p['id']) missing_pols = [p for p in policy_ids if p not in fetched_ids] self.assertEmpty(missing_pols) @decorators.attr(type='smoke') @decorators.idempotent_id('e544703a-2f03-4cf2-9b0f-350782fdb0d3') def test_create_update_delete_policy(self): # Test to update policy blob = data_utils.rand_name('BlobName') policy_type = data_utils.rand_name('PolicyType') policy = self.policies_client.create_policy(blob=blob, type=policy_type)['policy'] self.addCleanup(self._delete_policy, policy['id']) self.assertIn('type', policy) self.assertIn('blob', policy) self.assertIsNotNone(policy['id']) self.assertEqual(blob, policy['blob']) self.assertEqual(policy_type, policy['type']) # Update policy update_type = data_utils.rand_name('UpdatedPolicyType') data = self.policies_client.update_policy( policy['id'], type=update_type)['policy'] self.assertIn('type', data) # Assertion for updated value with fetched value fetched_policy = self.policies_client.show_policy( policy['id'])['policy'] self.assertIn('id', fetched_policy) self.assertIn('blob', fetched_policy) self.assertIn('type', fetched_policy) self.assertEqual(fetched_policy['id'], policy['id']) self.assertEqual(fetched_policy['blob'], policy['blob']) self.assertEqual(update_type, fetched_policy['type']) tempest-17.2.0/tempest/api/identity/admin/v3/test_roles.py0000666000175100017510000004452713207044712023564 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class RolesV3TestJSON(base.BaseIdentityV3AdminTest): @classmethod def resource_setup(cls): super(RolesV3TestJSON, cls).resource_setup() cls.roles = list() for _ in range(3): role_name = data_utils.rand_name(name='role') role = cls.roles_client.create_role(name=role_name)['role'] cls.roles.append(role) 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.create_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_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.users_client.create_user( name=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.roles_client.create_role( name=data_utils.rand_name('Role'))['role'] @classmethod def resource_cleanup(cls): cls.roles_client.delete_role(cls.role['id']) cls.groups_client.delete_group(cls.group_body['id']) cls.users_client.delete_user(cls.user_body['id']) cls.projects_client.delete_project(cls.project['id']) for role in cls.roles: cls.roles_client.delete_role(role['id']) super(RolesV3TestJSON, cls).resource_cleanup() @decorators.attr(type='smoke') @decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a') def test_role_create_update_show_list(self): r_name = data_utils.rand_name('Role') 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.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.roles_client.show_role(role['id'])['role'] self.assertEqual(new_name, new_role['name']) self.assertEqual(updated_role['id'], new_role['id']) roles = self.roles_client.list_roles()['roles'] self.assertIn(role['id'], [r['id'] for r in roles]) @decorators.idempotent_id('c6b80012-fe4a-498b-9ce8-eb391c05169f') def test_grant_list_revoke_role_to_user_on_project(self): self.roles_client.create_user_role_on_project(self.project['id'], self.user_body['id'], self.role['id']) roles = self.roles_client.list_user_roles_on_project( self.project['id'], self.user_body['id'])['roles'] self.assertEqual(1, len(roles)) self.assertEqual(self.role['id'], roles[0]['id']) self.roles_client.check_user_role_existence_on_project( self.project['id'], self.user_body['id'], self.role['id']) self.roles_client.delete_role_from_user_on_project( self.project['id'], self.user_body['id'], self.role['id']) @decorators.idempotent_id('6c9a2940-3625-43a3-ac02-5dcec62ef3bd') def test_grant_list_revoke_role_to_user_on_domain(self): self.roles_client.create_user_role_on_domain( self.domain['id'], self.user_body['id'], self.role['id']) roles = self.roles_client.list_user_roles_on_domain( self.domain['id'], self.user_body['id'])['roles'] self.assertEqual(1, len(roles)) self.assertEqual(self.role['id'], roles[0]['id']) self.roles_client.check_user_role_existence_on_domain( self.domain['id'], self.user_body['id'], self.role['id']) self.roles_client.delete_role_from_user_on_domain( self.domain['id'], self.user_body['id'], self.role['id']) @decorators.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.roles_client.create_group_role_on_project( self.project['id'], self.group_body['id'], self.role['id']) # List group roles on project roles = self.roles_client.list_group_roles_on_project( self.project['id'], self.group_body['id'])['roles'] self.assertEqual(1, len(roles)) self.assertEqual(self.role['id'], roles[0]['id']) # Add user to group, and insure user has role on project self.groups_client.add_group_user(self.group_body['id'], self.user_body['id']) self.addCleanup(self.groups_client.delete_group_user, self.group_body['id'], self.user_body['id']) body = self.token.auth(user_id=self.user_body['id'], password=self.u_password, user_domain_name=self.domain['name'], project_name=self.project['name'], project_domain_name=self.domain['name']) roles = body['token']['roles'] self.assertEqual(len(roles), 1) self.assertEqual(roles[0]['id'], self.role['id']) 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.roles_client.delete_role_from_group_on_project( self.project['id'], self.group_body['id'], self.role['id']) @decorators.idempotent_id('4bf8a70b-e785-413a-ad53-9f91ce02faa7') def test_grant_list_revoke_role_to_group_on_domain(self): self.roles_client.create_group_role_on_domain( self.domain['id'], self.group_body['id'], self.role['id']) roles = self.roles_client.list_group_roles_on_domain( self.domain['id'], self.group_body['id'])['roles'] self.assertEqual(1, len(roles)) self.assertEqual(self.role['id'], roles[0]['id']) self.roles_client.check_role_from_group_on_domain_existence( self.domain['id'], self.group_body['id'], self.role['id']) self.roles_client.delete_role_from_group_on_domain( self.domain['id'], self.group_body['id'], self.role['id']) @decorators.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94') def test_list_roles(self): # Return a list of all roles body = self.roles_client.list_roles()['roles'] found = [role for role in body if role in self.roles] self.assertEqual(len(found), len(self.roles)) def _create_implied_role(self, prior_role_id, implies_role_id, ignore_not_found=False): self.roles_client.create_role_inference_rule( prior_role_id, implies_role_id) if ignore_not_found: self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.roles_client.delete_role_inference_rule, prior_role_id, implies_role_id) else: self.addCleanup( self.roles_client.delete_role_inference_rule, prior_role_id, implies_role_id) @decorators.idempotent_id('c90c316c-d706-4728-bcba-eb1912081b69') def test_implied_roles_create_check_show_delete(self): prior_role_id = self.roles[0]['id'] implies_role_id = self.roles[1]['id'] # Create an inference rule from prior_role to implies_role self._create_implied_role(prior_role_id, implies_role_id, ignore_not_found=True) # Check if the inference rule exists self.roles_client.check_role_inference_rule( prior_role_id, implies_role_id) # Show the inference rule and check its elements resp_body = self.roles_client.show_role_inference_rule( prior_role_id, implies_role_id) self.assertIn('role_inference', resp_body) role_inference = resp_body['role_inference'] for key1 in ['prior_role', 'implies']: self.assertIn(key1, role_inference) for key2 in ['id', 'links', 'name']: self.assertIn(key2, role_inference[key1]) # Delete the inference rule self.roles_client.delete_role_inference_rule( prior_role_id, implies_role_id) # Check if the inference rule no longer exists self.assertRaises( lib_exc.NotFound, self.roles_client.show_role_inference_rule, prior_role_id, implies_role_id) @decorators.idempotent_id('dc6f5959-b74d-4e30-a9e5-a8255494ff00') def test_roles_hierarchy(self): # Create inference rule from "roles[0]" to "role[1]" self._create_implied_role( self.roles[0]['id'], self.roles[1]['id']) # Create inference rule from "roles[0]" to "role[2]" self._create_implied_role( self.roles[0]['id'], self.roles[2]['id']) # Create inference rule from "roles[2]" to "role" self._create_implied_role( self.roles[2]['id'], self.role['id']) # Listing inferences rules from "roles[2]" should only return "role" rules = self.roles_client.list_role_inferences_rules( self.roles[2]['id'])['role_inference'] self.assertEqual(1, len(rules['implies'])) self.assertEqual(self.role['id'], rules['implies'][0]['id']) # Listing inferences rules from "roles[0]" should return "roles[1]" and # "roles[2]" (only direct rules are listed) rules = self.roles_client.list_role_inferences_rules( self.roles[0]['id'])['role_inference'] implies_ids = [role['id'] for role in rules['implies']] self.assertEqual(2, len(implies_ids)) self.assertIn(self.roles[1]['id'], implies_ids) self.assertIn(self.roles[2]['id'], implies_ids) @decorators.idempotent_id('c8828027-df48-4021-95df-b65b92c7429e') def test_assignments_for_implied_roles_create_delete(self): # Create a grant using "roles[0]" self.roles_client.create_user_role_on_project( self.project['id'], self.user_body['id'], self.roles[0]['id']) self.addCleanup( self.roles_client.delete_role_from_user_on_project, self.project['id'], self.user_body['id'], self.roles[0]['id']) # Create an inference rule from "roles[0]" to "roles[1]" self._create_implied_role(self.roles[0]['id'], self.roles[1]['id'], ignore_not_found=True) # In the effective list of role assignments, both prior role and # implied role should be present. This means that a user can # authenticate using both roles (both roles will be present # in the token). params = {'scope.project.id': self.project['id'], 'user.id': self.user_body['id']} role_assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertEqual(2, len(role_assignments)) roles_ids = [assignment['role']['id'] for assignment in role_assignments] self.assertIn(self.roles[0]['id'], roles_ids) self.assertIn(self.roles[1]['id'], roles_ids) # After deleting the implied role, only the assignment with "roles[0]" # should be present. self.roles_client.delete_role_inference_rule( self.roles[0]['id'], self.roles[1]['id']) role_assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertEqual(1, len(role_assignments)) roles_ids = [assignment['role']['id'] for assignment in role_assignments] self.assertIn(self.roles[0]['id'], roles_ids) @decorators.idempotent_id('d92a41d2-5501-497a-84bb-6e294330e8f8') def test_domain_roles_create_delete(self): domain_role = self.roles_client.create_role( name=data_utils.rand_name('domain_role'), domain_id=self.domain['id'])['role'] self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.roles_client.delete_role, domain_role['id']) domain_roles = self.roles_client.list_roles( domain_id=self.domain['id'])['roles'] self.assertEqual(1, len(domain_roles)) self.assertIn(domain_role, domain_roles) self.roles_client.delete_role(domain_role['id']) domain_roles = self.roles_client.list_roles( domain_id=self.domain['id'])['roles'] self.assertEmpty(domain_roles) @decorators.idempotent_id('eb1e1c24-1bc4-4d47-9748-e127a1852c82') def test_implied_domain_roles(self): # Create two roles in the same domain domain_role1 = self.setup_test_role(domain_id=self.domain['id']) domain_role2 = self.setup_test_role(domain_id=self.domain['id']) # Check if we can create an inference rule from roles in the same # domain self._create_implied_role(domain_role1['id'], domain_role2['id']) # Create another role in a different domain domain2 = self.setup_test_domain() domain_role3 = self.setup_test_role(domain_id=domain2['id']) # Check if we can create cross domain implied roles self._create_implied_role(domain_role1['id'], domain_role3['id']) # Finally, we also should be able to create an implied from a # domain role to a global one self._create_implied_role(domain_role1['id'], self.role['id']) if CONF.identity_feature_enabled.forbid_global_implied_dsr: # The contrary is not true: we can't create an inference rule # from a global role to a domain role self.assertRaises( lib_exc.Forbidden, self.roles_client.create_role_inference_rule, self.role['id'], domain_role1['id']) @decorators.idempotent_id('3859df7e-5b78-4e4d-b10e-214c8953842a') def test_assignments_for_domain_roles(self): domain_role = self.setup_test_role(domain_id=self.domain['id']) # Create a grant using "domain_role" self.roles_client.create_user_role_on_project( self.project['id'], self.user_body['id'], domain_role['id']) self.addCleanup( self.roles_client.delete_role_from_user_on_project, self.project['id'], self.user_body['id'], domain_role['id']) # NOTE(rodrigods): Regular roles would appear in the effective # list of role assignments (meaning the role would be returned in # a token) as a result from the grant above. This is not the case # for domain roles, they should not appear in the effective role # assignments list. params = {'scope.project.id': self.project['id'], 'user.id': self.user_body['id']} role_assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertEmpty(role_assignments) @decorators.idempotent_id('3748c316-c18f-4b08-997b-c60567bc6235') def test_list_all_implied_roles(self): # Create inference rule from "roles[0]" to "roles[1]" self._create_implied_role( self.roles[0]['id'], self.roles[1]['id']) # Create inference rule from "roles[0]" to "roles[2]" self._create_implied_role( self.roles[0]['id'], self.roles[2]['id']) # Create inference rule from "roles[2]" to "role" self._create_implied_role( self.roles[2]['id'], self.role['id']) rules = self.roles_client.list_all_role_inference_rules()[ 'role_inferences'] # Sort the rules by the number of inferences, since there should be 1 # inference between "roles[2]" and "role" and 2 inferences for # "roles[0]": between "roles[1]" and "roles[2]". sorted_rules = sorted(rules, key=lambda r: len(r['implies'])) # Check that 2 sets of rules are returned. self.assertEqual(2, len(sorted_rules)) # Check that only 1 inference rule exists between "roles[2]" and "role" self.assertEqual(1, len(sorted_rules[0]['implies'])) # Check that 2 inference rules exist for "roles[0]": one between # "roles[1]" and one between "roles[2]". self.assertEqual(2, len(sorted_rules[1]['implies'])) # Check that "roles[2]" is the "prior_role" and that "role" is the # "implies" role. self.assertEqual(self.roles[2]['id'], sorted_rules[0]['prior_role']['id']) self.assertEqual(self.role['id'], sorted_rules[0]['implies'][0]['id']) # Check that "roles[0]" is the "prior_role" and that "roles[1]" and # "roles[2]" are the "implies" roles. self.assertEqual(self.roles[0]['id'], sorted_rules[1]['prior_role']['id']) implies_ids = [r['id'] for r in sorted_rules[1]['implies']] self.assertIn(self.roles[1]['id'], implies_ids) self.assertIn(self.roles[2]['id'], implies_ids) tempest-17.2.0/tempest/api/identity/admin/v3/test_projects_negative.py0000666000175100017510000000676113207044712026151 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ProjectsNegativeTestJSON(base.BaseIdentityV3AdminTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('24c49279-45dd-4155-887a-cb738c2385aa') def test_list_projects_by_unauthorized_user(self): # Non-admin user should not be able to list projects self.assertRaises(lib_exc.Forbidden, self.non_admin_projects_client.list_projects) @decorators.attr(type=['negative']) @decorators.idempotent_id('874c3e84-d174-4348-a16b-8c01f599561b') def test_project_create_duplicate(self): # Project names should be unique project_name = data_utils.rand_name('project-dup') self.setup_test_project(name=project_name) self.assertRaises(lib_exc.Conflict, self.projects_client.create_project, project_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('8fba9de2-3e1f-4e77-812a-60cb68f8df13') def test_create_project_by_unauthorized_user(self): # Non-admin user should not be authorized to create a project project_name = data_utils.rand_name('project') self.assertRaises( lib_exc.Forbidden, self.non_admin_projects_client.create_project, project_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('7828db17-95e5-475b-9432-9a51b4aa79a9') def test_create_project_with_empty_name(self): # Project name should not be empty self.assertRaises(lib_exc.BadRequest, self.projects_client.create_project, name='') @decorators.attr(type=['negative']) @decorators.idempotent_id('502b6ceb-b0c8-4422-bf53-f08fdb21e2f0') def test_create_projects_name_length_over_64(self): # Project name length should not be greater than 64 characters project_name = 'a' * 65 self.assertRaises(lib_exc.BadRequest, self.projects_client.create_project, project_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('8d68c012-89e0-4394-8d6b-ccd7196def97') def test_project_delete_by_unauthorized_user(self): # Non-admin user should not be able to delete a project project = self.setup_test_project() self.assertRaises( lib_exc.Forbidden, self.non_admin_projects_client.delete_project, project['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('7965b581-60c1-43b7-8169-95d4ab7fc6fb') def test_delete_non_existent_project(self): # Attempt to delete a non existent project should fail self.assertRaises(lib_exc.NotFound, self.projects_client.delete_project, data_utils.rand_uuid_hex()) tempest-17.2.0/tempest/api/identity/admin/v3/test_default_project_id.py0000666000175100017510000000716213207044712026260 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import clients from tempest import config from tempest.lib import auth from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class TestDefaultProjectId (base.BaseIdentityV3AdminTest): @classmethod def setup_credentials(cls): cls.set_network_resources() super(TestDefaultProjectId, cls).setup_credentials() def _delete_domain(self, domain_id): # It is necessary to disable the domain before deleting, # or else it would result in unauthorized error self.domains_client.update_domain(domain_id, enabled=False) self.domains_client.delete_domain(domain_id) @decorators.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.domains_client.create_domain( name=dom_name)['domain'] dom_id = domain_body['id'] self.addCleanup(self._delete_domain, dom_id) # create a project in the domain proj_body = self.setup_test_project(domain_id=dom_id) proj_id = proj_body['id'] self.assertEqual(proj_body['domain_id'], dom_id, "project " + proj_body['name'] + "doesn't have domain id " + dom_id) # create a user in the domain, with the previous project as his # default project user_name = data_utils.rand_name('user') user_body = self.users_client.create_user( name=user_name, password=user_name, domain_id=dom_id, default_project_id=proj_id)['user'] user_id = user_body['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) # get roles and find the admin role admin_role = self.get_role_by_name(CONF.identity.admin_role) admin_role_id = admin_role['id'] # grant the admin role to the user on his project self.roles_client.create_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, password=user_name, user_domain_name=dom_name) auth_provider = clients.get_auth_provider(creds) creds = auth_provider.fill_credentials() admin_client = clients.Manager(credentials=creds) # verify the user's token and see that it is scoped to the project token, _ = admin_client.auth_provider.get_auth() result = admin_client.identity_v3_client.show_token(token)['token'] self.assertEqual(result['project']['domain']['id'], dom_id) self.assertEqual(result['project']['id'], proj_id) tempest-17.2.0/tempest/api/identity/admin/v3/test_domain_configuration.py0000666000175100017510000001755413207044712026636 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class DomainConfigurationTestJSON(base.BaseIdentityV3AdminTest): custom_config = { "identity": { "driver": "ldap" }, "ldap": { "url": "ldap://myldap.com:389/", "user_tree_dn": "ou=Users,dc=my_new_root,dc=org" } } @classmethod def setup_clients(cls): super(DomainConfigurationTestJSON, cls).setup_clients() cls.client = cls.domain_config_client @classmethod def resource_setup(cls): super(DomainConfigurationTestJSON, cls).resource_setup() cls.group = cls.groups_client.create_group( name=data_utils.rand_name('group'), description=data_utils.rand_name('group-desc'))['group'] @classmethod def resource_cleanup(cls): cls.groups_client.delete_group(cls.group['id']) super(DomainConfigurationTestJSON, cls).resource_cleanup() def _create_domain_and_config(self, config): domain = self.setup_test_domain() config = self.client.create_domain_config(domain['id'], **config)[ 'config'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_domain_config, domain['id']) return domain, config @decorators.idempotent_id('11a02bf0-6f94-4380-b3b0-c8dc18fc0d22') def test_show_default_group_config_and_options(self): # The API supports only the identity and ldap groups. For the ldap # group, a valid value is url or user_tree_dn. For the identity group, # a valid value is driver. # Check that the default config has the identity and ldap groups. config = self.client.show_default_config_settings()['config'] self.assertIsInstance(config, dict) self.assertIn('identity', config) self.assertIn('ldap', config) # Check that the identity group is correct. identity_config = self.client.show_default_group_config('identity')[ 'config'] self.assertIsInstance(identity_config, dict) self.assertIn('identity', identity_config) self.assertIn('driver', identity_config['identity']) self.assertIn('list_limit', identity_config['identity']) # Show each option for the default domain and identity group. for config_opt_name in ['driver', 'list_limit']: retrieved_config_opt = self.client.show_default_group_option( 'identity', config_opt_name)['config'] self.assertIn(config_opt_name, retrieved_config_opt) # Check that the ldap group is correct. ldap_config = self.client.show_default_group_config('ldap')['config'] self.assertIsInstance(ldap_config, dict) self.assertIn('ldap', ldap_config) # Several valid options exist for ldap group. valid_options = ldap_config['ldap'].keys() # Show each option for the default domain and ldap group. for config_opt_name in valid_options: retrieved_config_opt = self.client.show_default_group_option( 'ldap', config_opt_name)['config'] self.assertIn(config_opt_name, retrieved_config_opt) @decorators.idempotent_id('9e3ff13c-f597-4f01-9377-d6c06c2a1477') def test_create_domain_config_and_show_config_groups_and_options(self): domain, created_config = self._create_domain_and_config( self.custom_config) # Check that the entire configuration is correct. self.assertEqual(self.custom_config, created_config) # Check that each configuration group is correct. for group_name in self.custom_config.keys(): group_cfg = self.client.show_domain_group_config( domain['id'], group_name)['config'] self.assertIn(group_name, group_cfg) self.assertEqual(self.custom_config[group_name], group_cfg[group_name]) # Check that each configuration option is correct. for opt_name in self.custom_config[group_name].keys(): group_opt = self.client.show_domain_group_option_config( domain['id'], group_name, opt_name)['config'] self.assertIn(opt_name, group_opt) self.assertEqual(self.custom_config[group_name][opt_name], group_opt[opt_name]) @decorators.idempotent_id('7161023e-5dd0-4612-9da0-1bac6ac30b63') def test_create_update_and_delete_domain_config(self): domain, created_config = self._create_domain_and_config( self.custom_config) new_config = created_config new_config['ldap']['url'] = data_utils.rand_url() # Check that the altered configuration is reflected in updated_config. updated_config = self.client.update_domain_config( domain['id'], **new_config)['config'] self.assertEqual(new_config, updated_config) # Check that showing the domain config shows the altered configuration. retrieved_config = self.client.show_domain_config(domain['id'])[ 'config'] self.assertEqual(new_config, retrieved_config) # Check that deleting a configuration works. self.client.delete_domain_config(domain['id']) self.assertRaises(lib_exc.NotFound, self.client.show_domain_config, domain['id']) @decorators.idempotent_id('c7510fa2-6661-4170-9c6b-4783a80651e9') def test_create_update_and_delete_domain_config_groups_and_opts(self): domain, _ = self._create_domain_and_config(self.custom_config) # Check that updating configuration groups work. new_driver = data_utils.rand_name('driver') new_limit = data_utils.rand_int_id(0, 100) new_group_config = {'identity': {'driver': new_driver, 'list_limit': new_limit}} updated_config = self.client.update_domain_group_config( domain['id'], 'identity', **new_group_config)['config'] self.assertEqual(new_driver, updated_config['identity']['driver']) self.assertEqual(new_limit, updated_config['identity']['list_limit']) # Check that updating individual configuration group options work. new_driver = data_utils.rand_name('driver') updated_config = self.client.update_domain_group_option_config( domain['id'], 'identity', 'driver', driver=new_driver)['config'] self.assertEqual(new_driver, updated_config['identity']['driver']) # Check that deleting individual configuration group options work. self.client.delete_domain_group_option_config( domain['id'], 'identity', 'driver') self.assertRaises(lib_exc.NotFound, self.client.show_domain_group_option_config, domain['id'], 'identity', 'driver') # Check that deleting configuration groups work. self.client.delete_domain_group_config(domain['id'], 'identity') self.assertRaises(lib_exc.NotFound, self.client.show_domain_group_config, domain['id'], 'identity') tempest-17.2.0/tempest/api/identity/admin/v3/test_credentials.py0000666000175100017510000001164613207044712024731 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class CredentialsTestJSON(base.BaseIdentityV3AdminTest): @classmethod def resource_setup(cls): super(CredentialsTestJSON, cls).resource_setup() cls.projects = list() cls.creds_list = [['project_id', 'user_id', 'id'], ['access', 'secret']] 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_password() for _ in range(2): cls.project = cls.projects_client.create_project( data_utils.rand_name('project'), description=data_utils.rand_name('project-desc'))['project'] cls.projects.append(cls.project['id']) cls.user_body = cls.users_client.create_user( name=u_name, description=u_desc, password=u_password, email=u_email, project_id=cls.projects[0])['user'] @classmethod def resource_cleanup(cls): 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() def _delete_credential(self, cred_id): self.creds_client.delete_credential(cred_id) @decorators.attr(type='smoke') @decorators.idempotent_id('7cd59bf9-bda4-4c72-9467-d21cab278355') def test_credentials_create_get_update_delete(self): blob = '{"access": "%s", "secret": "%s"}' % ( data_utils.rand_name('Access'), data_utils.rand_name('Secret')) cred = self.creds_client.create_credential( user_id=self.user_body['id'], project_id=self.projects[0], blob=blob, type='ec2')['credential'] self.addCleanup(self._delete_credential, cred['id']) for value1 in self.creds_list[0]: self.assertIn(value1, cred) for value2 in self.creds_list[1]: self.assertIn(value2, cred['blob']) new_keys = [data_utils.rand_name('NewAccess'), data_utils.rand_name('NewSecret')] blob = '{"access": "%s", "secret": "%s"}' % (new_keys[0], new_keys[1]) update_body = self.creds_client.update_credential( cred['id'], blob=blob, project_id=self.projects[1], type='ec2')['credential'] update_body['blob'] = json.loads(update_body['blob']) self.assertEqual(cred['id'], update_body['id']) self.assertEqual(self.projects[1], update_body['project_id']) self.assertEqual(self.user_body['id'], update_body['user_id']) self.assertEqual(update_body['blob']['access'], new_keys[0]) self.assertEqual(update_body['blob']['secret'], new_keys[1]) get_body = self.creds_client.show_credential(cred['id'])['credential'] get_body['blob'] = json.loads(get_body['blob']) for value1 in self.creds_list[0]: self.assertEqual(update_body[value1], get_body[value1]) for value2 in self.creds_list[1]: self.assertEqual(update_body['blob'][value2], get_body['blob'][value2]) @decorators.idempotent_id('13202c00-0021-42a1-88d4-81b44d448aab') def test_credentials_list_delete(self): created_cred_ids = list() fetched_cred_ids = list() for _ in range(2): blob = '{"access": "%s", "secret": "%s"}' % ( data_utils.rand_name('Access'), data_utils.rand_name('Secret')) cred = self.creds_client.create_credential( user_id=self.user_body['id'], project_id=self.projects[0], blob=blob, type='ec2')['credential'] created_cred_ids.append(cred['id']) self.addCleanup(self._delete_credential, cred['id']) creds = self.creds_client.list_credentials()['credentials'] for i in creds: fetched_cred_ids.append(i['id']) missing_creds = [c for c in created_cred_ids if c not in fetched_cred_ids] self.assertEmpty(missing_creds, "Failed to find cred %s in fetched list" % ', '.join(m_cred for m_cred in missing_creds)) tempest-17.2.0/tempest/api/identity/admin/v3/test_list_users.py0000666000175100017510000001312113207044712024616 0ustar zuulzuul00000000000000# 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.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class UsersV3TestJSON(base.BaseIdentityV3AdminTest): def _list_users_with_params(self, params, key, expected, not_expected): # Helper method to list users filtered with params and # 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.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)) @classmethod def resource_setup(cls): super(UsersV3TestJSON, cls).resource_setup() alt_user = data_utils.rand_name('test_user') alt_password = data_utils.rand_password() cls.alt_email = alt_user + '@testmail.tm' # Create a domain cls.domain = cls.create_domain() # Create user with Domain cls.users = list() u1_name = data_utils.rand_name('test_user') cls.domain_enabled_user = cls.users_client.create_user( name=u1_name, password=alt_password, email=cls.alt_email, domain_id=cls.domain['id'])['user'] cls.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.users_client.create_user( name=u2_name, password=alt_password, email=cls.alt_email, enabled=False)['user'] cls.users.append(cls.non_domain_enabled_user) @classmethod def resource_cleanup(cls): # Cleanup the users created during setup for user in cls.users: cls.users_client.delete_user(user['id']) super(UsersV3TestJSON, cls).resource_cleanup() @decorators.idempotent_id('08f9aabb-dcfe-41d0-8172-82b5fa0bd73d') def test_list_user_domains(self): # List users with domain params = {'domain_id': self.domain['id']} self._list_users_with_params(params, 'domain_id', self.domain_enabled_user, self.non_domain_enabled_user) @decorators.idempotent_id('bff8bf2f-9408-4ef5-b63a-753c8c2124eb') def test_list_users_with_not_enabled(self): # List the users with not enabled params = {'enabled': False} self._list_users_with_params(params, 'enabled', self.non_domain_enabled_user, self.domain_enabled_user) @decorators.idempotent_id('c285bb37-7325-4c02-bff3-3da5d946d683') def test_list_users_with_name(self): # List users with name params = {'name': self.domain_enabled_user['name']} # When domain specific drivers are enabled the operations # of listing all users and listing all groups are not supported, # they need a domain filter to be specified if CONF.identity_feature_enabled.domain_specific_drivers: params['domain_id'] = self.domain_enabled_user['domain_id'] self._list_users_with_params(params, 'name', self.domain_enabled_user, self.non_domain_enabled_user) @decorators.idempotent_id('b30d4651-a2ea-4666-8551-0c0e49692635') def test_list_users(self): # List users # When domain specific drivers are enabled the operations # of listing all users and listing all groups are not supported, # they need a domain filter to be specified if CONF.identity_feature_enabled.domain_specific_drivers: body_enabled_user = self.users_client.list_users( domain_id=self.domain_enabled_user['domain_id'])['users'] body_non_enabled_user = self.users_client.list_users( domain_id=self.non_domain_enabled_user['domain_id'])['users'] body = (body_enabled_user + body_non_enabled_user) else: body = self.users_client.list_users()['users'] fetched_ids = [u['id'] for u in body] missing_users = [u['id'] for u in self.users if u['id'] not in fetched_ids] self.assertEmpty(missing_users, "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) @decorators.idempotent_id('b4baa3ae-ac00-4b4e-9e27-80deaad7771f') def test_get_user(self): # Get a user detail user = self.users_client.show_user(self.users[0]['id'])['user'] self.assertEqual(self.users[0]['id'], user['id']) self.assertEqual(self.users[0]['name'], user['name']) self.assertEqual(self.alt_email, user['email']) self.assertEqual(self.domain['id'], user['domain_id']) tempest-17.2.0/tempest/api/identity/admin/v3/test_services.py0000666000175100017510000001067713207044712024262 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServicesTestJSON(base.BaseIdentityV3AdminTest): def _del_service(self, service_id): # Used for deleting the services created in this class self.services_client.delete_service(service_id) # Checking whether service is deleted successfully self.assertRaises(lib_exc.NotFound, self.services_client.show_service, service_id) @decorators.attr(type='smoke') @decorators.idempotent_id('5193aad5-bcb7-411d-85b0-b3b61b96ef06') def test_create_update_get_service(self): # Creating a Service name = data_utils.rand_name('service') serv_type = data_utils.rand_name('type') desc = data_utils.rand_name('description') create_service = self.services_client.create_service( type=serv_type, name=name, description=desc)['service'] self.addCleanup(self._del_service, create_service['id']) self.assertIsNotNone(create_service['id']) # Verifying response body of create service expected_data = {'name': name, 'type': serv_type, 'description': desc} self.assertDictContainsSubset(expected_data, create_service) # Update description s_id = create_service['id'] resp1_desc = create_service['description'] s_desc2 = data_utils.rand_name('desc2') update_service = self.services_client.update_service( s_id, description=s_desc2)['service'] resp2_desc = update_service['description'] self.assertNotEqual(resp1_desc, resp2_desc) # Get service fetched_service = self.services_client.show_service(s_id)['service'] resp3_desc = fetched_service['description'] self.assertEqual(resp2_desc, resp3_desc) self.assertDictContainsSubset(update_service, fetched_service) @decorators.idempotent_id('d1dcb1a1-2b6b-4da8-bbb8-5532ef6e8269') def test_create_service_without_description(self): # Create a service only with name and type name = data_utils.rand_name('service') serv_type = data_utils.rand_name('type') service = self.services_client.create_service( type=serv_type, name=name)['service'] self.addCleanup(self.services_client.delete_service, service['id']) expected_data = {'name': name, 'type': serv_type} self.assertDictContainsSubset(expected_data, service) @decorators.idempotent_id('e55908e8-360e-439e-8719-c3230a3e179e') def test_list_services(self): # Create, List, Verify and Delete Services service_ids = list() service_types = list() for _ in range(3): name = data_utils.rand_name(self.__class__.__name__ + '-Service') serv_type = data_utils.rand_name(self.__class__.__name__ + '-Type') create_service = self.services_client.create_service( type=serv_type, name=name)['service'] self.addCleanup(self.services_client.delete_service, create_service['id']) service_ids.append(create_service['id']) service_types.append(serv_type) # List and Verify Services services = self.services_client.list_services()['services'] fetched_ids = [service['id'] for service in services] found = [s for s in fetched_ids if s in service_ids] self.assertEqual(len(found), len(service_ids)) # Check that filtering by service type works. for serv_type in service_types: fetched_services = self.services_client.list_services( type=serv_type)['services'] self.assertEqual(1, len(fetched_services)) self.assertEqual(serv_type, fetched_services[0]['type']) tempest-17.2.0/tempest/api/identity/admin/v3/__init__.py0000666000175100017510000000000013207044712023112 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/v3/test_regions.py0000666000175100017510000001267413207044712024104 0ustar zuulzuul00000000000000# 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.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators class RegionsTestJSON(base.BaseIdentityV3AdminTest): @classmethod def setup_clients(cls): super(RegionsTestJSON, cls).setup_clients() cls.client = cls.regions_client @classmethod def resource_setup(cls): super(RegionsTestJSON, cls).resource_setup() cls.setup_regions = list() for _ in range(2): r_description = data_utils.rand_name('description') region = cls.client.create_region( description=r_description)['region'] cls.setup_regions.append(region) @classmethod def resource_cleanup(cls): for r in cls.setup_regions: cls.client.delete_region(r['id']) super(RegionsTestJSON, cls).resource_cleanup() @decorators.idempotent_id('56186092-82e4-43f2-b954-91013218ba42') def test_create_update_get_delete_region(self): # Create region r_description = data_utils.rand_name('description') region = self.client.create_region( description=r_description, parent_region_id=self.setup_regions[0]['id'])['region'] # This test will delete the region as part of the validation # procedure, so it needs a different cleanup method that # would be useful in case the tests fails at any point before # reaching the deletion part. self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_region, region['id']) self.assertEqual(r_description, region['description']) self.assertEqual(self.setup_regions[0]['id'], region['parent_region_id']) # Update region with new description and parent ID r_alt_description = data_utils.rand_name('description') region = self.client.update_region( region['id'], description=r_alt_description, parent_region_id=self.setup_regions[1]['id'])['region'] self.assertEqual(r_alt_description, region['description']) self.assertEqual(self.setup_regions[1]['id'], region['parent_region_id']) # Get the details of region region = self.client.show_region(region['id'])['region'] self.assertEqual(r_alt_description, region['description']) self.assertEqual(self.setup_regions[1]['id'], region['parent_region_id']) # Delete the region self.client.delete_region(region['id']) body = self.client.list_regions()['regions'] regions_list = [r['id'] for r in body] self.assertNotIn(region['id'], regions_list) @decorators.attr(type='smoke') @decorators.idempotent_id('2c12c5b5-efcf-4aa5-90c5-bff1ab0cdbe2') def test_create_region_with_specific_id(self): # Create a region with a specific id r_region_id = data_utils.rand_uuid() r_description = data_utils.rand_name('description') region = self.client.create_region( region_id=r_region_id, description=r_description)['region'] self.addCleanup(self.client.delete_region, region['id']) # Asserting Create Region with specific id response body self.assertEqual(r_region_id, region['id']) self.assertEqual(r_description, region['description']) @decorators.idempotent_id('d180bf99-544a-445c-ad0d-0c0d27663796') def test_list_regions(self): # Get a list of regions fetched_regions = self.client.list_regions()['regions'] missing_regions =\ [e for e in self.setup_regions if e not in fetched_regions] # Asserting List Regions response self.assertEmpty(missing_regions, "Failed to find region %s in fetched list" % ', '.join(str(e) for e in missing_regions)) @decorators.idempotent_id('2d1057cb-bbde-413a-acdf-e2d265284542') def test_list_regions_filter_by_parent_region_id(self): # Add a sub-region to one of the existing test regions r_description = data_utils.rand_name('description') region = self.client.create_region( description=r_description, parent_region_id=self.setup_regions[0]['id'])['region'] self.addCleanup(self.client.delete_region, region['id']) # Get the list of regions filtering with the parent_region_id params = {'parent_region_id': self.setup_regions[0]['id']} fetched_regions = self.client.list_regions(params=params)['regions'] # Asserting list regions response self.assertIn(region, fetched_regions) for r in fetched_regions: self.assertEqual(self.setup_regions[0]['id'], r['parent_region_id']) tempest-17.2.0/tempest/api/identity/admin/v3/test_inherits.py0000666000175100017510000002267313207044712024263 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class InheritsV3TestJSON(base.BaseIdentityV3AdminTest): @classmethod def skip_checks(cls): super(InheritsV3TestJSON, cls).skip_checks() if not utils.is_extension_enabled('OS-INHERIT', 'identity'): raise cls.skipException("Inherits aren't enabled") @classmethod def resource_setup(cls): super(InheritsV3TestJSON, 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.create_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( name=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']) super(InheritsV3TestJSON, 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) @decorators.idempotent_id('4e6f0366-97c8-423c-b2be-41eae6ac91c8') def test_inherit_assign_list_check_revoke_roles_on_domains_user(self): # Create role src_role = self.setup_test_role() # Assign role on domains user self.inherited_roles_client.create_inherited_role_on_domains_user( self.domain['id'], self.user['id'], src_role['id']) # list role on domains user roles = self.inherited_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.inherited_roles_client. check_user_inherited_project_role_on_domain( self.domain['id'], self.user['id'], src_role['id'])) # Revoke role from domains user. self.inherited_roles_client.delete_inherited_role_from_user_on_domain( self.domain['id'], self.user['id'], src_role['id']) @decorators.idempotent_id('c7a8dda2-be50-4fb4-9a9c-e830771078b1') def test_inherit_assign_list_check_revoke_roles_on_domains_group(self): # Create role src_role = self.setup_test_role() # Assign role on domains group self.inherited_roles_client.create_inherited_role_on_domains_group( self.domain['id'], self.group['id'], src_role['id']) # List role on domains group roles = self.inherited_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.inherited_roles_client. check_group_inherited_project_role_on_domain( self.domain['id'], self.group['id'], src_role['id'])) # Revoke role from domains group self.inherited_roles_client.delete_inherited_role_from_group_on_domain( self.domain['id'], self.group['id'], src_role['id']) @decorators.idempotent_id('18b70e45-7687-4b72-8277-b8f1a47d7591') def test_inherit_assign_check_revoke_roles_on_projects_user(self): # Create role src_role = self.setup_test_role() # Assign role on projects user self.inherited_roles_client.create_inherited_role_on_projects_user( self.project['id'], self.user['id'], src_role['id']) # Check role on projects user (self.inherited_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.inherited_roles_client.delete_inherited_role_from_user_on_project( self.project['id'], self.user['id'], src_role['id']) @decorators.idempotent_id('26021436-d5a4-4256-943c-ded01e0d4b45') def test_inherit_assign_check_revoke_roles_on_projects_group(self): # Create role src_role = self.setup_test_role() # Assign role on projects group self.inherited_roles_client.create_inherited_role_on_projects_group( self.project['id'], self.group['id'], src_role['id']) # Check role on projects group (self.inherited_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.inherited_roles_client. delete_inherited_role_from_group_on_project( self.project['id'], self.group['id'], src_role['id'])) @decorators.idempotent_id('3acf666e-5354-42ac-8e17-8b68893bcd36') def test_inherit_assign_list_revoke_user_roles_on_domain(self): # Create role src_role = self.setup_test_role() # Create a project hierarchy leaf_project = self.setup_test_project(domain_id=self.domain['id'], parent_id=self.project['id']) # Assign role on domain self.inherited_roles_client.create_inherited_role_on_domains_user( self.domain['id'], self.user['id'], src_role['id']) # List "effective" role assignments from user on the parent project params = {'scope.project.id': self.project['id'], 'user.id': self.user['id']} assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertNotEmpty(assignments) # List "effective" role assignments from user on the leaf project params['scope.project.id'] = leaf_project['id'] assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertNotEmpty(assignments) # Revoke role from domain self.inherited_roles_client.delete_inherited_role_from_user_on_domain( self.domain['id'], self.user['id'], src_role['id']) # List "effective" role assignments from user on the parent project # should return an empty list params['scope.project.id'] = self.project['id'] assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertEmpty(assignments) # List "effective" role assignments from user on the leaf project # should return an empty list params['scope.project.id'] = leaf_project['id'] assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertEmpty(assignments) @decorators.idempotent_id('9f02ccd9-9b57-46b4-8f77-dd5a736f3a06') def test_inherit_assign_list_revoke_user_roles_on_project_tree(self): # Create role src_role = self.setup_test_role() # Create a project hierarchy leaf_project = self.setup_test_project(domain_id=self.domain['id'], parent_id=self.project['id']) # Assign role on parent project self.inherited_roles_client.create_inherited_role_on_projects_user( self.project['id'], self.user['id'], src_role['id']) # List "effective" role assignments from user on the leaf project params = {'scope.project.id': leaf_project['id'], 'user.id': self.user['id']} assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertNotEmpty(assignments) # Revoke role from parent project self.inherited_roles_client.delete_inherited_role_from_user_on_project( self.project['id'], self.user['id'], src_role['id']) # List "effective" role assignments from user on the leaf project # should return an empty list assignments = self.role_assignments.list_role_assignments( effective=True, **params)['role_assignments'] self.assertEmpty(assignments) tempest-17.2.0/tempest/api/identity/admin/v3/test_endpoints.py0000666000175100017510000002045513207044712024435 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class EndPointsTestJSON(base.BaseIdentityV3AdminTest): @classmethod def setup_clients(cls): super(EndPointsTestJSON, cls).setup_clients() cls.client = cls.endpoints_client @classmethod def resource_setup(cls): super(EndPointsTestJSON, cls).resource_setup() cls.service_ids = list() # Create endpoints so as to use for LIST and GET test cases interfaces = ['public', 'internal'] cls.setup_endpoint_ids = list() for i in range(2): cls._create_service() region = data_utils.rand_name('region') url = data_utils.rand_url() endpoint = cls.client.create_endpoint( service_id=cls.service_ids[i], interface=interfaces[i], url=url, region=region, enabled=True)['endpoint'] cls.setup_endpoint_ids.append(endpoint['id']) @classmethod def _create_service(cls, s_name=None, s_type=None, s_description=None): if s_name is None: s_name = data_utils.rand_name('service') if s_type is None: s_type = data_utils.rand_name('type') if s_description is None: s_description = data_utils.rand_name('description') service_data = ( cls.services_client.create_service(name=s_name, type=s_type, description=s_description)) service = service_data['service'] cls.service_ids.append(service['id']) return service @classmethod def resource_cleanup(cls): for e in cls.setup_endpoint_ids: cls.client.delete_endpoint(e) for s in cls.service_ids: cls.services_client.delete_service(s) super(EndPointsTestJSON, cls).resource_cleanup() @decorators.idempotent_id('c19ecf90-240e-4e23-9966-21cee3f6a618') def test_list_endpoints(self): # Get the list of all the endpoints. fetched_endpoints = self.client.list_endpoints()['endpoints'] fetched_endpoint_ids = [e['id'] for e in fetched_endpoints] # Check that all the created endpoints are present in # "fetched_endpoints". missing_endpoints =\ [e for e in self.setup_endpoint_ids if e not in fetched_endpoint_ids] self.assertEqual(0, len(missing_endpoints), "Failed to find endpoint %s in fetched list" % ', '.join(str(e) for e in missing_endpoints)) # Check that filtering endpoints by service_id works. fetched_endpoints_for_service = self.client.list_endpoints( service_id=self.service_ids[0])['endpoints'] fetched_endpoints_for_alt_service = self.client.list_endpoints( service_id=self.service_ids[1])['endpoints'] # Assert that both filters returned the correct result. self.assertEqual(1, len(fetched_endpoints_for_service)) self.assertEqual(1, len(fetched_endpoints_for_alt_service)) self.assertEqual(set(self.setup_endpoint_ids), set([fetched_endpoints_for_service[0]['id'], fetched_endpoints_for_alt_service[0]['id']])) # Check that filtering endpoints by interface works. fetched_public_endpoints = self.client.list_endpoints( interface='public')['endpoints'] fetched_internal_endpoints = self.client.list_endpoints( interface='internal')['endpoints'] # Check that the expected endpoint_id is present per filter. [0] is # public and [1] is internal. self.assertIn(self.setup_endpoint_ids[0], [e['id'] for e in fetched_public_endpoints]) self.assertIn(self.setup_endpoint_ids[1], [e['id'] for e in fetched_internal_endpoints]) @decorators.idempotent_id('0e2446d2-c1fd-461b-a729-b9e73e3e3b37') def test_create_list_show_delete_endpoint(self): region = data_utils.rand_name('region') url = data_utils.rand_url() interface = 'public' endpoint = self.client.create_endpoint(service_id=self.service_ids[0], interface=interface, url=url, region=region, enabled=True)['endpoint'] self.setup_endpoint_ids.append(endpoint['id']) # Asserting Create Endpoint response body self.assertEqual(region, endpoint['region']) self.assertEqual(url, endpoint['url']) # Checking if created endpoint is present in the list of endpoints fetched_endpoints = self.client.list_endpoints()['endpoints'] fetched_endpoints_id = [e['id'] for e in fetched_endpoints] self.assertIn(endpoint['id'], fetched_endpoints_id) # Show endpoint fetched_endpoint = ( self.client.show_endpoint(endpoint['id'])['endpoint']) # Asserting if the attributes of endpoint are the same self.assertEqual(self.service_ids[0], fetched_endpoint['service_id']) self.assertEqual(interface, fetched_endpoint['interface']) self.assertEqual(url, fetched_endpoint['url']) self.assertEqual(region, fetched_endpoint['region']) self.assertEqual(True, fetched_endpoint['enabled']) # Deleting the endpoint created in this method self.client.delete_endpoint(endpoint['id']) self.setup_endpoint_ids.remove(endpoint['id']) # Checking whether endpoint is deleted successfully fetched_endpoints = self.client.list_endpoints()['endpoints'] fetched_endpoints_id = [e['id'] for e in fetched_endpoints] self.assertNotIn(endpoint['id'], fetched_endpoints_id) @decorators.attr(type='smoke') @decorators.idempotent_id('37e8f15e-ee7c-4657-a1e7-f6b61e375eff') def test_update_endpoint(self): # Creating an endpoint so as to check update endpoint # with new values region1 = data_utils.rand_name('region') url1 = data_utils.rand_url() interface1 = 'public' endpoint_for_update = ( self.client.create_endpoint(service_id=self.service_ids[0], interface=interface1, url=url1, region=region1, enabled=True)['endpoint']) self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id']) # Creating service so as update endpoint with new service ID s_name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') s_description = data_utils.rand_name('description') service2 = self._create_service(s_name=s_name, s_type=s_type, s_description=s_description) # Updating endpoint with new values region2 = data_utils.rand_name('region') url2 = data_utils.rand_url() interface2 = 'internal' endpoint = self.client.update_endpoint(endpoint_for_update['id'], service_id=service2['id'], interface=interface2, url=url2, region=region2, enabled=False)['endpoint'] # Asserting if the attributes of endpoint are updated self.assertEqual(service2['id'], endpoint['service_id']) self.assertEqual(interface2, endpoint['interface']) self.assertEqual(url2, endpoint['url']) self.assertEqual(region2, endpoint['region']) self.assertEqual(False, endpoint['enabled']) tempest-17.2.0/tempest/api/identity/admin/v3/test_endpoint_groups.py0000666000175100017510000001424413207044712025650 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class EndPointGroupsTest(base.BaseIdentityV3AdminTest): @classmethod def setup_clients(cls): super(EndPointGroupsTest, cls).setup_clients() cls.client = cls.endpoint_groups_client @classmethod def resource_setup(cls): super(EndPointGroupsTest, cls).resource_setup() cls.service_ids = list() cls.endpoint_groups = list() # Create endpoint group so as to use it for LIST test service_id = cls._create_service() name = data_utils.rand_name('service_group') description = data_utils.rand_name('description') filters = {'service_id': service_id} endpoint_group = cls.client.create_endpoint_group( name=name, description=description, filters=filters)['endpoint_group'] cls.endpoint_groups.append(endpoint_group) @classmethod def resource_cleanup(cls): for e in cls.endpoint_groups: cls.client.delete_endpoint_group(e['id']) for s in cls.service_ids: cls.services_client.delete_service(s) super(EndPointGroupsTest, cls).resource_cleanup() @classmethod def _create_service(cls): s_name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') s_description = data_utils.rand_name('description') service_data = ( cls.services_client.create_service(name=s_name, type=s_type, description=s_description)) service_id = service_data['service']['id'] cls.service_ids.append(service_id) return service_id @decorators.idempotent_id('7c69e7a1-f865-402d-a2ea-44493017315a') def test_create_list_show_check_delete_endpoint_group(self): service_id = self._create_service() name = data_utils.rand_name('service_group') description = data_utils.rand_name('description') filters = {'service_id': service_id} endpoint_group = self.client.create_endpoint_group( name=name, description=description, filters=filters)['endpoint_group'] self.endpoint_groups.append(endpoint_group) # Asserting created endpoint group response body self.assertIn('id', endpoint_group) self.assertEqual(name, endpoint_group['name']) self.assertEqual(description, endpoint_group['description']) # Checking if endpoint groups are present in the list of endpoints # Note that there are two endpoint groups in the list, one created # in the resource setup, one created in this test case. fetched_endpoints = \ self.client.list_endpoint_groups()['endpoint_groups'] missing_endpoints = \ [e for e in self.endpoint_groups if e not in fetched_endpoints] # Asserting LIST endpoints self.assertEmpty(missing_endpoints, "Failed to find endpoint %s in fetched list" % ', '.join(str(e) for e in missing_endpoints)) # Show endpoint group fetched_endpoint = self.client.show_endpoint_group( endpoint_group['id'])['endpoint_group'] # Asserting if the attributes of endpoint group are the same self.assertEqual(service_id, fetched_endpoint['filters']['service_id']) for attr in ('id', 'name', 'description'): self.assertEqual(endpoint_group[attr], fetched_endpoint[attr]) # Check endpoint group self.client.check_endpoint_group(endpoint_group['id']) # Deleting the endpoint group created in this method self.client.delete_endpoint_group(endpoint_group['id']) self.endpoint_groups.remove(endpoint_group) # Checking whether endpoint group is deleted successfully fetched_endpoints = \ self.client.list_endpoint_groups()['endpoint_groups'] fetched_endpoint_ids = [e['id'] for e in fetched_endpoints] self.assertNotIn(endpoint_group['id'], fetched_endpoint_ids) @decorators.idempotent_id('51c8fc38-fa84-4e76-b5b6-6fc37770fb26') def test_update_endpoint_group(self): # Creating an endpoint group so as to check update endpoint group # with new values service1_id = self._create_service() name = data_utils.rand_name('service_group') description = data_utils.rand_name('description') filters = {'service_id': service1_id} endpoint_group = self.client.create_endpoint_group( name=name, description=description, filters=filters)['endpoint_group'] self.endpoint_groups.append(endpoint_group) # Creating new attr values to update endpoint group service2_id = self._create_service() name2 = data_utils.rand_name('service_group2') description2 = data_utils.rand_name('description2') filters = {'service_id': service2_id} # Updating endpoint group with new attr values updated_endpoint_group = self.client.update_endpoint_group( endpoint_group['id'], name=name2, description=description2, filters=filters)['endpoint_group'] self.assertEqual(name2, updated_endpoint_group['name']) self.assertEqual(description2, updated_endpoint_group['description']) self.assertEqual(service2_id, updated_endpoint_group['filters']['service_id']) tempest-17.2.0/tempest/api/identity/admin/v3/test_trusts.py0000666000175100017510000002734413207044712024002 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import re from oslo_utils import timeutils from tempest.api.identity import base from tempest import clients from tempest.common import credentials_factory as common_creds from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class TrustsV3TestJSON(base.BaseIdentityV3AdminTest): @classmethod def skip_checks(cls): super(TrustsV3TestJSON, cls).skip_checks() if not CONF.identity_feature_enabled.trust: raise cls.skipException("Trusts aren't enabled") def setUp(self): super(TrustsV3TestJSON, self).setUp() # Use alt_username as the trustee self.trust_id = None self.create_trustor_and_roles() self.addCleanup(self.cleanup_user_and_roles) def tearDown(self): if self.trust_id: # Do the delete in tearDown not addCleanup - we want the test to # fail in the event there is a bug which causes undeletable trusts self.delete_trust() super(TrustsV3TestJSON, self).tearDown() def create_trustor_and_roles(self): # create a project that trusts will be granted on trustor_project_name = data_utils.rand_name(name='project') project = self.projects_client.create_project( trustor_project_name, domain_id=CONF.identity.default_domain_id)['project'] self.trustor_project_id = project['id'] self.assertIsNotNone(self.trustor_project_id) # Create a trustor User trustor_username = data_utils.rand_name('user') u_desc = trustor_username + 'description' u_email = trustor_username + '@testmail.xx' trustor_password = data_utils.rand_password() user = self.users_client.create_user( name=trustor_username, description=u_desc, password=trustor_password, email=u_email, project_id=self.trustor_project_id, domain_id=CONF.identity.default_domain_id)['user'] self.trustor_user_id = user['id'] # And two roles, one we'll delegate and one we won't self.delegated_role = data_utils.rand_name('DelegatedRole') self.not_delegated_role = data_utils.rand_name('NotDelegatedRole') role = self.roles_client.create_role(name=self.delegated_role)['role'] self.delegated_role_id = role['id'] role = self.roles_client.create_role( name=self.not_delegated_role)['role'] self.not_delegated_role_id = role['id'] # Assign roles to trustor self.roles_client.create_user_role_on_project( self.trustor_project_id, self.trustor_user_id, self.delegated_role_id) self.roles_client.create_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 self.trustee_user_id = self.get_user_by_name(trustee_username)['id'] self.assertIsNotNone(self.trustee_user_id) # Initialize a new client with the trustor credentials creds = common_creds.get_credentials( identity_version='v3', username=trustor_username, password=trustor_password, user_domain_id=CONF.identity.default_domain_id, tenant_name=trustor_project_name, project_domain_id=CONF.identity.default_domain_id, domain_id=CONF.identity.default_domain_id) os = clients.Manager(credentials=creds) self.trustor_client = os.trusts_client def cleanup_user_and_roles(self): if 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.roles_client.delete_role(self.delegated_role_id) if self.not_delegated_role_id: self.roles_client.delete_role(self.not_delegated_role_id) def create_trust(self, impersonate=True, expires=None): trust_create = self.trustor_client.create_trust( trustor_user_id=self.trustor_user_id, trustee_user_id=self.trustee_user_id, project_id=self.trustor_project_id, roles=[{'name': self.delegated_role}], impersonation=impersonate, expires_at=expires)['trust'] self.trust_id = trust_create['id'] return trust_create def validate_trust(self, trust, impersonate=True, expires=None, summary=False): self.assertIsNotNone(trust['id']) self.assertEqual(impersonate, trust['impersonation']) if expires is not None: # Omit microseconds component of the expiry time trust_expires_at = re.sub(r'\.([0-9]){6}', '', trust['expires_at']) self.assertEqual(expires, trust_expires_at) else: self.assertIsNone(trust['expires_at']) self.assertEqual(self.trustor_user_id, trust['trustor_user_id']) self.assertEqual(self.trustee_user_id, trust['trustee_user_id']) self.assertIn('v3/OS-TRUST/trusts', trust['links']['self']) self.assertEqual(self.trustor_project_id, trust['project_id']) if not summary: self.assertEqual(self.delegated_role, trust['roles'][0]['name']) self.assertEqual(1, len(trust['roles'])) def show_trust(self): trust_get = self.trustor_client.show_trust(self.trust_id)['trust'] return trust_get def validate_role(self, role): self.assertEqual(self.delegated_role_id, role['id']) self.assertEqual(self.delegated_role, role['name']) self.assertIn('v3/roles/%s' % self.delegated_role_id, role['links']['self']) self.assertNotEqual(self.not_delegated_role_id, role['id']) self.assertNotEqual(self.not_delegated_role, role['name']) self.assertNotIn('v3/roles/%s' % self.not_delegated_role_id, role['links']['self']) def check_trust_roles(self): # Check we find the delegated role roles_get = self.trustor_client.list_trust_roles( self.trust_id)['roles'] self.assertEqual(1, len(roles_get)) self.validate_role(roles_get[0]) role_get = self.trustor_client.show_trust_role( self.trust_id, self.delegated_role_id)['role'] self.validate_role(role_get) role_get = self.trustor_client.check_trust_role( self.trust_id, self.delegated_role_id) # And that we don't find not_delegated_role self.assertRaises(lib_exc.NotFound, self.trustor_client.show_trust_role, self.trust_id, self.not_delegated_role_id) self.assertRaises(lib_exc.NotFound, self.trustor_client.check_trust_role, self.trust_id, self.not_delegated_role_id) def delete_trust(self): self.trustor_client.delete_trust(self.trust_id) self.assertRaises(lib_exc.NotFound, self.trustor_client.show_trust, self.trust_id) self.trust_id = None @decorators.idempotent_id('5a0a91a4-baef-4a14-baba-59bf4d7fcace') def test_trust_impersonate(self): # Test case to check we can create, get and delete a trust # updates are not supported for trusts trust = self.create_trust() self.validate_trust(trust) trust_get = self.show_trust() self.validate_trust(trust_get) self.check_trust_roles() @decorators.idempotent_id('ed2a8779-a7ac-49dc-afd7-30f32f936ed2') def test_trust_noimpersonate(self): # Test case to check we can create, get and delete a trust # with impersonation=False trust = self.create_trust(impersonate=False) self.validate_trust(trust, impersonate=False) trust_get = self.show_trust() self.validate_trust(trust_get, impersonate=False) self.check_trust_roles() @decorators.idempotent_id('0ed14b66-cefd-4b5c-a964-65759453e292') def test_trust_expire(self): # Test case to check we can create, get and delete a trust # with an expiry specified expires_at = timeutils.utcnow() + datetime.timedelta(hours=1) # NOTE(ylobankov) In some cases the expiry time may be rounded up # because of microseconds. In fact, it depends on database and its # version. At least MySQL 5.6.16 does this. # For example, when creating a trust, we will set the expiry time of # the trust to 2015-02-17T17:34:01.907051Z. However, if we make a GET # request on the trust, the response will contain the time rounded up # to 2015-02-17T17:34:02.000000Z. That is why we set microsecond to # 0 when we invoke isoformat to avoid problems with rounding. expires_at = expires_at.replace(microsecond=0) # NOTE(ekhugen) Python datetime does not support military timezones # since we used UTC we'll add the Z so our compare works. expires_str = expires_at.isoformat() + 'Z' trust = self.create_trust(expires=expires_str) self.validate_trust(trust, expires=expires_str) trust_get = self.show_trust() self.validate_trust(trust_get, expires=expires_str) self.check_trust_roles() @decorators.idempotent_id('3e48f95d-e660-4fa9-85e0-5a3d85594384') def test_trust_expire_invalid(self): # Test case to check we can check an invalid expiry time # is rejected with the correct error # with an expiry specified expires_str = 'bad.123Z' self.assertRaises(lib_exc.BadRequest, self.create_trust, expires=expires_str) @decorators.idempotent_id('6268b345-87ca-47c0-9ce3-37792b43403a') def test_get_trusts_query(self): self.create_trust() trusts_get = self.trustor_client.list_trusts( trustor_user_id=self.trustor_user_id)['trusts'] self.assertEqual(1, len(trusts_get)) self.validate_trust(trusts_get[0], summary=True) @decorators.attr(type='smoke') @decorators.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d') def test_get_trusts_all(self): # Simple function that can be used for cleanup def set_scope(auth_provider, scope): auth_provider.scope = scope self.create_trust() # Listing trusts can be done by trustor, by trustee, or without # any filter if scoped to a project, so we must ensure token scope is # project for this test. original_scope = self.os_admin.auth_provider.scope set_scope(self.os_admin.auth_provider, 'project') self.addCleanup(set_scope, self.os_admin.auth_provider, original_scope) 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)) self.validate_trust(trusts[0], summary=True) tempest-17.2.0/tempest/api/identity/admin/v3/test_groups.py0000666000175100017510000001430213207044712023743 0ustar zuulzuul00000000000000# 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 tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class GroupsV3TestJSON(base.BaseIdentityV3AdminTest): @classmethod def resource_setup(cls): super(GroupsV3TestJSON, cls).resource_setup() cls.domain = cls.create_domain() @decorators.idempotent_id('2e80343b-6c81-4ac3-88c7-452f3e9d5129') def test_group_create_update_get(self): name = data_utils.rand_name('Group') description = data_utils.rand_name('Description') group = self.groups_client.create_group( name=name, domain_id=self.domain['id'], description=description)['group'] self.addCleanup(self.groups_client.delete_group, group['id']) self.assertEqual(group['name'], name) self.assertEqual(group['description'], description) new_name = data_utils.rand_name('UpdateGroup') new_desc = data_utils.rand_name('UpdateDescription') updated_group = self.groups_client.update_group( group['id'], name=new_name, description=new_desc)['group'] self.assertEqual(updated_group['name'], new_name) self.assertEqual(updated_group['description'], new_desc) new_group = self.groups_client.show_group(group['id'])['group'] self.assertEqual(group['id'], new_group['id']) self.assertEqual(new_name, new_group['name']) self.assertEqual(new_desc, new_group['description']) @decorators.idempotent_id('b66eb441-b08a-4a6d-81ab-fef71baeb26c') def test_group_update_with_few_fields(self): name = data_utils.rand_name('Group') old_description = data_utils.rand_name('Description') group = self.groups_client.create_group( name=name, domain_id=self.domain['id'], description=old_description)['group'] self.addCleanup(self.groups_client.delete_group, group['id']) new_name = data_utils.rand_name('UpdateGroup') updated_group = self.groups_client.update_group( group['id'], name=new_name)['group'] self.assertEqual(new_name, updated_group['name']) # Verify that 'description' is not being updated or deleted. self.assertEqual(old_description, updated_group['description']) @decorators.attr(type='smoke') @decorators.idempotent_id('1598521a-2f36-4606-8df9-30772bd51339') def test_group_users_add_list_delete(self): name = data_utils.rand_name('Group') group = self.groups_client.create_group( name=name, domain_id=self.domain['id'])['group'] self.addCleanup(self.groups_client.delete_group, group['id']) # add user into group users = [] for _ in range(3): user = self.create_test_user() users.append(user) self.groups_client.add_group_user(group['id'], user['id']) # list users in group group_users = self.groups_client.list_group_users(group['id'])['users'] self.assertEqual(sorted(users, key=lambda k: k['name']), sorted(group_users, key=lambda k: k['name'])) # check and delete user in group for user in users: self.groups_client.check_group_user_existence( group['id'], user['id']) self.groups_client.delete_group_user(group['id'], user['id']) group_users = self.groups_client.list_group_users(group['id'])['users'] self.assertEqual(len(group_users), 0) @decorators.idempotent_id('64573281-d26a-4a52-b899-503cb0f4e4ec') def test_list_user_groups(self): # create a user user = self.create_test_user() # create two groups, and add user into them groups = [] for _ in range(2): name = data_utils.rand_name('Group') group = self.groups_client.create_group( name=name, domain_id=self.domain['id'])['group'] groups.append(group) self.addCleanup(self.groups_client.delete_group, group['id']) self.groups_client.add_group_user(group['id'], user['id']) # list groups which user belongs to user_groups = self.users_client.list_user_groups(user['id'])['groups'] self.assertEqual(sorted(groups, key=lambda k: k['name']), sorted(user_groups, key=lambda k: k['name'])) self.assertEqual(2, len(user_groups)) @decorators.idempotent_id('cc9a57a5-a9ed-4f2d-a29f-4f979a06ec71') def test_list_groups(self): # Test to list groups group_ids = list() fetched_ids = list() for _ in range(3): name = data_utils.rand_name('Group') description = data_utils.rand_name('Description') group = self.groups_client.create_group( name=name, domain_id=self.domain['id'], description=description)['group'] self.addCleanup(self.groups_client.delete_group, group['id']) group_ids.append(group['id']) # List and Verify Groups # When domain specific drivers are enabled the operations # of listing all users and listing all groups are not supported, # they need a domain filter to be specified if CONF.identity_feature_enabled.domain_specific_drivers: body = self.groups_client.list_groups( domain_id=self.domain['id'])['groups'] else: body = self.groups_client.list_groups()['groups'] for g in body: fetched_ids.append(g['id']) missing_groups = [g for g in group_ids if g not in fetched_ids] self.assertEmpty(missing_groups) tempest-17.2.0/tempest/api/identity/admin/v3/test_users_negative.py0000666000175100017510000000412613207044712025452 0ustar zuulzuul00000000000000# Copyright 2015 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 tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class UsersNegativeTest(base.BaseIdentityV3AdminTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('e75f006c-89cc-477b-874d-588e4eab4b17') def test_create_user_for_non_existent_domain(self): # Attempt to create a user in a non-existent domain should fail 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.users_client.create_user, name=u_name, password=u_password, email=u_email, domain_id=data_utils.rand_uuid_hex()) @decorators.attr(type=['negative']) @decorators.idempotent_id('b3c9fccc-4134-46f5-b600-1da6fb0a3b1f') def test_authentication_for_disabled_user(self): # Attempt to authenticate for disabled user should fail password = data_utils.rand_password() user = self.setup_test_user(password) self.disable_user(user['name'], user['domain_id']) self.assertRaises(lib_exc.Unauthorized, self.token.auth, username=user['name'], password=password, user_domain_id=CONF.identity.default_domain_id) tempest-17.2.0/tempest/api/identity/admin/v3/test_domains.py0000666000175100017510000001510313207044712024056 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class DomainsTestJSON(base.BaseIdentityV3AdminTest): @classmethod def resource_setup(cls): super(DomainsTestJSON, cls).resource_setup() # Create some test domains to be used during tests # One of those domains will be disabled cls.setup_domains = list() for i in range(3): domain = cls.create_domain(enabled=i < 2) cls.setup_domains.append(domain) @decorators.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4') def test_list_domains(self): # Test to list domains fetched_ids = list() # List and Verify Domains body = self.domains_client.list_domains()['domains'] for d in body: fetched_ids.append(d['id']) missing_doms = [d for d in self.setup_domains if d['id'] not in fetched_ids] self.assertEmpty(missing_doms) @decorators.idempotent_id('c6aee07b-4981-440c-bb0b-eb598f58ffe9') def test_list_domains_filter_by_name(self): # List domains filtering by name params = {'name': self.setup_domains[0]['name']} fetched_domains = self.domains_client.list_domains( **params)['domains'] # Verify the filtered list is correct, domain names are unique # so exactly one domain should be found with the provided name self.assertEqual(1, len(fetched_domains)) self.assertEqual(self.setup_domains[0]['name'], fetched_domains[0]['name']) @decorators.idempotent_id('3fd19840-65c1-43f8-b48c-51bdd066dff9') def test_list_domains_filter_by_enabled(self): # List domains filtering by enabled domains params = {'enabled': True} fetched_domains = self.domains_client.list_domains( **params)['domains'] # Verify the filtered list is correct self.assertIn(self.setup_domains[0], fetched_domains) self.assertIn(self.setup_domains[1], fetched_domains) for domain in fetched_domains: self.assertEqual(True, domain['enabled']) @decorators.attr(type='smoke') @decorators.idempotent_id('f2f5b44a-82e8-4dad-8084-0661ea3b18cf') def test_create_update_delete_domain(self): # Create domain d_name = data_utils.rand_name('domain') d_desc = data_utils.rand_name('domain-desc') domain = self.domains_client.create_domain( name=d_name, description=d_desc)['domain'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.delete_domain, domain['id']) self.assertIn('description', domain) self.assertIn('name', domain) self.assertIn('enabled', domain) self.assertIn('links', domain) self.assertIsNotNone(domain['id']) self.assertEqual(d_name, domain['name']) self.assertEqual(d_desc, domain['description']) self.assertEqual(True, domain['enabled']) # Update domain new_desc = data_utils.rand_name('new-desc') new_name = data_utils.rand_name('new-name') updated_domain = self.domains_client.update_domain( domain['id'], name=new_name, description=new_desc, enabled=False)['domain'] self.assertIn('id', updated_domain) self.assertIn('description', updated_domain) self.assertIn('name', updated_domain) self.assertIn('enabled', updated_domain) self.assertIn('links', updated_domain) self.assertIsNotNone(updated_domain['id']) self.assertEqual(new_name, updated_domain['name']) self.assertEqual(new_desc, updated_domain['description']) self.assertEqual(False, updated_domain['enabled']) # Show 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(False, fetched_domain['enabled']) # Delete domain self.domains_client.delete_domain(domain['id']) body = self.domains_client.list_domains()['domains'] domains_list = [d['id'] for d in body] self.assertNotIn(domain['id'], domains_list) @decorators.idempotent_id('036df86e-bb5d-42c0-a7c2-66b9db3a6046') def test_create_domain_with_disabled_status(self): # Create domain with enabled status as false d_name = data_utils.rand_name('domain') d_desc = data_utils.rand_name('domain-desc') domain = self.domains_client.create_domain( name=d_name, description=d_desc, enabled=False)['domain'] 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']) @decorators.idempotent_id('2abf8764-309a-4fa9-bc58-201b799817ad') def test_create_domain_without_description(self): # Create domain only with name d_name = data_utils.rand_name('domain') domain = self.domains_client.create_domain(name=d_name)['domain'] self.addCleanup(self.delete_domain, domain['id']) expected_data = {'name': d_name, 'enabled': True} self.assertEqual('', domain['description']) self.assertDictContainsSubset(expected_data, domain) class DefaultDomainTestJSON(base.BaseIdentityV3AdminTest): @classmethod def resource_setup(cls): cls.domain_id = CONF.identity.default_domain_id super(DefaultDomainTestJSON, cls).resource_setup() @decorators.attr(type='smoke') @decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5') def test_default_domain_exists(self): domain = self.domains_client.show_domain(self.domain_id)['domain'] self.assertTrue(domain['enabled']) tempest-17.2.0/tempest/api/identity/admin/v3/test_oauth_consumers.py0000666000175100017510000001017413207044712025645 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions class OAUTHConsumersV3Test(base.BaseIdentityV3AdminTest): def _create_consumer(self): """Creates a consumer with a random description.""" description = data_utils.rand_name('test_create_consumer') consumer = self.oauth_consumers_client.create_consumer( description)['consumer'] # cleans up created consumers after tests self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.oauth_consumers_client.delete_consumer, consumer['id']) return consumer @decorators.idempotent_id('c8307ea6-a86c-47fd-ae7b-5b3b2caca76d') def test_create_and_show_consumer(self): """Tests to make sure that a consumer with parameters is made""" consumer = self._create_consumer() # fetch created consumer from client fetched_consumer = self.oauth_consumers_client.show_consumer( consumer['id'])['consumer'] # assert that the fetched consumer matches the created one and # has all parameters for key in ['description', 'id', 'links']: self.assertEqual(consumer[key], fetched_consumer[key]) @decorators.idempotent_id('fdfa1b7f-2a31-4354-b2c7-f6ae20554f93') def test_delete_consumer(self): """Tests the delete function.""" consumer = self._create_consumer() # fetch consumer from client to confirm it exists fetched_consumer = self.oauth_consumers_client.show_consumer( consumer['id'])['consumer'] self.assertEqual(consumer['id'], fetched_consumer['id']) # delete existing consumer self.oauth_consumers_client.delete_consumer(consumer['id']) # check that consumer no longer exists self.assertRaises(exceptions.NotFound, self.oauth_consumers_client.show_consumer, consumer['id']) @decorators.idempotent_id('080a9b1a-c009-47c0-9979-5305bf72e3dc') def test_update_consumer(self): """Tests the update functionality""" # create a new consumer to update consumer = self._create_consumer() # create new description new_description = data_utils.rand_name('test_update_consumer') # update consumer self.oauth_consumers_client.update_consumer(consumer['id'], new_description) # check that the same consumer now has the new description updated_consumer = self.oauth_consumers_client.show_consumer( consumer['id'])['consumer'] self.assertEqual(new_description, updated_consumer['description']) @decorators.idempotent_id('09ca50de-78f2-4ffb-ac71-f2254036b2b8') def test_list_consumers(self): """Test for listing consumers""" # create two consumers to populate list new_consumer_one = self._create_consumer() new_consumer_two = self._create_consumer() # fetch the list of consumers consumer_list = self.oauth_consumers_client \ .list_consumers()['consumers'] # add fetched consumer ids to a list id_list = [consumer['id'] for consumer in consumer_list] # check if created consumers are in the list self.assertIn(new_consumer_one['id'], id_list) self.assertIn(new_consumer_two['id'], id_list) tempest-17.2.0/tempest/api/identity/admin/v3/test_projects.py0000666000175100017510000002407213207044712024262 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ProjectsTestJSON(base.BaseIdentityV3AdminTest): @decorators.idempotent_id('0ecf465c-0dc4-4532-ab53-91ffeb74d12d') def test_project_create_with_description(self): # Create project with a description project_desc = data_utils.rand_name('desc') project = self.setup_test_project(description=project_desc) project_id = project['id'] desc1 = project['description'] self.assertEqual(desc1, project_desc, 'Description should have ' 'been sent in response for create') body = self.projects_client.show_project(project_id)['project'] desc2 = body['description'] self.assertEqual(desc2, project_desc, 'Description does not appear' 'to be set') @decorators.idempotent_id('5f50fe07-8166-430b-a882-3b2ee0abe26f') def test_project_create_with_domain(self): # Create project with a domain domain = self.setup_test_domain() project_name = data_utils.rand_name('project') project = self.setup_test_project( name=project_name, domain_id=domain['id']) project_id = project['id'] self.assertEqual(project_name, project['name']) self.assertEqual(domain['id'], project['domain_id']) body = self.projects_client.show_project(project_id)['project'] self.assertEqual(project_name, body['name']) self.assertEqual(domain['id'], body['domain_id']) @decorators.idempotent_id('1854f9c0-70bc-4d11-a08a-1c789d339e3d') def test_project_create_with_parent(self): # Create root project without providing a parent_id domain = self.setup_test_domain() domain_id = domain['id'] root_project_name = data_utils.rand_name('root_project') root_project = self.setup_test_project( name=root_project_name, domain_id=domain_id) root_project_id = root_project['id'] parent_id = root_project['parent_id'] self.assertEqual(root_project_name, root_project['name']) # If not provided, the parent_id must point to the top level # project in the hierarchy, i.e. its domain self.assertEqual(domain_id, parent_id) # Create a project using root_project_id as parent_id project_name = data_utils.rand_name('project') project = self.setup_test_project( name=project_name, domain_id=domain_id, parent_id=root_project_id) parent_id = project['parent_id'] self.assertEqual(project_name, project['name']) self.assertEqual(root_project_id, parent_id) @decorators.idempotent_id('a7eb9416-6f9b-4dbb-b71b-7f73aaef59d5') def test_create_is_domain_project(self): project = self.setup_test_project(domain_id=None, is_domain=True) # To delete a domain, we need to disable it first self.addCleanup(self.projects_client.update_project, project['id'], enabled=False) # Check if the is_domain project is correctly returned by both # project and domain APIs projects_list = self.projects_client.list_projects( params={'is_domain': True})['projects'] project_ids = [p['id'] for p in projects_list] self.assertIn(project['id'], project_ids) # The domains API return different attributes for the entity, so we # compare the entities IDs domains_ids = [d['id'] for d in self.domains_client.list_domains()[ 'domains']] self.assertIn(project['id'], domains_ids) @decorators.idempotent_id('1f66dc76-50cc-4741-a200-af984509e480') def test_project_create_enabled(self): # Create a project that is enabled project = self.setup_test_project(enabled=True) project_id = project['id'] en1 = project['enabled'] self.assertTrue(en1, 'Enable should be True in response') body = self.projects_client.show_project(project_id)['project'] en2 = body['enabled'] self.assertTrue(en2, 'Enable should be True in lookup') @decorators.idempotent_id('78f96a9c-e0e0-4ee6-a3ba-fbf6dfd03207') def test_project_create_not_enabled(self): # Create a project that is not enabled project = self.setup_test_project(enabled=False) en1 = project['enabled'] self.assertEqual('false', str(en1).lower(), 'Enable should be False in response') body = self.projects_client.show_project(project['id'])['project'] en2 = body['enabled'] self.assertEqual('false', str(en2).lower(), 'Enable should be False in lookup') @decorators.idempotent_id('f608f368-048c-496b-ad63-d286c26dab6b') def test_project_update_name(self): # Update name attribute of a project p_name1 = data_utils.rand_name('project') project = self.setup_test_project(name=p_name1) resp1_name = project['name'] p_name2 = data_utils.rand_name('project2') body = self.projects_client.update_project(project['id'], name=p_name2)['project'] resp2_name = body['name'] self.assertNotEqual(resp1_name, resp2_name) body = self.projects_client.show_project(project['id'])['project'] resp3_name = body['name'] self.assertNotEqual(resp1_name, resp3_name) self.assertEqual(p_name1, resp1_name) self.assertEqual(resp2_name, resp3_name) @decorators.idempotent_id('f138b715-255e-4a7d-871d-351e1ef2e153') def test_project_update_desc(self): # Update description attribute of a project p_desc = data_utils.rand_name('desc') project = self.setup_test_project(description=p_desc) resp1_desc = project['description'] p_desc2 = data_utils.rand_name('desc2') body = self.projects_client.update_project( project['id'], description=p_desc2)['project'] resp2_desc = body['description'] self.assertNotEqual(resp1_desc, resp2_desc) body = self.projects_client.show_project(project['id'])['project'] resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(p_desc, resp1_desc) self.assertEqual(resp2_desc, resp3_desc) @decorators.idempotent_id('b6b25683-c97f-474d-a595-55d410b68100') def test_project_update_enable(self): # Update the enabled attribute of a project p_en = False project = self.setup_test_project(enabled=p_en) resp1_en = project['enabled'] p_en2 = True body = self.projects_client.update_project(project['id'], enabled=p_en2)['project'] resp2_en = body['enabled'] self.assertNotEqual(resp1_en, resp2_en) body = self.projects_client.show_project(project['id'])['project'] resp3_en = body['enabled'] self.assertNotEqual(resp1_en, resp3_en) self.assertEqual('false', str(resp1_en).lower()) self.assertEqual(resp2_en, resp3_en) @decorators.idempotent_id('59398d4a-5dc5-4f86-9a4c-c26cc804d6c6') def test_associate_user_to_project(self): # Associate a user to a project # Create a Project project = self.setup_test_project() # Create a User u_name = data_utils.rand_name('user') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = data_utils.rand_password() user = self.users_client.create_user( name=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.users_client.delete_user, user['id']) # Get User To validate the user details 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']) self.assertEqual(project['id'], new_user_get['project_id']) self.assertEqual(u_email, new_user_get['email']) @decorators.idempotent_id('d1db68b6-aebe-4fa0-b79d-d724d2e21162') def test_project_get_equals_list(self): fields = ['parent_id', 'is_domain', 'description', 'links', 'name', 'enabled', 'domain_id', 'id', 'tags'] # Tags must be unique, keystone API will reject duplicates tags = ['a', 'c', 'b', 'd'] # Create a Project, cleanup is handled in the helper project = self.setup_test_project(tags=tags) # Show and list for the project project_get = self.projects_client.show_project( project['id'])['project'] _projects = self.projects_client.list_projects()['projects'] project_list = next(x for x in _projects if x['id'] == project['id']) # Assert the list of fields is correct (one is enough to check here) self.assertSetEqual(set(fields), set(project_get.keys())) # Ensure the set of tags is identical and match the expected one get_tags = set(project_get.pop("tags")) self.assertSetEqual(get_tags, set(project_list.pop("tags"))) self.assertSetEqual(get_tags, set(tags)) # Ensure all other fields are identical self.assertDictEqual(project_get, project_list) tempest-17.2.0/tempest/api/identity/admin/v3/test_endpoints_negative.py0000666000175100017510000001011413207044712026306 0ustar zuulzuul00000000000000 # 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 tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class EndpointsNegativeTestJSON(base.BaseIdentityV3AdminTest): @classmethod def setup_clients(cls): super(EndpointsNegativeTestJSON, cls).setup_clients() cls.client = cls.endpoints_client @classmethod def resource_setup(cls): super(EndpointsNegativeTestJSON, cls).resource_setup() cls.service_ids = list() s_name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') s_description = data_utils.rand_name('description') service_data = ( cls.services_client.create_service(name=s_name, type=s_type, description=s_description) ['service']) cls.service_id = service_data['id'] cls.service_ids.append(cls.service_id) @classmethod def resource_cleanup(cls): for s in cls.service_ids: cls.services_client.delete_service(s) super(EndpointsNegativeTestJSON, cls).resource_cleanup() @decorators.attr(type=['negative']) @decorators.idempotent_id('ac6c137e-4d3d-448f-8c83-4f13d0942651') def test_create_with_enabled_False(self): # Enabled should be a boolean, not a string like 'False' interface = 'public' url = data_utils.rand_url() region = data_utils.rand_name('region') self.assertRaises(lib_exc.BadRequest, self.client.create_endpoint, service_id=self.service_id, interface=interface, url=url, region=region, enabled='False') @decorators.attr(type=['negative']) @decorators.idempotent_id('9c43181e-0627-484a-8c79-923e8a59598b') def test_create_with_enabled_True(self): # Enabled should be a boolean, not a string like 'True' interface = 'public' url = data_utils.rand_url() region = data_utils.rand_name('region') self.assertRaises(lib_exc.BadRequest, self.client.create_endpoint, service_id=self.service_id, interface=interface, url=url, region=region, enabled='True') def _assert_update_raises_bad_request(self, enabled): # Create an endpoint region1 = data_utils.rand_name('region') url1 = data_utils.rand_url() interface1 = 'public' endpoint_for_update = ( self.client.create_endpoint(service_id=self.service_id, interface=interface1, url=url1, region=region1, enabled=True)['endpoint']) self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id']) self.assertRaises(lib_exc.BadRequest, self.client.update_endpoint, endpoint_for_update['id'], enabled=enabled) @decorators.attr(type=['negative']) @decorators.idempotent_id('65e41f32-5eb7-498f-a92a-a6ccacf7439a') def test_update_with_enabled_False(self): # Enabled should be a boolean, not a string like 'False' self._assert_update_raises_bad_request('False') @decorators.attr(type=['negative']) @decorators.idempotent_id('faba3587-f066-4757-a48e-b4a3f01803bb') def test_update_with_enabled_True(self): # Enabled should be a boolean, not a string like 'True' self._assert_update_raises_bad_request('True') tempest-17.2.0/tempest/api/identity/admin/v3/test_domains_negative.py0000666000175100017510000000557413207044712025753 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest): @decorators.attr(type=['negative', 'gate']) @decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b') def test_delete_active_domain(self): domain = self.create_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.domains_client.delete_domain, domain_id) @decorators.attr(type=['negative']) @decorators.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.domains_client.create_domain, name='') @decorators.attr(type=['negative']) @decorators.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.domains_client.create_domain, name=d_name) @decorators.attr(type=['negative']) @decorators.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.domains_client.delete_domain, data_utils.rand_uuid_hex()) @decorators.attr(type=['negative']) @decorators.idempotent_id('e6f9e4a2-4f36-4be8-bdbc-4e199ae29427') def test_domain_create_duplicate(self): domain_name = data_utils.rand_name('domain-dup') domain = self.domains_client.create_domain(name=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.domains_client.create_domain, name=domain_name) tempest-17.2.0/tempest/api/identity/admin/v3/test_users.py0000666000175100017510000001663513207044712023600 0ustar zuulzuul00000000000000# 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 time import testtools from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class UsersV3TestJSON(base.BaseIdentityV3AdminTest): @decorators.idempotent_id('b537d090-afb9-4519-b95d-270b0708e87e') def test_user_update(self): # Test case to check if updating of user attributes is successful. # Creating first user u_name = data_utils.rand_name('user') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = data_utils.rand_password() user = self.users_client.create_user( name=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.users_client.delete_user, user['id']) # Creating second project for updation project = self.setup_test_project() # Updating user details with new values update_kwargs = {'name': data_utils.rand_name('user2'), 'description': data_utils.rand_name('desc2'), 'project_id': project['id'], 'email': 'user2@testmail.tm', 'enabled': False} updated_user = self.users_client.update_user( user['id'], **update_kwargs)['user'] for field in update_kwargs: self.assertEqual(update_kwargs[field], updated_user[field]) # GET by id after updating new_user_get = self.users_client.show_user(user['id'])['user'] # Assert response body of GET after updation for field in update_kwargs: self.assertEqual(update_kwargs[field], new_user_get[field]) @decorators.idempotent_id('2d223a0e-e457-4a70-9fb1-febe027a0ff9') def test_update_user_password(self): # Creating User to check password updation u_name = data_utils.rand_name('user') original_password = data_utils.rand_password() user = self.users_client.create_user( name=u_name, password=original_password)['user'] # Delete the User at the end all test methods self.addCleanup(self.users_client.delete_user, user['id']) # Update user with new password new_password = data_utils.rand_password() self.users_client.update_user_password( user['id'], password=new_password, original_password=original_password) # NOTE(morganfainberg): Fernet tokens are not subsecond aware and # Keystone should only be precise to the second. Sleep to ensure # we are passing the second boundary. time.sleep(1) resp = self.token.auth(user_id=user['id'], password=new_password).response subject_token = resp['x-subject-token'] # Perform GET Token to verify and confirm password is updated token_details = self.client.show_token(subject_token)['token'] self.assertEqual(token_details['user']['id'], user['id']) self.assertEqual(token_details['user']['name'], u_name) @decorators.idempotent_id('a831e70c-e35b-430b-92ed-81ebbc5437b8') def test_list_user_projects(self): # List the projects that a user has access upon assigned_project_ids = list() fetched_project_ids = list() u_project = self.setup_test_project() # Create a user. u_name = data_utils.rand_name('user') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = data_utils.rand_password() user_body = self.users_client.create_user( name=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.users_client.delete_user, user_body['id']) # Creating Role role_body = self.setup_test_role() user = self.users_client.show_user(user_body['id'])['user'] role = self.roles_client.show_role(role_body['id'])['role'] for _ in range(2): # Creating project so as to assign role project_body = self.setup_test_project() project = self.projects_client.show_project( project_body['id'])['project'] # Assigning roles to user on project self.roles_client.create_user_role_on_project(project['id'], user['id'], role['id']) assigned_project_ids.append(project['id']) 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 missing_projects =\ [p for p in assigned_project_ids if p not in fetched_project_ids] self.assertEmpty(missing_projects, "Failed to find project %s in fetched list" % ', '.join(m_project for m_project in missing_projects)) @decorators.idempotent_id('c10dcd90-461d-4b16-8e23-4eb836c00644') def test_get_user(self): # Get a user detail user = self.setup_test_user() fetched_user = self.users_client.show_user(user['id'])['user'] self.assertEqual(user['id'], fetched_user['id']) @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance, 'Security compliance not available.') @decorators.idempotent_id('568cd46c-ee6c-4ab4-a33a-d3791931979e') def test_password_history_not_enforced_in_admin_reset(self): old_password = self.os_primary.credentials.password user_id = self.os_primary.credentials.user_id new_password = data_utils.rand_password() self.users_client.update_user(user_id, password=new_password) # To be safe, we add this cleanup to restore the original password in # case something goes wrong before it is restored later. self.addCleanup( self.users_client.update_user, user_id, password=old_password) # Check authorization with new password self.token.auth(user_id=user_id, password=new_password) if CONF.identity.user_unique_last_password_count > 1: # The password history is not enforced via the admin reset route. # We can set the same password. self.users_client.update_user(user_id, password=new_password) # Restore original password self.users_client.update_user(user_id, password=old_password) # Check authorization with old password self.token.auth(user_id=user_id, password=old_password) tempest-17.2.0/tempest/api/identity/admin/v3/test_list_projects.py0000666000175100017510000000764713207044712025326 0ustar zuulzuul00000000000000# 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.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ListProjectsTestJSON(base.BaseIdentityV3AdminTest): @classmethod def resource_setup(cls): super(ListProjectsTestJSON, cls).resource_setup() cls.project_ids = list() # Create a domain cls.domain = cls.create_domain() # Create project with domain cls.projects = list() cls.p1_name = data_utils.rand_name('project') cls.p1 = cls.projects_client.create_project( cls.p1_name, enabled=False, domain_id=cls.domain['id'])['project'] cls.projects.append(cls.p1) cls.project_ids.append(cls.p1['id']) # Create default project p2_name = data_utils.rand_name('project') cls.p2 = cls.projects_client.create_project(p2_name)['project'] cls.projects.append(cls.p2) cls.project_ids.append(cls.p2['id']) # Create a new project (p3) using p2 as parent project p3_name = data_utils.rand_name('project') cls.p3 = cls.projects_client.create_project( p3_name, parent_id=cls.p2['id'])['project'] cls.projects.append(cls.p3) cls.project_ids.append(cls.p3['id']) @classmethod def resource_cleanup(cls): # Cleanup the projects created during setup in inverse order for project in reversed(cls.projects): cls.projects_client.delete_project(project['id']) super(ListProjectsTestJSON, cls).resource_cleanup() @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44') def test_list_projects(self): # List projects list_projects = self.projects_client.list_projects()['projects'] for p in self.project_ids: show_project = self.projects_client.show_project(p)['project'] self.assertIn(show_project, list_projects) @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10') def test_list_projects_with_domains(self): # List projects with domain self._list_projects_with_params( {'domain_id': self.domain['id']}, 'domain_id') @decorators.idempotent_id('0fe7a334-675a-4509-b00e-1c4b95d5dae8') def test_list_projects_with_enabled(self): # List the projects with enabled self._list_projects_with_params({'enabled': False}, 'enabled') @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26') def test_list_projects_with_name(self): # List projects with name self._list_projects_with_params({'name': self.p1_name}, 'name') @decorators.idempotent_id('6edc66f5-2941-4a17-9526-4073311c1fac') def test_list_projects_with_parent(self): # List projects with parent params = {'parent_id': self.p3['parent_id']} fetched_projects = self.projects_client.list_projects( params)['projects'] self.assertNotEmpty(fetched_projects) for project in fetched_projects: self.assertEqual(self.p3['parent_id'], project['parent_id']) def _list_projects_with_params(self, params, key): body = self.projects_client.list_projects(params)['projects'] self.assertIn(self.p1[key], map(lambda x: x[key], body)) self.assertNotIn(self.p2[key], map(lambda x: x[key], body)) tempest-17.2.0/tempest/api/identity/admin/v3/test_tokens.py0000666000175100017510000002517113207044712023735 0ustar zuulzuul00000000000000# 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 six from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class TokensV3TestJSON(base.BaseIdentityV3AdminTest): credentials = ['primary', 'admin', 'alt'] @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212') def test_tokens(self): # Valid user's token is authenticated # Create a User u_name = data_utils.rand_name('user') u_desc = '%s-description' % u_name u_password = data_utils.rand_password() user = self.create_test_user( name=u_name, description=u_desc, password=u_password) # Perform Authentication resp = self.token.auth(user_id=user['id'], password=u_password).response subject_token = resp['x-subject-token'] self.client.check_token_existence(subject_token) # Perform GET Token token_details = self.client.show_token(subject_token)['token'] self.assertEqual(resp['x-subject-token'], subject_token) self.assertEqual(token_details['user']['id'], user['id']) self.assertEqual(token_details['user']['name'], u_name) # Perform Delete Token self.client.delete_token(subject_token) self.assertRaises(lib_exc.NotFound, self.client.check_token_existence, subject_token) @decorators.idempotent_id('565fa210-1da1-4563-999b-f7b5b67cf112') def test_rescope_token(self): """Rescope a token. An unscoped token can be requested, that token can be used to request a scoped token. The scoped token can be revoked, and the original token used to get a token in a different project. """ # Create a user. user_password = data_utils.rand_password() user = self.create_test_user(password=user_password) # Create a couple projects project1_name = data_utils.rand_name(name='project') project1 = self.setup_test_project(name=project1_name) project2_name = data_utils.rand_name(name='project') project2 = self.setup_test_project(name=project2_name) self.addCleanup(self.projects_client.delete_project, project2['id']) # Create a role role = self.setup_test_role() # Grant the user the role on both projects. self.roles_client.create_user_role_on_project(project1['id'], user['id'], role['id']) self.roles_client.create_user_role_on_project(project2['id'], user['id'], role['id']) # Get an unscoped token. token_auth = self.token.auth(user_id=user['id'], password=user_password) token_id = token_auth.response['x-subject-token'] orig_expires_at = token_auth['token']['expires_at'] orig_user = token_auth['token']['user'] self.assertIsInstance(token_auth['token']['expires_at'], six.text_type) self.assertIsInstance(token_auth['token']['issued_at'], six.text_type) self.assertEqual(['password'], token_auth['token']['methods']) self.assertEqual(user['id'], token_auth['token']['user']['id']) self.assertEqual(user['name'], token_auth['token']['user']['name']) self.assertEqual(CONF.identity.default_domain_id, token_auth['token']['user']['domain']['id']) self.assertIsNotNone(token_auth['token']['user']['domain']['name']) self.assertNotIn('catalog', token_auth['token']) self.assertNotIn('project', token_auth['token']) self.assertNotIn('roles', token_auth['token']) # Use the unscoped token to get a scoped token. token_auth = self.token.auth( token=token_id, project_name=project1_name, project_domain_id=CONF.identity.default_domain_id) token1_id = token_auth.response['x-subject-token'] self.assertEqual(orig_expires_at, token_auth['token']['expires_at'], 'Expiration time should match original token') self.assertIsInstance(token_auth['token']['issued_at'], six.text_type) self.assertEqual(set(['password', 'token']), set(token_auth['token']['methods'])) self.assertEqual(orig_user, token_auth['token']['user'], 'User should match original token') self.assertIsInstance(token_auth['token']['catalog'], list) self.assertEqual(project1['id'], token_auth['token']['project']['id']) self.assertEqual(project1['name'], token_auth['token']['project']['name']) self.assertEqual(CONF.identity.default_domain_id, token_auth['token']['project']['domain']['id']) self.assertIsNotNone(token_auth['token']['project']['domain']['name']) self.assertEqual(1, len(token_auth['token']['roles'])) self.assertEqual(role['id'], token_auth['token']['roles'][0]['id']) self.assertEqual(role['name'], token_auth['token']['roles'][0]['name']) # Revoke the unscoped token. self.client.delete_token(token1_id) # Now get another scoped token using the unscoped token. token_auth = self.token.auth( token=token_id, project_name=project2_name, project_domain_id=CONF.identity.default_domain_id) self.assertEqual(project2['id'], token_auth['token']['project']['id']) self.assertEqual(project2['name'], token_auth['token']['project']['name']) @decorators.idempotent_id('08ed85ce-2ba8-4864-b442-bcc61f16ae89') def test_get_available_project_scopes(self): manager_project_id = self.os_primary.credentials.project_id admin_user_id = self.os_admin.credentials.user_id admin_role_id = self.get_role_by_name(CONF.identity.admin_role)['id'] # Grant the user the role on both projects. self.roles_client.create_user_role_on_project( manager_project_id, admin_user_id, admin_role_id) self.addCleanup( self.roles_client.delete_role_from_user_on_project, manager_project_id, admin_user_id, admin_role_id) assigned_project_ids = [self.os_admin.credentials.project_id, manager_project_id] # Get available project scopes available_projects = self.client.list_auth_projects()['projects'] # Create list to save fetched project IDs fetched_project_ids = [i['id'] for i in available_projects] # verifying the project ids in list missing_project_ids = \ [p for p in assigned_project_ids if p not in fetched_project_ids] self.assertEmpty(missing_project_ids, "Failed to find project_ids %s in fetched list" % ', '.join(missing_project_ids)) @decorators.idempotent_id('ec5ecb05-af64-4c04-ac86-4d9f6f12f185') def test_get_available_domain_scopes(self): # Test for verifying that listing domain scopes for a user works if # the user has a domain role or belongs to a group that has a domain # role. For this test, admin client is used to add roles to alt user, # which performs API calls, to avoid 401 Unauthorized errors. alt_user_id = self.os_alt.credentials.user_id def _create_user_domain_role_for_alt_user(): domain_id = self.setup_test_domain()['id'] role_id = self.setup_test_role()['id'] # Create a role association between the user and domain. self.roles_client.create_user_role_on_domain( domain_id, alt_user_id, role_id) self.addCleanup( self.roles_client.delete_role_from_user_on_domain, domain_id, alt_user_id, role_id) return domain_id def _create_group_domain_role_for_alt_user(): domain_id = self.setup_test_domain()['id'] role_id = self.setup_test_role()['id'] # Create a group. group_name = data_utils.rand_name('Group') group_id = self.groups_client.create_group( name=group_name, domain_id=domain_id)['group']['id'] self.addCleanup(self.groups_client.delete_group, group_id) # Add the alt user to the group. self.groups_client.add_group_user(group_id, alt_user_id) self.addCleanup(self.groups_client.delete_group_user, group_id, alt_user_id) # Create a role association between the group and domain. self.roles_client.create_group_role_on_domain( domain_id, group_id, role_id) self.addCleanup( self.roles_client.delete_role_from_group_on_domain, domain_id, group_id, role_id) return domain_id # Add the alt user to 2 random domains and 2 random groups # with randomized domains and roles. assigned_domain_ids = [] for _ in range(2): domain_id = _create_user_domain_role_for_alt_user() assigned_domain_ids.append(domain_id) domain_id = _create_group_domain_role_for_alt_user() assigned_domain_ids.append(domain_id) # Get available domain scopes for the alt user. available_domains = self.os_alt.identity_v3_client.list_auth_domains()[ 'domains'] fetched_domain_ids = [i['id'] for i in available_domains] # Verify the expected domain IDs are in the list. missing_domain_ids = \ [p for p in assigned_domain_ids if p not in fetched_domain_ids] self.assertEmpty(missing_domain_ids, "Failed to find domain_ids %s in fetched list" % ", ".join(missing_domain_ids)) tempest-17.2.0/tempest/api/identity/admin/v2/0000775000175100017510000000000013207045130021003 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/v2/test_roles.py0000666000175100017510000001200713207044712023547 0ustar zuulzuul00000000000000# 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 tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators class RolesTestJSON(base.BaseIdentityV2AdminTest): @classmethod def resource_setup(cls): super(RolesTestJSON, cls).resource_setup() cls.roles = list() for _ in range(5): role_name = data_utils.rand_name(name='role') role = cls.roles_client.create_role(name=role_name)['role'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.roles_client.delete_role, role['id']) cls.roles.append(role) def _get_role_params(self): user = self.setup_test_user() tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] role = self.setup_test_role() return (user, tenant, role) def assert_role_in_role_list(self, role, roles): found = False for user_role in roles: if user_role['id'] == role['id']: found = True self.assertTrue(found, "assigned role was not in list") @decorators.idempotent_id('75d9593f-50b7-4fcf-bd64-e3fb4a278e23') def test_list_roles(self): """Return a list of all roles.""" body = self.roles_client.list_roles()['roles'] found = [role for role in body if role in self.roles] self.assertNotEmpty(found) self.assertEqual(len(found), len(self.roles)) @decorators.idempotent_id('c62d909d-6c21-48c0-ae40-0a0760e6db5e') def test_role_create_delete(self): """Role should be created, verified, and deleted.""" role_name = data_utils.rand_name(name='role-test') body = self.roles_client.create_role(name=role_name)['role'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.roles_client.delete_role, body['id']) self.assertEqual(role_name, body['name']) body = self.roles_client.list_roles()['roles'] found = [role for role in body if role['name'] == role_name] self.assertNotEmpty(found) body = self.roles_client.delete_role(found[0]['id']) body = self.roles_client.list_roles()['roles'] found = [role for role in body if role['name'] == role_name] self.assertEmpty(found) @decorators.idempotent_id('db6870bd-a6ed-43be-a9b1-2f10a5c9994f') def test_get_role_by_id(self): """Get a role by its id.""" role = self.setup_test_role() role_id = role['id'] role_name = role['name'] body = self.roles_client.show_role(role_id)['role'] self.assertEqual(role_id, body['id']) self.assertEqual(role_name, body['name']) @decorators.idempotent_id('0146f675-ffbd-4208-b3a4-60eb628dbc5e') def test_assign_user_role(self): """Assign a role to a user on a tenant.""" (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) roles = self.roles_client.list_user_roles_on_project( tenant['id'], user['id'])['roles'] self.assert_role_in_role_list(role, roles) @decorators.idempotent_id('f0b9292c-d3ba-4082-aa6c-440489beef69') def test_remove_user_role(self): """Remove a role assigned to a user on a tenant.""" (user, tenant, role) = self._get_role_params() user_role = self.roles_client.create_user_role_on_project( tenant['id'], user['id'], role['id'])['role'] self.roles_client.delete_role_from_user_on_project(tenant['id'], user['id'], user_role['id']) @decorators.idempotent_id('262e1e3e-ed71-4edd-a0e5-d64e83d66d05') def test_list_user_roles(self): """List roles assigned to a user on tenant.""" (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) roles = self.roles_client.list_user_roles_on_project( tenant['id'], user['id'])['roles'] self.assert_role_in_role_list(role, roles) tempest-17.2.0/tempest/api/identity/admin/v2/test_services.py0000666000175100017510000001076213207044712024254 0ustar zuulzuul00000000000000# 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 tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServicesTestJSON(base.BaseIdentityV2AdminTest): def _del_service(self, service_id): # Deleting the service created in this method self.services_client.delete_service(service_id) # Checking whether service is deleted successfully self.assertRaises(lib_exc.NotFound, self.services_client.show_service, service_id) @decorators.idempotent_id('84521085-c6e6-491c-9a08-ec9f70f90110') def test_create_get_delete_service(self): # GET Service # Creating a Service name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') description = data_utils.rand_name('description') service_data = self.services_client.create_service( name=name, type=s_type, description=description)['OS-KSADM:service'] self.assertIsNotNone(service_data['id']) self.addCleanup(self._del_service, service_data['id']) # Verifying response body of create service self.assertIn('name', service_data) self.assertEqual(name, service_data['name']) self.assertIn('type', service_data) self.assertEqual(s_type, service_data['type']) self.assertIn('description', service_data) self.assertEqual(description, service_data['description']) # Get service fetched_service = ( self.services_client.show_service(service_data['id']) ['OS-KSADM:service']) # verifying the existence of service created self.assertIn('id', fetched_service) self.assertEqual(fetched_service['id'], service_data['id']) self.assertIn('name', fetched_service) self.assertEqual(fetched_service['name'], service_data['name']) self.assertIn('type', fetched_service) self.assertEqual(fetched_service['type'], service_data['type']) self.assertIn('description', fetched_service) self.assertEqual(fetched_service['description'], service_data['description']) @decorators.idempotent_id('5d3252c8-e555-494b-a6c8-e11d7335da42') def test_create_service_without_description(self): # Create a service only with name and type name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') service = self.services_client.create_service( name=name, type=s_type)['OS-KSADM:service'] self.assertIn('id', service) self.addCleanup(self._del_service, service['id']) self.assertIn('name', service) self.assertEqual(name, service['name']) self.assertIn('type', service) self.assertEqual(s_type, service['type']) @decorators.attr(type='smoke') @decorators.idempotent_id('34ea6489-012d-4a86-9038-1287cadd5eca') def test_list_services(self): # Create, List, Verify and Delete Services services = [] for _ in range(3): name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') description = data_utils.rand_name('description') service = self.services_client.create_service( name=name, type=s_type, description=description)['OS-KSADM:service'] services.append(service) service_ids = [svc['id'] for svc in services] def delete_services(): for service_id in service_ids: self.services_client.delete_service(service_id) self.addCleanup(delete_services) # List and Verify Services body = self.services_client.list_services()['OS-KSADM:services'] found = [serv for serv in body if serv['id'] in service_ids] self.assertEqual(len(found), len(services), 'Services not found') tempest-17.2.0/tempest/api/identity/admin/v2/test_tenants.py0000666000175100017510000001406313207044712024103 0ustar zuulzuul00000000000000# 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 tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class TenantsTestJSON(base.BaseIdentityV2AdminTest): @decorators.idempotent_id('16c6e05c-6112-4b0e-b83f-5e43f221b6b0') def test_tenant_list_delete(self): # Create several tenants and delete them tenants = [] for _ in range(3): tenant = self.setup_test_tenant() tenants.append(tenant) tenant_ids = [tn['id'] for tn in tenants] body = self.tenants_client.list_tenants()['tenants'] found = [t for t in body if t['id'] in tenant_ids] self.assertEqual(len(found), len(tenants), 'Tenants not created') for tenant in tenants: self.tenants_client.delete_tenant(tenant['id']) body = self.tenants_client.list_tenants()['tenants'] found = [tenant for tenant in body if tenant['id'] in tenant_ids] self.assertEmpty(found, 'Tenants failed to delete') @decorators.idempotent_id('d25e9f24-1310-4d29-b61b-d91299c21d6d') def test_tenant_create_with_description(self): # Create tenant with a description tenant_desc = data_utils.rand_name(name='desc') tenant = self.setup_test_tenant(description=tenant_desc) tenant_id = tenant['id'] desc1 = tenant['description'] self.assertEqual(desc1, tenant_desc, 'Description should have ' 'been sent in response for create') body = self.tenants_client.show_tenant(tenant_id)['tenant'] desc2 = body['description'] self.assertEqual(desc2, tenant_desc, 'Description does not appear' 'to be set') self.tenants_client.delete_tenant(tenant_id) @decorators.idempotent_id('670bdddc-1cd7-41c7-b8e2-751cfb67df50') def test_tenant_create_enabled(self): # Create a tenant that is enabled tenant = self.setup_test_tenant(enabled=True) tenant_id = tenant['id'] en1 = tenant['enabled'] self.assertTrue(en1, 'Enable should be True in response') body = self.tenants_client.show_tenant(tenant_id)['tenant'] en2 = body['enabled'] self.assertTrue(en2, 'Enable should be True in lookup') self.tenants_client.delete_tenant(tenant_id) @decorators.idempotent_id('3be22093-b30f-499d-b772-38340e5e16fb') def test_tenant_create_not_enabled(self): # Create a tenant that is not enabled tenant = self.setup_test_tenant(enabled=False) tenant_id = tenant['id'] en1 = tenant['enabled'] self.assertEqual('false', str(en1).lower(), 'Enable should be False in response') body = self.tenants_client.show_tenant(tenant_id)['tenant'] en2 = body['enabled'] self.assertEqual('false', str(en2).lower(), 'Enable should be False in lookup') self.tenants_client.delete_tenant(tenant_id) @decorators.idempotent_id('781f2266-d128-47f3-8bdb-f70970add238') def test_tenant_update_name(self): # Update name attribute of a tenant t_name1 = data_utils.rand_name(name='tenant') tenant = self.setup_test_tenant(name=t_name1) t_id = tenant['id'] resp1_name = tenant['name'] t_name2 = data_utils.rand_name(name='tenant2') body = self.tenants_client.update_tenant(t_id, name=t_name2)['tenant'] resp2_name = body['name'] self.assertNotEqual(resp1_name, resp2_name) body = self.tenants_client.show_tenant(t_id)['tenant'] resp3_name = body['name'] self.assertNotEqual(resp1_name, resp3_name) self.assertEqual(t_name1, resp1_name) self.assertEqual(resp2_name, resp3_name) self.tenants_client.delete_tenant(t_id) @decorators.idempotent_id('859fcfe1-3a03-41ef-86f9-b19a47d1cd87') def test_tenant_update_desc(self): # Update description attribute of a tenant t_desc = data_utils.rand_name(name='desc') tenant = self.setup_test_tenant(description=t_desc) t_id = tenant['id'] resp1_desc = tenant['description'] t_desc2 = data_utils.rand_name(name='desc2') body = self.tenants_client.update_tenant(t_id, description=t_desc2) updated_tenant = body['tenant'] resp2_desc = updated_tenant['description'] self.assertNotEqual(resp1_desc, resp2_desc) body = self.tenants_client.show_tenant(t_id)['tenant'] resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(t_desc, resp1_desc) self.assertEqual(resp2_desc, resp3_desc) self.tenants_client.delete_tenant(t_id) @decorators.idempotent_id('8fc8981f-f12d-4c66-9972-2bdcf2bc2e1a') def test_tenant_update_enable(self): # Update the enabled attribute of a tenant t_en = False tenant = self.setup_test_tenant(enabled=t_en) t_id = tenant['id'] resp1_en = tenant['enabled'] t_en2 = True body = self.tenants_client.update_tenant(t_id, enabled=t_en2) updated_tenant = body['tenant'] resp2_en = updated_tenant['enabled'] self.assertNotEqual(resp1_en, resp2_en) body = self.tenants_client.show_tenant(t_id)['tenant'] resp3_en = body['enabled'] self.assertNotEqual(resp1_en, resp3_en) self.assertEqual('false', str(resp1_en).lower()) self.assertEqual(resp2_en, resp3_en) self.tenants_client.delete_tenant(t_id) tempest-17.2.0/tempest/api/identity/admin/v2/test_roles_negative.py0000666000175100017510000003051413207044712025434 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class RolesNegativeTestJSON(base.BaseIdentityV2AdminTest): def _get_role_params(self): user = self.setup_test_user() tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] role = self.setup_test_role() return (user, tenant, role) @decorators.attr(type=['negative']) @decorators.idempotent_id('d5d5f1df-f8ca-4de0-b2ef-259c1cc67025') def test_list_roles_by_unauthorized_user(self): # Non-administrator user should not be able to list roles self.assertRaises(lib_exc.Forbidden, self.non_admin_roles_client.list_roles) @decorators.attr(type=['negative']) @decorators.idempotent_id('11a3c7da-df6c-40c2-abc2-badd682edf9f') def test_list_roles_request_without_token(self): # Request to list roles without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.roles_client.list_roles) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('c0b89e56-accc-4c73-85f8-9c0f866104c1') def test_role_create_blank_name(self): # Should not be able to create a role with a blank name self.assertRaises(lib_exc.BadRequest, self.roles_client.create_role, name='') @decorators.attr(type=['negative']) @decorators.idempotent_id('585c8998-a8a4-4641-a5dd-abef7a8ced00') def test_create_role_by_unauthorized_user(self): # Non-administrator user should not be able to create role role_name = data_utils.rand_name(name='role') self.assertRaises(lib_exc.Forbidden, self.non_admin_roles_client.create_role, name=role_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('a7edd17a-e34a-4aab-8bb7-fa6f498645b8') def test_create_role_request_without_token(self): # Request to create role without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) role_name = data_utils.rand_name(name='role') self.assertRaises(lib_exc.Unauthorized, self.roles_client.create_role, name=role_name) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('c0cde2c8-81c1-4bb0-8fe2-cf615a3547a8') def test_role_create_duplicate(self): # Role names should be unique role_name = data_utils.rand_name(name='role-dup') body = self.roles_client.create_role(name=role_name)['role'] role1_id = body.get('id') self.addCleanup(self.roles_client.delete_role, role1_id) self.assertRaises(lib_exc.Conflict, self.roles_client.create_role, name=role_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('15347635-b5b1-4a87-a280-deb2bd6d865e') def test_delete_role_by_unauthorized_user(self): # Non-administrator user should not be able to delete role role_name = data_utils.rand_name(name='role') body = self.roles_client.create_role(name=role_name)['role'] self.addCleanup(self.roles_client.delete_role, body['id']) role_id = body.get('id') self.assertRaises(lib_exc.Forbidden, self.non_admin_roles_client.delete_role, role_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('44b60b20-70de-4dac-beaf-a3fc2650a16b') def test_delete_role_request_without_token(self): # Request to delete role without a valid token should fail role_name = data_utils.rand_name(name='role') body = self.roles_client.create_role(name=role_name)['role'] self.addCleanup(self.roles_client.delete_role, body['id']) role_id = body.get('id') token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.roles_client.delete_role, role_id) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('38373691-8551-453a-b074-4260ad8298ef') def test_delete_role_non_existent(self): # Attempt to delete a non existent role should fail non_existent_role = data_utils.rand_uuid_hex() self.assertRaises(lib_exc.NotFound, self.roles_client.delete_role, non_existent_role) @decorators.attr(type=['negative']) @decorators.idempotent_id('391df5cf-3ec3-46c9-bbe5-5cb58dd4dc41') def test_assign_user_role_by_unauthorized_user(self): # Non-administrator user should not be authorized to # assign a role to user (user, tenant, role) = self._get_role_params() self.assertRaises( lib_exc.Forbidden, self.non_admin_roles_client.create_user_role_on_project, tenant['id'], user['id'], role['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('f0d2683c-5603-4aee-95d7-21420e87cfd8') def test_assign_user_role_request_without_token(self): # Request to assign a role to a user without a valid token (user, tenant, role) = self._get_role_params() token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises( lib_exc.Unauthorized, self.roles_client.create_user_role_on_project, tenant['id'], user['id'], role['id']) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('99b297f6-2b5d-47c7-97a9-8b6bb4f91042') def test_assign_user_role_for_non_existent_role(self): # Attempt to assign a non existent role to user should fail (user, tenant, _) = self._get_role_params() non_existent_role = data_utils.rand_uuid_hex() self.assertRaises(lib_exc.NotFound, self.roles_client.create_user_role_on_project, tenant['id'], user['id'], non_existent_role) @decorators.attr(type=['negative']) @decorators.idempotent_id('b2285aaa-9e76-4704-93a9-7a8acd0a6c8f') def test_assign_user_role_for_non_existent_tenant(self): # Attempt to assign a role on a non existent tenant should fail (user, _, role) = self._get_role_params() non_existent_tenant = data_utils.rand_uuid_hex() self.assertRaises(lib_exc.NotFound, self.roles_client.create_user_role_on_project, non_existent_tenant, user['id'], role['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('5c3132cd-c4c8-4402-b5ea-71eb44e97793') def test_assign_duplicate_user_role(self): # Duplicate user role should not get assigned (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) self.assertRaises(lib_exc.Conflict, self.roles_client.create_user_role_on_project, tenant['id'], user['id'], role['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('d0537987-0977-448f-a435-904c15de7298') def test_remove_user_role_by_unauthorized_user(self): # Non-administrator user should not be authorized to # remove a user's role (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) self.assertRaises( lib_exc.Forbidden, self.non_admin_roles_client.delete_role_from_user_on_project, tenant['id'], user['id'], role['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('cac81cf4-c1d2-47dc-90d3-f2b7eb572286') def test_remove_user_role_request_without_token(self): # Request to remove a user's role without a valid token (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.roles_client.delete_role_from_user_on_project, tenant['id'], user['id'], role['id']) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('ab32d759-cd16-41f1-a86e-44405fa9f6d2') def test_remove_user_role_non_existent_role(self): # Attempt to delete a non existent role from a user should fail (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) non_existent_role = data_utils.rand_uuid_hex() self.assertRaises(lib_exc.NotFound, self.roles_client.delete_role_from_user_on_project, tenant['id'], user['id'], non_existent_role) @decorators.attr(type=['negative']) @decorators.idempotent_id('67a679ec-03dd-4551-bbfc-d1c93284f023') def test_remove_user_role_non_existent_tenant(self): # Attempt to remove a role from a non existent tenant should fail (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) non_existent_tenant = data_utils.rand_uuid_hex() self.assertRaises(lib_exc.NotFound, self.roles_client.delete_role_from_user_on_project, non_existent_tenant, user['id'], role['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('7391ab4c-06f3-477a-a64a-c8e55ce89837') def test_list_user_roles_by_unauthorized_user(self): # Non-administrator user should not be authorized to list # a user's roles (user, tenant, role) = self._get_role_params() self.roles_client.create_user_role_on_project(tenant['id'], user['id'], role['id']) self.assertRaises( lib_exc.Forbidden, self.non_admin_roles_client.list_user_roles_on_project, tenant['id'], user['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('682adfb2-fd5f-4b0a-a9ca-322e9bebb907') def test_list_user_roles_request_without_token(self): # Request to list user's roles without a valid token should fail (user, tenant, _) = self._get_role_params() token = self.client.auth_provider.get_token() self.client.delete_token(token) try: self.assertRaises(lib_exc.Unauthorized, self.roles_client.list_user_roles_on_project, tenant['id'], user['id']) finally: self.client.auth_provider.clear_auth() tempest-17.2.0/tempest/api/identity/admin/v2/__init__.py0000666000175100017510000000000013207044712023111 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/admin/v2/test_endpoints.py0000666000175100017510000000761313207044712024435 0ustar zuulzuul00000000000000# 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. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class EndPointsTestJSON(base.BaseIdentityV2AdminTest): @classmethod def resource_setup(cls): super(EndPointsTestJSON, cls).resource_setup() s_name = data_utils.rand_name('service') s_type = data_utils.rand_name('type') s_description = data_utils.rand_name('description') service_data = cls.services_client.create_service( name=s_name, type=s_type, description=s_description)['OS-KSADM:service'] cls.addClassResourceCleanup(cls.services_client.delete_service, service_data['id']) cls.service_id = service_data['id'] # Create endpoints so as to use for LIST and GET test cases cls.setup_endpoints = list() for _ in range(2): region = data_utils.rand_name('region') url = data_utils.rand_url() endpoint = cls.endpoints_client.create_endpoint( service_id=cls.service_id, region=region, publicurl=url, adminurl=url, internalurl=url)['endpoint'] cls.addClassResourceCleanup(cls.endpoints_client.delete_endpoint, endpoint['id']) # list_endpoints() will return 'enabled' field endpoint['enabled'] = True cls.setup_endpoints.append(endpoint) @decorators.idempotent_id('11f590eb-59d8-4067-8b2b-980c7f387f51') def test_list_endpoints(self): # Get a list of endpoints fetched_endpoints = self.endpoints_client.list_endpoints()['endpoints'] # Asserting LIST endpoints missing_endpoints =\ [e for e in self.setup_endpoints if e not in fetched_endpoints] self.assertEmpty(missing_endpoints, "Failed to find endpoint %s in fetched list" % ', '.join(str(e) for e in missing_endpoints)) @decorators.idempotent_id('9974530a-aa28-4362-8403-f06db02b26c1') def test_create_list_delete_endpoint(self): region = data_utils.rand_name('region') url = data_utils.rand_url() endpoint = self.endpoints_client.create_endpoint( service_id=self.service_id, region=region, publicurl=url, adminurl=url, internalurl=url)['endpoint'] # Asserting Create Endpoint response body self.assertIn('id', endpoint) self.assertEqual(region, endpoint['region']) self.assertEqual(url, endpoint['publicurl']) # Checking if created endpoint is present in the list of endpoints fetched_endpoints = self.endpoints_client.list_endpoints()['endpoints'] fetched_endpoints_id = [e['id'] for e in fetched_endpoints] self.assertIn(endpoint['id'], fetched_endpoints_id) # Deleting the endpoint created in this method self.endpoints_client.delete_endpoint(endpoint['id']) # Checking whether endpoint is deleted successfully fetched_endpoints = self.endpoints_client.list_endpoints()['endpoints'] fetched_endpoints_id = [e['id'] for e in fetched_endpoints] self.assertNotIn(endpoint['id'], fetched_endpoints_id) tempest-17.2.0/tempest/api/identity/admin/v2/test_users_negative.py0000666000175100017510000003064313207044712025454 0ustar zuulzuul00000000000000# 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 tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class UsersNegativeTestJSON(base.BaseIdentityV2AdminTest): @classmethod def resource_setup(cls): super(UsersNegativeTestJSON, cls).resource_setup() cls.alt_user = data_utils.rand_name('test_user') cls.alt_password = data_utils.rand_password() cls.alt_email = cls.alt_user + '@testmail.tm' @decorators.attr(type=['negative']) @decorators.idempotent_id('60a1f5fa-5744-4cdf-82bf-60b7de2d29a4') def test_create_user_by_unauthorized_user(self): # Non-administrator should not be authorized to create a user tenant = self.setup_test_tenant() self.assertRaises(lib_exc.Forbidden, self.non_admin_users_client.create_user, name=self.alt_user, password=self.alt_password, tenantId=tenant['id'], email=self.alt_email) @decorators.attr(type=['negative']) @decorators.idempotent_id('d80d0c2f-4514-4d1e-806d-0930dfc5a187') def test_create_user_with_empty_name(self): # User with an empty name should not be created tenant = self.setup_test_tenant() self.assertRaises(lib_exc.BadRequest, self.users_client.create_user, name='', password=self.alt_password, tenantId=tenant['id'], email=self.alt_email) @decorators.attr(type=['negative']) @decorators.idempotent_id('7704b4f3-3b75-4b82-87cc-931d41c8f780') def test_create_user_with_name_length_over_255(self): # Length of user name filed should be restricted to 255 characters tenant = self.setup_test_tenant() self.assertRaises(lib_exc.BadRequest, self.users_client.create_user, name='a' * 256, password=self.alt_password, tenantId=tenant['id'], email=self.alt_email) @decorators.attr(type=['negative']) @decorators.idempotent_id('57ae8558-120c-4723-9308-3751474e7ecf') def test_create_user_with_duplicate_name(self): # Duplicate user should not be created password = data_utils.rand_password() user = self.setup_test_user(password) tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] self.assertRaises(lib_exc.Conflict, self.users_client.create_user, name=user['name'], password=password, tenantId=tenant['id'], email=user['email']) @decorators.attr(type=['negative']) @decorators.idempotent_id('0132cc22-7c4f-42e1-9e50-ac6aad31d59a') def test_create_user_for_non_existent_tenant(self): # Attempt to create a user in a non-existent tenant should fail self.assertRaises(lib_exc.NotFound, self.users_client.create_user, name=self.alt_user, password=self.alt_password, tenantId='49ffgg99999', email=self.alt_email) @decorators.attr(type=['negative']) @decorators.idempotent_id('55bbb103-d1ae-437b-989b-bcdf8175c1f4') def test_create_user_request_without_a_token(self): # Request to create a user without a valid token should fail tenant = self.setup_test_tenant() # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) # Unset the token to allow further tests to generate a new token self.addCleanup(self.client.auth_provider.clear_auth) self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user, name=self.alt_user, password=self.alt_password, tenantId=tenant['id'], email=self.alt_email) @decorators.attr(type=['negative']) @decorators.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad') def test_create_user_with_enabled_non_bool(self): # Attempt to create a user with valid enabled para should fail tenant = self.setup_test_tenant() name = data_utils.rand_name('test_user') self.assertRaises(lib_exc.BadRequest, self.users_client.create_user, name=name, password=self.alt_password, tenantId=tenant['id'], email=self.alt_email, enabled=3) @decorators.attr(type=['negative']) @decorators.idempotent_id('3d07e294-27a0-4144-b780-a2a1bf6fee19') def test_update_user_for_non_existent_user(self): # Attempt to update a user non-existent user should fail user_name = data_utils.rand_name('user') non_existent_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.users_client.update_user, non_existent_id, name=user_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('3cc2a64b-83aa-4b02-88f0-d6ab737c4466') def test_update_user_request_without_a_token(self): # Request to update a user without a valid token should fail # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) # Unset the token to allow further tests to generate a new token self.addCleanup(self.client.auth_provider.clear_auth) self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user, self.alt_user) @decorators.attr(type=['negative']) @decorators.idempotent_id('424868d5-18a7-43e1-8903-a64f95ee3aac') def test_update_user_by_unauthorized_user(self): # Non-administrator should not be authorized to update user self.assertRaises(lib_exc.Forbidden, self.non_admin_users_client.update_user, self.alt_user) @decorators.attr(type=['negative']) @decorators.idempotent_id('d45195d5-33ed-41b9-a452-7d0d6a00f6e9') def test_delete_users_by_unauthorized_user(self): # Non-administrator user should not be authorized to delete a user user = self.setup_test_user() self.assertRaises(lib_exc.Forbidden, self.non_admin_users_client.delete_user, user['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('7cc82f7e-9998-4f89-abae-23df36495867') def test_delete_non_existent_user(self): # Attempt to delete a non-existent user should fail self.assertRaises(lib_exc.NotFound, self.users_client.delete_user, 'junk12345123') @decorators.attr(type=['negative']) @decorators.idempotent_id('57fe1df8-0aa7-46c0-ae9f-c2e785c7504a') def test_delete_user_request_without_a_token(self): # Request to delete a user without a valid token should fail # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) # Unset the token to allow further tests to generate a new token self.addCleanup(self.client.auth_provider.clear_auth) self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user, self.alt_user) @decorators.attr(type=['negative']) @decorators.idempotent_id('593a4981-f6d4-460a-99a1-57a78bf20829') def test_authentication_for_disabled_user(self): # Disabled user's token should not get authenticated password = data_utils.rand_password() user = self.setup_test_user(password) tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] self.disable_user(user['name']) self.assertRaises(lib_exc.Unauthorized, self.token_client.auth, user['name'], password, tenant['name']) @decorators.attr(type=['negative']) @decorators.idempotent_id('440a7a8d-9328-4b7b-83e0-d717010495e4') def test_authentication_when_tenant_is_disabled(self): # User's token for a disabled tenant should not be authenticated password = data_utils.rand_password() user = self.setup_test_user(password) tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] self.disable_tenant(tenant['name']) self.assertRaises(lib_exc.Unauthorized, self.token_client.auth, user['name'], password, tenant['name']) @decorators.attr(type=['negative']) @decorators.idempotent_id('921f1ad6-7907-40b8-853f-637e7ee52178') def test_authentication_with_invalid_tenant(self): # User's token for an invalid tenant should not be authenticated password = data_utils.rand_password() user = self.setup_test_user(password) self.assertRaises(lib_exc.Unauthorized, self.token_client.auth, user['name'], password, 'junktenant1234') @decorators.attr(type=['negative']) @decorators.idempotent_id('bde9aecd-3b1c-4079-858f-beb5deaa5b5e') def test_authentication_with_invalid_username(self): # Non-existent user's token should not get authenticated password = data_utils.rand_password() user = self.setup_test_user(password) tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] self.assertRaises(lib_exc.Unauthorized, self.token_client.auth, 'junkuser123', password, tenant['name']) @decorators.attr(type=['negative']) @decorators.idempotent_id('d5308b33-3574-43c3-8d87-1c090c5e1eca') def test_authentication_with_invalid_password(self): # User's token with invalid password should not be authenticated user = self.setup_test_user() tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] self.assertRaises(lib_exc.Unauthorized, self.token_client.auth, user['name'], 'junkpass1234', tenant['name']) @decorators.attr(type=['negative']) @decorators.idempotent_id('284192ce-fb7c-4909-a63b-9a502e0ddd11') def test_get_users_by_unauthorized_user(self): # Non-administrator user should not be authorized to get user list self.assertRaises(lib_exc.Forbidden, self.non_admin_users_client.list_users) @decorators.attr(type=['negative']) @decorators.idempotent_id('a73591ec-1903-4ffe-be42-282b39fefc9d') def test_get_users_request_without_token(self): # Request to get list of users without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) # Unset the token to allow further tests to generate a new token self.addCleanup(self.client.auth_provider.clear_auth) self.assertRaises(lib_exc.Unauthorized, self.users_client.list_users) @decorators.attr(type=['negative']) @decorators.idempotent_id('f5d39046-fc5f-425c-b29e-bac2632da28e') def test_list_users_with_invalid_tenant(self): # Should not be able to return a list of all # users for a non-existent tenant # Assign invalid tenant ids invalid_id = list() invalid_id.append(data_utils.rand_name('999')) invalid_id.append('alpha') invalid_id.append(data_utils.rand_name("dddd@#%%^$")) invalid_id.append('!@#()$%^&*?<>{}[]') # List the users with invalid tenant id for invalid in invalid_id: self.assertRaises(lib_exc.NotFound, self.tenants_client.list_tenant_users, invalid) tempest-17.2.0/tempest/api/identity/admin/v2/test_tokens_negative.py0000666000175100017510000000301313207044712025605 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class TokensAdminTestNegative(base.BaseIdentityV2AdminTest): credentials = ['primary', 'admin', 'alt'] @decorators.attr(type=['negative']) @decorators.idempotent_id('a0a0a600-4292-4364-99c5-922c834fdf05') def test_check_token_existence_negative(self): creds = self.os_primary.credentials creds_alt = self.os_alt.credentials username = creds.username password = creds.password tenant_name = creds.tenant_name alt_tenant_name = creds_alt.tenant_name body = self.token_client.auth(username, password, tenant_name) self.assertRaises(lib_exc.Unauthorized, self.client.check_token_existence, body['token']['id'], belongsTo=alt_tenant_name) tempest-17.2.0/tempest/api/identity/admin/v2/test_users.py0000666000175100017510000002111413207044712023563 0ustar zuulzuul00000000000000# 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 time from testtools import matchers from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class UsersTestJSON(base.BaseIdentityV2AdminTest): @classmethod def resource_setup(cls): super(UsersTestJSON, cls).resource_setup() cls.alt_user = data_utils.rand_name('test_user') cls.alt_email = cls.alt_user + '@testmail.tm' @decorators.attr(type='smoke') @decorators.idempotent_id('2d55a71e-da1d-4b43-9c03-d269fd93d905') def test_create_user(self): # Create a user tenant = self.setup_test_tenant() user = self.create_test_user(name=self.alt_user, tenantId=tenant['id']) self.assertEqual(self.alt_user, user['name']) @decorators.idempotent_id('89d9fdb8-15c2-4304-a429-48715d0af33d') def test_create_user_with_enabled(self): # Create a user with enabled : False tenant = self.setup_test_tenant() name = data_utils.rand_name('test_user') user = self.create_test_user(name=name, tenantId=tenant['id'], email=self.alt_email, enabled=False) self.assertEqual(name, user['name']) self.assertEqual(False, user['enabled']) self.assertEqual(self.alt_email, user['email']) @decorators.idempotent_id('39d05857-e8a5-4ed4-ba83-0b52d3ab97ee') def test_update_user(self): # Test case to check if updating of user attributes is successful. tenant = self.setup_test_tenant() user = self.create_test_user(tenantId=tenant['id']) # Updating user details with new values u_name2 = data_utils.rand_name('user2') u_email2 = u_name2 + '@testmail.tm' update_user = self.users_client.update_user(user['id'], name=u_name2, email=u_email2, enabled=False)['user'] self.assertEqual(u_name2, update_user['name']) self.assertEqual(u_email2, update_user['email']) self.assertEqual(False, update_user['enabled']) # GET by id after updating updated_user = self.users_client.show_user(user['id'])['user'] # Assert response body of GET after updating self.assertEqual(u_name2, updated_user['name']) self.assertEqual(u_email2, updated_user['email']) self.assertEqual(False, update_user['enabled']) @decorators.idempotent_id('29ed26f4-a74e-4425-9a85-fdb49fa269d2') def test_delete_user(self): # Delete a user tenant = self.setup_test_tenant() user = self.create_test_user(tenantId=tenant['id']) self.users_client.delete_user(user['id']) @decorators.idempotent_id('aca696c3-d645-4f45-b728-63646045beb1') def test_user_authentication(self): # Valid user's token is authenticated password = data_utils.rand_password() user = self.setup_test_user(password) tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] # Get a token self.token_client.auth(user['name'], password, tenant['name']) # Re-auth self.token_client.auth(user['name'], password, tenant['name']) @decorators.idempotent_id('5d1fa498-4c2d-4732-a8fe-2b054598cfdd') def test_authentication_request_without_token(self): # Request for token authentication with a valid token in header password = data_utils.rand_password() user = self.setup_test_user(password) tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] self.token_client.auth(user['name'], password, tenant['name']) # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) # Re-auth self.token_client.auth(user['name'], password, tenant['name']) self.client.auth_provider.clear_auth() @decorators.idempotent_id('a149c02e-e5e0-4b89-809e-7e8faf33ccda') def test_get_users(self): # Get a list of users and find the test user user = self.setup_test_user() users = self.users_client.list_users()['users'] self.assertThat([u['name'] for u in users], matchers.Contains(user['name']), "Could not find %s" % user['name']) @decorators.idempotent_id('6e317209-383a-4bed-9f10-075b7c82c79a') def test_list_users_for_tenant(self): # Return a list of all users for a tenant tenant = self.setup_test_tenant() user_ids = list() fetched_user_ids = list() user1 = self.create_test_user(tenantId=tenant['id']) user_ids.append(user1['id']) user2 = self.create_test_user(tenantId=tenant['id']) user_ids.append(user2['id']) # List of users for the respective tenant ID body = (self.tenants_client.list_tenant_users(tenant['id']) ['users']) for i in body: fetched_user_ids.append(i['id']) # verifying the user Id in the list missing_users =\ [user for user in user_ids if user not in fetched_user_ids] self.assertEmpty(missing_users, "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) @decorators.idempotent_id('a8b54974-40e1-41c0-b812-50fc90827971') def test_list_users_with_roles_for_tenant(self): # Return list of users on tenant when roles are assigned to users user = self.setup_test_user() tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] role = self.setup_test_role() # Assigning roles to two users user_ids = list() fetched_user_ids = list() user_ids.append(user['id']) role = self.roles_client.create_user_role_on_project( tenant['id'], user['id'], role['id'])['role'] second_user = self.create_test_user(tenantId=tenant['id']) user_ids.append(second_user['id']) role = self.roles_client.create_user_role_on_project( tenant['id'], second_user['id'], role['id'])['role'] # List of users with roles for the respective tenant ID body = (self.tenants_client.list_tenant_users(tenant['id'])['users']) for i in body: fetched_user_ids.append(i['id']) # verifying the user Id in the list missing_users = [missing_user for missing_user in user_ids if missing_user not in fetched_user_ids] self.assertEmpty(missing_users, "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) @decorators.idempotent_id('1aeb25ac-6ec5-4d8b-97cb-7ac3567a989f') def test_update_user_password(self): # Test case to check if updating of user password is successful. user = self.setup_test_user() tenant = self.tenants_client.show_tenant(user['tenantId'])['tenant'] # Updating the user with new password new_pass = data_utils.rand_password() update_user = self.users_client.update_user_password( user['id'], password=new_pass)['user'] self.assertEqual(update_user['id'], user['id']) # NOTE(morganfainberg): Fernet tokens are not subsecond aware and # Keystone should only be precise to the second. Sleep to ensure # we are passing the second boundary. time.sleep(1) # Validate the updated password through getting a token. body = self.token_client.auth(user['name'], new_pass, tenant['name']) self.assertIn('id', body['token']) tempest-17.2.0/tempest/api/identity/admin/v2/test_tenant_negative.py0000666000175100017510000001500513207044712025577 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class TenantsNegativeTestJSON(base.BaseIdentityV2AdminTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('ca9bb202-63dd-4240-8a07-8ef9c19c04bb') def test_list_tenants_by_unauthorized_user(self): # Non-administrator user should not be able to list tenants self.assertRaises(lib_exc.Forbidden, self.non_admin_tenants_client.list_tenants) @decorators.attr(type=['negative']) @decorators.idempotent_id('df33926c-1c96-4d8d-a762-79cc6b0c3cf4') def test_list_tenant_request_without_token(self): # Request to list tenants without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.tenants_client.list_tenants) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('162ba316-f18b-4987-8c0c-fd9140cd63ed') def test_tenant_delete_by_unauthorized_user(self): # Non-administrator user should not be able to delete a tenant tenant = self.setup_test_tenant() self.assertRaises(lib_exc.Forbidden, self.non_admin_tenants_client.delete_tenant, tenant['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('e450db62-2e9d-418f-893a-54772d6386b1') def test_tenant_delete_request_without_token(self): # Request to delete a tenant without a valid token should fail tenant = self.setup_test_tenant() token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.tenants_client.delete_tenant, tenant['id']) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('9c9a2aed-6e3c-467a-8f5c-89da9d1b516b') def test_delete_non_existent_tenant(self): # Attempt to delete a non existent tenant should fail self.assertRaises(lib_exc.NotFound, self.tenants_client.delete_tenant, data_utils.rand_uuid_hex()) @decorators.attr(type=['negative']) @decorators.idempotent_id('af16f44b-a849-46cb-9f13-a751c388f739') def test_tenant_create_duplicate(self): # Tenant names should be unique tenant_name = data_utils.rand_name(name='tenant') self.setup_test_tenant(name=tenant_name) self.assertRaises(lib_exc.Conflict, self.tenants_client.create_tenant, name=tenant_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('d26b278a-6389-4702-8d6e-5980d80137e0') def test_create_tenant_by_unauthorized_user(self): # Non-administrator user should not be authorized to create a tenant tenant_name = data_utils.rand_name(name='tenant') self.assertRaises(lib_exc.Forbidden, self.non_admin_tenants_client.create_tenant, name=tenant_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('a3ee9d7e-6920-4dd5-9321-d4b2b7f0a638') def test_create_tenant_request_without_token(self): # Create tenant request without a token should not be authorized tenant_name = data_utils.rand_name(name='tenant') token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.tenants_client.create_tenant, name=tenant_name) self.client.auth_provider.clear_auth() @decorators.attr(type=['negative']) @decorators.idempotent_id('5a2e4ca9-b0c0-486c-9c48-64a94fba2395') def test_create_tenant_with_empty_name(self): # Tenant name should not be empty self.assertRaises(lib_exc.BadRequest, self.tenants_client.create_tenant, name='') @decorators.attr(type=['negative']) @decorators.idempotent_id('2ff18d1e-dfe3-4359-9dc3-abf582c196b9') def test_create_tenants_name_length_over_64(self): # Tenant name length should not be greater than 64 characters tenant_name = 'a' * 65 self.assertRaises(lib_exc.BadRequest, self.tenants_client.create_tenant, name=tenant_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('bd20dc2a-9557-4db7-b755-f48d952ad706') def test_update_non_existent_tenant(self): # Attempt to update a non existent tenant should fail self.assertRaises(lib_exc.NotFound, self.tenants_client.update_tenant, data_utils.rand_uuid_hex()) @decorators.attr(type=['negative']) @decorators.idempotent_id('41704dc5-c5f7-4f79-abfa-76e6fedc570b') def test_tenant_update_by_unauthorized_user(self): # Non-administrator user should not be able to update a tenant tenant = self.setup_test_tenant() self.assertRaises(lib_exc.Forbidden, self.non_admin_tenants_client.update_tenant, tenant['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('7a421573-72c7-4c22-a98e-ce539219c657') def test_tenant_update_request_without_token(self): # Request to update a tenant without a valid token should fail tenant = self.setup_test_tenant() token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(lib_exc.Unauthorized, self.tenants_client.update_tenant, tenant['id']) self.client.auth_provider.clear_auth() tempest-17.2.0/tempest/api/identity/admin/v2/test_tokens.py0000666000175100017510000001273713207044712023740 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class TokensTestJSON(base.BaseIdentityV2AdminTest): @decorators.idempotent_id('453ad4d5-e486-4b2f-be72-cffc8149e586') def test_create_check_get_delete_token(self): # get a token by username and password user_name = data_utils.rand_name(name='user') user_password = data_utils.rand_password() # first:create a tenant tenant = self.setup_test_tenant() # second:create a user user = self.create_test_user(name=user_name, password=user_password, tenantId=tenant['id'], email='') # then get a token for the user body = self.token_client.auth(user_name, user_password, tenant['name']) self.assertEqual(body['token']['tenant']['name'], tenant['name']) # Perform GET Token token_id = body['token']['id'] self.client.check_token_existence(token_id) token_details = self.client.show_token(token_id)['access'] self.assertEqual(token_id, token_details['token']['id']) self.assertEqual(user['id'], token_details['user']['id']) self.assertEqual(user_name, token_details['user']['name']) self.assertEqual(tenant['name'], token_details['token']['tenant']['name']) # then delete the token self.client.delete_token(token_id) self.assertRaises(lib_exc.NotFound, self.client.check_token_existence, token_id) @decorators.idempotent_id('25ba82ee-8a32-4ceb-8f50-8b8c71e8765e') def test_rescope_token(self): """An unscoped token can be requested That token can be used to request a scoped token. """ # Create a user. user_name = data_utils.rand_name(name='user') user_password = data_utils.rand_password() tenant_id = None # No default tenant so will get unscoped token. user = self.create_test_user(name=user_name, password=user_password, tenantId=tenant_id, email='') # Create a couple tenants. tenant1_name = data_utils.rand_name(name='tenant') tenant1 = self.setup_test_tenant(name=tenant1_name) tenant2_name = data_utils.rand_name(name='tenant') tenant2 = self.setup_test_tenant(name=tenant2_name) # Create a role role = self.setup_test_role() # Grant the user the role on the tenants. self.roles_client.create_user_role_on_project(tenant1['id'], user['id'], role['id']) self.roles_client.create_user_role_on_project(tenant2['id'], user['id'], role['id']) # Get an unscoped token. body = self.token_client.auth(user_name, user_password) token_id = body['token']['id'] # Use the unscoped token to get a token scoped to tenant1 body = self.token_client.auth_token(token_id, tenant=tenant1_name) scoped_token_id = body['token']['id'] # Revoke the scoped token self.client.delete_token(scoped_token_id) # Use the unscoped token to get a token scoped to tenant2 body = self.token_client.auth_token(token_id, tenant=tenant2_name) @decorators.idempotent_id('ca3ea6f7-ed08-4a61-adbd-96906456ad31') def test_list_endpoints_for_token(self): # get a token for the user creds = self.os_primary.credentials username = creds.username password = creds.password tenant_name = creds.tenant_name token = self.token_client.auth(username, password, tenant_name)['token'] endpoints = self.client.list_endpoints_for_token( token['id'])['endpoints'] self.assertIsInstance(endpoints, list) # Store list of service names service_names = [e['name'] for e in endpoints] # Get the list of available services. available_services = [s[0] for s in list( CONF.service_available.items()) if s[1] is True] # Verify that all available services are present. for service in available_services: self.assertIn(service, service_names) tempest-17.2.0/tempest/api/identity/__init__.py0000666000175100017510000000150513207044712021505 0ustar zuulzuul00000000000000# 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_log import log as logging LOG = logging.getLogger(__name__) # All identity tests -- single setup function def setup_package(): LOG.debug("Entering tempest.api.identity.setup_package") tempest-17.2.0/tempest/api/identity/base.py0000666000175100017510000002722213207044712020664 0ustar zuulzuul00000000000000# 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 tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils import tempest.test CONF = config.CONF class BaseIdentityTest(tempest.test.BaseTestCase): @classmethod def setup_credentials(cls): # Create no network resources for these test. cls.set_network_resources() super(BaseIdentityTest, cls).setup_credentials() @classmethod def disable_user(cls, user_name): user = cls.get_user_by_name(user_name) cls.users_client.update_user_enabled(user['id'], enabled=False) @classmethod def disable_tenant(cls, tenant_name): tenant = cls.get_tenant_by_name(tenant_name) cls.tenants_client.update_tenant(tenant['id'], enabled=False) @classmethod 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 user: return user[0] @classmethod def get_tenant_by_name(cls, name): try: tenants = cls.tenants_client.list_tenants()['tenants'] except AttributeError: tenants = cls.projects_client.list_projects()['projects'] tenant = [t for t in tenants if t['name'] == name] if tenant: return tenant[0] @classmethod def get_role_by_name(cls, name): roles = cls.roles_client.list_roles()['roles'] role = [r for r in roles if r['name'] == name] if role: return role[0] def create_test_user(self, **kwargs): if kwargs.get('password', None) is None: kwargs['password'] = data_utils.rand_password() if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name('test_user') if 'email' not in kwargs: kwargs['email'] = kwargs['name'] + '@testmail.tm' user = self.users_client.create_user(**kwargs)['user'] # Delete the user at the end of the test self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.users_client.delete_user, user['id']) return user def setup_test_role(self, name=None, domain_id=None): """Set up a test role.""" params = {'name': name or data_utils.rand_name('test_role')} if domain_id: params['domain_id'] = domain_id role = self.roles_client.create_role(**params)['role'] # Delete the role at the end of the test self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.roles_client.delete_role, role['id']) return role class BaseIdentityV2Test(BaseIdentityTest): credentials = ['primary'] # identity v2 tests should obtain tokens and create accounts via v2 # regardless of the configured CONF.identity.auth_version identity_version = 'v2' @classmethod def setup_clients(cls): super(BaseIdentityV2Test, cls).setup_clients() cls.non_admin_client = cls.os_primary.identity_public_client cls.non_admin_token_client = cls.os_primary.token_client cls.non_admin_tenants_client = cls.os_primary.tenants_public_client cls.non_admin_users_client = cls.os_primary.users_public_client class BaseIdentityV2AdminTest(BaseIdentityV2Test): credentials = ['primary', 'admin'] # NOTE(andreaf) Identity tests work with credentials, so it is safer # for them to always use disposable credentials. Forcing dynamic creds # on regular identity tests would be however to restrictive, since it # would prevent any identity test from being executed against clouds where # admin credentials are not available. # Since All admin tests require admin credentials to be # executed, so this will not impact the ability to execute tests. force_tenant_isolation = True @classmethod def skip_checks(cls): super(BaseIdentityV2AdminTest, cls).skip_checks() if not CONF.identity_feature_enabled.api_v2_admin: raise cls.skipException('Identity v2 admin not available') @classmethod def setup_clients(cls): super(BaseIdentityV2AdminTest, cls).setup_clients() cls.client = cls.os_admin.identity_client cls.non_admin_client = cls.os_primary.identity_client cls.token_client = cls.os_admin.token_client cls.tenants_client = cls.os_admin.tenants_client cls.non_admin_tenants_client = cls.os_primary.tenants_client cls.roles_client = cls.os_admin.roles_client cls.non_admin_roles_client = cls.os_primary.roles_client cls.users_client = cls.os_admin.users_client cls.non_admin_users_client = cls.os_primary.users_client cls.services_client = cls.os_admin.identity_services_client cls.endpoints_client = cls.os_admin.endpoints_client @classmethod def resource_setup(cls): super(BaseIdentityV2AdminTest, cls).resource_setup() cls.projects_client = cls.tenants_client def setup_test_user(self, password=None): """Set up a test user.""" tenant = self.setup_test_tenant() user = self.create_test_user(tenantId=tenant['id'], password=password) return user def setup_test_tenant(self, **kwargs): """Set up a test tenant.""" if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name('test_tenant') if 'description' not in kwargs: kwargs['description'] = data_utils.rand_name('desc') tenant = self.projects_client.create_tenant(**kwargs)['tenant'] # Delete the tenant at the end of the test self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.tenants_client.delete_tenant, tenant['id']) return tenant class BaseIdentityV3Test(BaseIdentityTest): credentials = ['primary'] # identity v3 tests should obtain tokens and create accounts via v3 # regardless of the configured CONF.identity.auth_version identity_version = 'v3' @classmethod def setup_clients(cls): super(BaseIdentityV3Test, cls).setup_clients() cls.non_admin_client = cls.os_primary.identity_v3_client cls.non_admin_users_client = cls.os_primary.users_v3_client cls.non_admin_token = cls.os_primary.token_v3_client cls.non_admin_projects_client = cls.os_primary.projects_client cls.non_admin_catalog_client = cls.os_primary.catalog_client cls.non_admin_versions_client =\ cls.os_primary.identity_versions_v3_client class BaseIdentityV3AdminTest(BaseIdentityV3Test): credentials = ['primary', 'admin'] # NOTE(andreaf) Identity tests work with credentials, so it is safer # for them to always use disposable credentials. Forcing dynamic creds # on regular identity tests would be however to restrictive, since it # would prevent any identity test from being executed against clouds where # admin credentials are not available. # Since All admin tests require admin credentials to be # executed, so this will not impact the ability to execute tests. force_tenant_isolation = True @classmethod def setup_clients(cls): super(BaseIdentityV3AdminTest, cls).setup_clients() cls.client = cls.os_admin.identity_v3_client cls.domains_client = cls.os_admin.domains_client cls.users_client = cls.os_admin.users_v3_client cls.trusts_client = cls.os_admin.trusts_client cls.roles_client = cls.os_admin.roles_v3_client cls.inherited_roles_client = cls.os_admin.inherited_roles_client cls.token = cls.os_admin.token_v3_client cls.endpoints_client = cls.os_admin.endpoints_v3_client cls.regions_client = cls.os_admin.regions_client cls.services_client = cls.os_admin.identity_services_v3_client cls.policies_client = cls.os_admin.policies_client cls.creds_client = cls.os_admin.credentials_client cls.groups_client = cls.os_admin.groups_client cls.projects_client = cls.os_admin.projects_client cls.role_assignments = cls.os_admin.role_assignments_client cls.oauth_consumers_client = cls.os_admin.oauth_consumers_client cls.oauth_token_client = cls.os_admin.oauth_token_client cls.domain_config_client = cls.os_admin.domain_config_client cls.endpoint_filter_client = cls.os_admin.endpoint_filter_client cls.endpoint_groups_client = cls.os_admin.endpoint_groups_client if CONF.identity.admin_domain_scope: # NOTE(andreaf) When keystone policy requires it, the identity # admin clients for these tests shall use 'domain' scoped tokens. # As the client manager is already created by the base class, # we set the scope for the inner auth provider. cls.os_admin.auth_provider.scope = 'domain' @classmethod 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'], name=user_name, enabled=False) @classmethod def create_domain(cls, **kwargs): """Create a domain.""" if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name('test_domain') if 'description' not in kwargs: kwargs['description'] = data_utils.rand_name('desc') domain = cls.domains_client.create_domain(**kwargs)['domain'] cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.delete_domain, domain['id']) return domain @classmethod def delete_domain(cls, domain_id): # NOTE(mpavlase) It is necessary to disable the domain before deleting # otherwise it raises Forbidden exception cls.domains_client.update_domain(domain_id, enabled=False) cls.domains_client.delete_domain(domain_id) def setup_test_user(self, password=None): """Set up a test user.""" project = self.setup_test_project() user = self.create_test_user(project_id=project['id'], password=password) return user def setup_test_project(self, **kwargs): """Set up a test project.""" if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name('test_project') if 'description' not in kwargs: kwargs['description'] = data_utils.rand_name('test_description') project = self.projects_client.create_project(**kwargs)['project'] # Delete the project at the end of the test self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.projects_client.delete_project, project['id']) return project def setup_test_domain(self): """Set up a test domain.""" domain = self.create_domain() # Delete the domain at the end of the test self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.delete_domain, domain['id']) return domain tempest-17.2.0/tempest/api/identity/v3/0000775000175100017510000000000013207045130017714 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/v3/test_catalog.py0000666000175100017510000000354613207044712022756 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 import config from tempest.lib import decorators CONF = config.CONF class IdentityCatalogTest(base.BaseIdentityV3Test): @decorators.idempotent_id('56b57ced-22b8-4127-9b8a-565dfb0207e2') def test_catalog_standardization(self): # http://git.openstack.org/cgit/openstack/service-types-authority # /tree/service-types.yaml standard_service_values = [{'name': 'keystone', 'type': 'identity'}, {'name': 'nova', 'type': 'compute'}, {'name': 'glance', 'type': 'image'}, {'name': 'swift', 'type': 'object-store'}] # next, we need to GET the catalog using the catalog client catalog = self.non_admin_catalog_client.show_catalog()['catalog'] # get list of the service types present in the catalog catalog_services = [] for service in catalog: catalog_services.append(service['type']) for service in standard_service_values: # if service enabled, check if it has a standard typevalue if service['name'] == 'keystone' or\ getattr(CONF.service_available, service['name']): self.assertIn(service['type'], catalog_services) tempest-17.2.0/tempest/api/identity/v3/test_api_discovery.py0000666000175100017510000000600113207044712024171 0ustar zuulzuul00000000000000# Copyright 2015 OpenStack Foundation. # Copyright 2015, Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib import decorators class TestApiDiscovery(base.BaseIdentityV3Test): """Tests for API discovery features.""" @decorators.idempotent_id('721f480f-35b6-46c7-846e-047e6acea0dc') @decorators.attr(type='smoke') def test_list_api_versions(self): # NOTE: Actually this API doesn't depend on v3 API at all, because # the API operation is "GET /" without v3's endpoint. The reason of # this test path is just v3 API is CURRENT on Keystone side. versions = self.non_admin_versions_client.list_versions() expected_resources = ('id', 'links', 'media-types', 'status', 'updated') for version in versions['versions']["values"]: for res in expected_resources: self.assertIn(res, version) @decorators.attr(type='smoke') @decorators.idempotent_id('b9232f5e-d9e5-4d97-b96c-28d3db4de1bd') def test_api_version_resources(self): descr = self.non_admin_client.show_api_description()['version'] expected_resources = ('id', 'links', 'media-types', 'status', 'updated') keys = descr.keys() for res in expected_resources: self.assertIn(res, keys) @decorators.attr(type='smoke') @decorators.idempotent_id('657c1970-4722-4189-8831-7325f3bc4265') def test_api_media_types(self): descr = self.non_admin_client.show_api_description()['version'] # Get MIME type bases and descriptions media_types = [(media_type['base'], media_type['type']) for media_type in descr['media-types']] # These are supported for API version 2 supported_types = [('application/json', 'application/vnd.openstack.identity-v3+json')] # Check if supported types exist in response body for s_type in supported_types: self.assertIn(s_type, media_types) @decorators.attr(type='smoke') @decorators.idempotent_id('8879a470-abfb-47bb-bb8d-5a7fd279ad1e') def test_api_version_statuses(self): descr = self.non_admin_client.show_api_description()['version'] status = descr['status'].lower() supported_statuses = ['current', 'stable', 'experimental', 'supported', 'deprecated'] self.assertIn(status, supported_statuses) tempest-17.2.0/tempest/api/identity/v3/__init__.py0000666000175100017510000000000013207044712022022 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/v3/test_projects.py0000666000175100017510000000513313207044712023167 0ustar zuulzuul00000000000000# Copyright 2015 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 tempest.api.identity import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class IdentityV3ProjectsTest(base.BaseIdentityV3Test): credentials = ['primary', 'alt'] @decorators.idempotent_id('86128d46-e170-4644-866a-cc487f699e1d') def test_list_projects_returns_only_authorized_projects(self): alt_project_name = self.os_alt.credentials.project_name resp = self.non_admin_users_client.list_user_projects( self.os_primary.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_primary.credentials.username, user_domain_id=self.os_primary.credentials.user_domain_id, password=self.os_primary.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']) self.assertEqual(body['project']['name'], project['name']) self.assertEqual( body['user']['id'], self.os_primary.credentials.user_id) # check that user cannot log in to alt user's project self.assertRaises( lib_exc.Unauthorized, self.non_admin_token.get_token, username=self.os_primary.credentials.username, user_domain_id=self.os_primary.credentials.user_domain_id, password=self.os_primary.credentials.password, project_name=alt_project_name, project_domain_id=project['domain_id']) tempest-17.2.0/tempest/api/identity/v3/test_users.py0000666000175100017510000001515313207044712022502 0ustar zuulzuul00000000000000# Copyright 2015 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 time import testtools from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class IdentityV3UsersTest(base.BaseIdentityV3Test): @classmethod def resource_setup(cls): super(IdentityV3UsersTest, cls).resource_setup() cls.creds = cls.os_primary.credentials cls.user_id = cls.creds.user_id def _update_password(self, original_password, password): self.non_admin_users_client.update_user_password( self.user_id, password=password, original_password=original_password) # NOTE(morganfainberg): Fernet tokens are not subsecond aware and # Keystone should only be precise to the second. Sleep to ensure # we are passing the second boundary. time.sleep(1) # check authorization with new password self.non_admin_token.auth(user_id=self.user_id, password=password) # Reset auth to get a new token with the new password self.non_admin_users_client.auth_provider.clear_auth() self.non_admin_users_client.auth_provider.credentials.password = ( password) def _restore_password(self, old_pass, new_pass): if CONF.identity_feature_enabled.security_compliance: # First we need to clear the password history unique_count = CONF.identity.user_unique_last_password_count for _ in range(unique_count): random_pass = data_utils.rand_password() self._update_password( original_password=new_pass, password=random_pass) new_pass = random_pass self._update_password(original_password=new_pass, password=old_pass) # Reset auth again to verify the password restore does work. # Clear auth restores the original credentials and deletes # cached auth data self.non_admin_users_client.auth_provider.clear_auth() # NOTE(lbragstad): Fernet tokens are not subsecond aware and # Keystone should only be precise to the second. Sleep to ensure we # are passing the second boundary before attempting to # authenticate. time.sleep(1) self.non_admin_users_client.auth_provider.set_auth() @decorators.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27') def test_user_update_own_password(self): old_pass = self.creds.password old_token = self.non_admin_client.token new_pass = data_utils.rand_password() # to change password back. important for allow_tenant_isolation = false self.addCleanup(self._restore_password, old_pass, new_pass) # user updates own password self._update_password(original_password=old_pass, password=new_pass) # authorize with old token should lead to IdentityError (404 code) self.assertRaises(exceptions.IdentityError, self.non_admin_token.auth, token=old_token) # authorize with old password should lead to Unauthorized self.assertRaises(exceptions.Unauthorized, self.non_admin_token.auth, user_id=self.user_id, password=old_pass) @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance, 'Security compliance not available.') @decorators.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516') def test_password_history_check_self_service_api(self): old_pass = self.creds.password new_pass1 = data_utils.rand_password() new_pass2 = data_utils.rand_password() self.addCleanup(self._restore_password, old_pass, new_pass2) # Update password self._update_password(original_password=old_pass, password=new_pass1) if CONF.identity.user_unique_last_password_count > 1: # Can not reuse a previously set password self.assertRaises(exceptions.BadRequest, self.non_admin_users_client.update_user_password, self.user_id, password=new_pass1, original_password=new_pass1) self.assertRaises(exceptions.BadRequest, self.non_admin_users_client.update_user_password, self.user_id, password=old_pass, original_password=new_pass1) # A different password can be set self._update_password(original_password=new_pass1, password=new_pass2) @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance, 'Security compliance not available.') @decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658') def test_user_account_lockout(self): password = self.creds.password # First, we login using the correct credentials self.non_admin_token.auth(user_id=self.user_id, password=password) # Lock user account by using the wrong password to login bad_password = data_utils.rand_password() for _ in range(CONF.identity.user_lockout_failure_attempts): self.assertRaises(exceptions.Unauthorized, self.non_admin_token.auth, user_id=self.user_id, password=bad_password) # The user account must be locked, so now it is not possible to login # even using the correct password self.assertRaises(exceptions.Unauthorized, self.non_admin_token.auth, user_id=self.user_id, password=password) # If we wait the required time, the user account will be unlocked time.sleep(CONF.identity.user_lockout_duration + 1) self.non_admin_token.auth(user_id=self.user_id, password=password) tempest-17.2.0/tempest/api/identity/v3/test_tokens.py0000666000175100017510000000701513207044712022642 0ustar zuulzuul00000000000000# Copyright 2015 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_utils import timeutils import six from tempest.api.identity import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class TokensV3Test(base.BaseIdentityV3Test): @decorators.idempotent_id('a9512ac3-3909-48a4-b395-11f438e16260') def test_validate_token(self): creds = self.os_primary.credentials user_id = creds.user_id username = creds.username password = creds.password user_domain_id = creds.user_domain_id # GET and validate token subject_token, token_body = self.non_admin_token.get_token( user_id=user_id, username=username, user_domain_id=user_domain_id, password=password, auth_data=True) authenticated_token = self.non_admin_client.show_token( subject_token)['token'] # sanity checking to make sure they are indeed the same token self.assertEqual(authenticated_token, token_body) # test to see if token has been properly authenticated self.assertEqual(authenticated_token['user']['id'], user_id) self.assertEqual(authenticated_token['user']['name'], username) self.non_admin_client.delete_token(subject_token) self.assertRaises( lib_exc.NotFound, self.non_admin_client.show_token, subject_token) @decorators.idempotent_id('6f8e4436-fc96-4282-8122-e41df57197a9') def test_create_token(self): creds = self.os_primary.credentials user_id = creds.user_id username = creds.username password = creds.password user_domain_id = creds.user_domain_id # '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, username=username, user_domain_id=user_domain_id, password=password, auth_data=True) self.assertNotEmpty(token_id) self.assertIsInstance(token_id, six.string_types) now = timeutils.utcnow() expires_at = timeutils.normalize_time( timeutils.parse_isotime(resp['expires_at'])) self.assertGreater(resp['expires_at'], resp['issued_at']) self.assertGreater(expires_at, now) subject_id = resp['user']['id'] if user_id: self.assertEqual(subject_id, user_id) else: # Expect a user ID, but don't know what it will be. self.assertIsNotNone(subject_id, 'Expected user ID in token.') subject_name = resp['user']['name'] if username: self.assertEqual(subject_name, username) else: # Expect a user name, but don't know what it will be. self.assertIsNotNone(subject_name, 'Expected user name in token.') self.assertEqual(resp['methods'][0], 'password') tempest-17.2.0/tempest/api/identity/v2/0000775000175100017510000000000013207045130017713 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/v2/test_extension.py0000666000175100017510000000226513207044712023354 0ustar zuulzuul00000000000000# 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.api.identity import base from tempest.lib import decorators class ExtensionTestJSON(base.BaseIdentityV2Test): @decorators.idempotent_id('85f3f661-f54c-4d48-b563-72ae952b9383') def test_list_extensions(self): # List all the extensions body = self.non_admin_client.list_extensions()['extensions']['values'] self.assertNotEmpty(body) keys = ['name', 'updated', 'alias', 'links', 'namespace', 'description'] for value in body: for key in keys: self.assertIn(key, value) tempest-17.2.0/tempest/api/identity/v2/test_api_discovery.py0000666000175100017510000000452713207044712024203 0ustar zuulzuul00000000000000# Copyright 2015 OpenStack Foundation. # Copyright 2015, Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.lib import decorators class TestApiDiscovery(base.BaseIdentityV2Test): """Tests for API discovery features.""" @decorators.attr(type='smoke') @decorators.idempotent_id('ea889a68-a15f-4166-bfb1-c12456eae853') def test_api_version_resources(self): descr = self.non_admin_client.show_api_description()['version'] expected_resources = ('id', 'links', 'media-types', 'status', 'updated') keys = descr.keys() for res in expected_resources: self.assertIn(res, keys) @decorators.attr(type='smoke') @decorators.idempotent_id('007a0be0-78fe-4fdb-bbee-e9216cc17bb2') def test_api_media_types(self): descr = self.non_admin_client.show_api_description()['version'] # Get MIME type bases and descriptions media_types = [(media_type['base'], media_type['type']) for media_type in descr['media-types']] # These are supported for API version 2 supported_types = [('application/json', 'application/vnd.openstack.identity-v2.0+json')] # Check if supported types exist in response body for s_type in supported_types: self.assertIn(s_type, media_types) @decorators.attr(type='smoke') @decorators.idempotent_id('77fd6be0-8801-48e6-b9bf-38cdd2f253ec') def test_api_version_statuses(self): descr = self.non_admin_client.show_api_description()['version'] status = descr['status'].lower() supported_statuses = ['current', 'stable', 'experimental', 'supported', 'deprecated'] self.assertIn(status, supported_statuses) tempest-17.2.0/tempest/api/identity/v2/test_tenants.py0000666000175100017510000000413313207044712023010 0ustar zuulzuul00000000000000# Copyright 2015 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 tempest.api.identity import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class IdentityTenantsTest(base.BaseIdentityV2Test): credentials = ['primary', 'alt'] @decorators.idempotent_id('ecae2459-243d-4ba1-ad02-65f15dc82b78') def test_list_tenants_returns_only_authorized_tenants(self): alt_tenant_name = self.os_alt.credentials.tenant_name resp = self.non_admin_tenants_client.list_tenants() # check that user can see only that tenants that he presents in so user # can successfully authenticate using his credentials and tenant name # from received tenants list for tenant in resp['tenants']: body = self.non_admin_token_client.auth( self.os_primary.credentials.username, self.os_primary.credentials.password, tenant['name']) self.assertNotEmpty(body['token']['id']) self.assertEqual(body['token']['tenant']['id'], tenant['id']) self.assertEqual(body['token']['tenant']['name'], tenant['name']) self.assertEqual( body['user']['id'], self.os_primary.credentials.user_id) # check that user cannot log in to alt user's tenant self.assertRaises( lib_exc.Unauthorized, self.non_admin_token_client.auth, self.os_primary.credentials.username, self.os_primary.credentials.password, alt_tenant_name) tempest-17.2.0/tempest/api/identity/v2/__init__.py0000666000175100017510000000000013207044712022021 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/identity/v2/test_ec2_credentials.py0000666000175100017510000001142213207044712024361 0ustar zuulzuul00000000000000# Copyright 2015 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 tempest.api.identity import base from tempest.common import utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class EC2CredentialsTest(base.BaseIdentityV2Test): @classmethod def skip_checks(cls): super(EC2CredentialsTest, cls).skip_checks() if not utils.is_extension_enabled('OS-EC2', 'identity'): msg = "OS-EC2 identity extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(EC2CredentialsTest, cls).resource_setup() cls.creds = cls.os_primary.credentials @decorators.idempotent_id('b580fab9-7ae9-46e8-8138-417260cb6f9f') def test_create_ec2_credential(self): """Create user ec2 credential.""" resp = self.non_admin_users_client.create_user_ec2_credential( self.creds.user_id, tenant_id=self.creds.tenant_id)["credential"] access = resp['access'] self.addCleanup( self.non_admin_users_client.delete_user_ec2_credential, self.creds.user_id, access) self.assertNotEmpty(resp['access']) self.assertNotEmpty(resp['secret']) self.assertEqual(self.creds.user_id, resp['user_id']) self.assertEqual(self.creds.tenant_id, resp['tenant_id']) @decorators.idempotent_id('9e2ea42f-0a4f-468c-a768-51859ce492e0') def test_list_ec2_credentials(self): """Get the list of user ec2 credentials.""" created_creds = [] # create first ec2 credentials creds1 = self.non_admin_users_client.create_user_ec2_credential( self.creds.user_id, tenant_id=self.creds.tenant_id)["credential"] created_creds.append(creds1['access']) # create second ec2 credentials creds2 = self.non_admin_users_client.create_user_ec2_credential( self.creds.user_id, tenant_id=self.creds.tenant_id)["credential"] created_creds.append(creds2['access']) # add credentials to be cleaned up self.addCleanup( self.non_admin_users_client.delete_user_ec2_credential, self.creds.user_id, creds1['access']) self.addCleanup( self.non_admin_users_client.delete_user_ec2_credential, self.creds.user_id, creds2['access']) # get the list of user ec2 credentials resp = self.non_admin_users_client.list_user_ec2_credentials( self.creds.user_id)["credentials"] fetched_creds = [cred['access'] for cred in resp] # created credentials should be in a fetched list missing = [cred for cred in created_creds if cred not in fetched_creds] self.assertEmpty(missing, "Failed to find ec2_credentials %s in fetched list" % ', '.join(cred for cred in missing)) @decorators.idempotent_id('cb284075-b613-440d-83ca-fe0b33b3c2b8') def test_show_ec2_credential(self): """Get the definite user ec2 credential.""" resp = self.non_admin_users_client.create_user_ec2_credential( self.creds.user_id, tenant_id=self.creds.tenant_id)["credential"] self.addCleanup( self.non_admin_users_client.delete_user_ec2_credential, self.creds.user_id, resp['access']) ec2_creds = self.non_admin_users_client.show_user_ec2_credential( self.creds.user_id, resp['access'] )["credential"] for key in ['access', 'secret', 'user_id', 'tenant_id']: self.assertEqual(ec2_creds[key], resp[key]) @decorators.idempotent_id('6aba0d4c-b76b-4e46-aa42-add79bc1551d') def test_delete_ec2_credential(self): """Delete user ec2 credential.""" resp = self.non_admin_users_client.create_user_ec2_credential( self.creds.user_id, tenant_id=self.creds.tenant_id)["credential"] access = resp['access'] self.non_admin_users_client.delete_user_ec2_credential( self.creds.user_id, access) self.assertRaises( lib_exc.NotFound, self.non_admin_users_client.show_user_ec2_credential, self.creds.user_id, access) tempest-17.2.0/tempest/api/identity/v2/test_users.py0000666000175100017510000001032213207044712022472 0ustar zuulzuul00000000000000# Copyright 2015 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 time from tempest.api.identity import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class IdentityUsersTest(base.BaseIdentityV2Test): @classmethod def resource_setup(cls): super(IdentityUsersTest, cls).resource_setup() cls.creds = cls.os_primary.credentials cls.username = cls.creds.username cls.password = cls.creds.password cls.tenant_name = cls.creds.tenant_name def _update_password(self, user_id, original_password, password): self.non_admin_users_client.update_user_own_password( user_id, password=password, original_password=original_password) # NOTE(morganfainberg): Fernet tokens are not subsecond aware and # Keystone should only be precise to the second. Sleep to ensure # we are passing the second boundary. time.sleep(1) # check authorization with new password self.non_admin_token_client.auth(self.username, password, self.tenant_name) # Reset auth to get a new token with the new password self.non_admin_users_client.auth_provider.clear_auth() self.non_admin_users_client.auth_provider.credentials.password = ( password) def _restore_password(self, user_id, old_pass, new_pass): if CONF.identity_feature_enabled.security_compliance: # First we need to clear the password history unique_count = CONF.identity.user_unique_last_password_count for _ in range(unique_count): random_pass = data_utils.rand_password() self._update_password( user_id, original_password=new_pass, password=random_pass) new_pass = random_pass self._update_password( user_id, original_password=new_pass, password=old_pass) # Reset auth again to verify the password restore does work. # Clear auth restores the original credentials and deletes # cached auth data self.non_admin_users_client.auth_provider.clear_auth() # NOTE(lbragstad): Fernet tokens are not subsecond aware and # Keystone should only be precise to the second. Sleep to ensure we # are passing the second boundary before attempting to # authenticate. time.sleep(1) self.non_admin_users_client.auth_provider.set_auth() @decorators.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7') def test_user_update_own_password(self): old_pass = self.creds.password old_token = self.non_admin_users_client.token new_pass = data_utils.rand_password() user_id = self.creds.user_id # to change password back. important for allow_tenant_isolation = false self.addCleanup(self._restore_password, user_id, old_pass, new_pass) # user updates own password self._update_password( user_id, original_password=old_pass, password=new_pass) # authorize with old token should lead to Unauthorized self.assertRaises(exceptions.Unauthorized, self.non_admin_token_client.auth_token, old_token) # authorize with old password should lead to Unauthorized self.assertRaises(exceptions.Unauthorized, self.non_admin_token_client.auth, self.username, old_pass, self.tenant_name) tempest-17.2.0/tempest/api/identity/v2/test_tokens.py0000666000175100017510000000335413207044712022643 0ustar zuulzuul00000000000000# Copyright 2015 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_utils import timeutils import six from tempest.api.identity import base from tempest.lib import decorators class TokensTest(base.BaseIdentityV2Test): @decorators.idempotent_id('65ae3b78-91ff-467b-a705-f6678863b8ec') def test_create_token(self): token_client = self.non_admin_token_client # get a token for the user creds = self.os_primary.credentials username = creds.username password = creds.password tenant_name = creds.tenant_name body = token_client.auth(username, password, tenant_name) self.assertNotEmpty(body['token']['id']) self.assertIsInstance(body['token']['id'], six.string_types) now = timeutils.utcnow() expires_at = timeutils.normalize_time( timeutils.parse_isotime(body['token']['expires'])) self.assertGreater(expires_at, now) self.assertEqual(body['token']['tenant']['id'], creds.tenant_id) self.assertEqual(body['token']['tenant']['name'], tenant_name) self.assertEqual(body['user']['id'], creds.user_id) tempest-17.2.0/tempest/api/__init__.py0000666000175100017510000000000013207044712017641 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/volume/0000775000175100017510000000000013207045130017042 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/volume/api_microversion_fixture.py0000666000175100017510000000204213207044712024537 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.lib.services.volume import base_client class APIMicroversionFixture(fixtures.Fixture): def __init__(self, volume_microversion): self.volume_microversion = volume_microversion def _setUp(self): super(APIMicroversionFixture, self)._setUp() base_client.VOLUME_MICROVERSION = self.volume_microversion self.addCleanup(self._reset_volume_microversion) def _reset_volume_microversion(self): base_client.VOLUME_MICROVERSION = None tempest-17.2.0/tempest/api/volume/test_snapshot_metadata.py0000666000175100017510000001124213207044712024161 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.volume import base from tempest import config from tempest.lib import decorators CONF = config.CONF class SnapshotMetadataTestJSON(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(SnapshotMetadataTestJSON, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder snapshot feature disabled") @classmethod def resource_setup(cls): super(SnapshotMetadataTestJSON, cls).resource_setup() # Create a volume cls.volume = cls.create_volume() # Create a snapshot cls.snapshot = cls.create_snapshot(volume_id=cls.volume['id']) def tearDown(self): # Update the metadata to {} self.snapshots_client.update_snapshot_metadata( self.snapshot['id'], metadata={}) super(SnapshotMetadataTestJSON, self).tearDown() @decorators.idempotent_id('a2f20f99-e363-4584-be97-bc33afb1a56c') def test_crud_snapshot_metadata(self): # Create metadata for the snapshot metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update = {"key3": "value3_update", "key4": "value4"} expect = {"key4": "value4"} # Create metadata body = self.snapshots_client.create_snapshot_metadata( self.snapshot['id'], metadata)['metadata'] self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Get the metadata of the snapshot body = self.snapshots_client.show_snapshot_metadata( self.snapshot['id'])['metadata'] self.assertThat(body.items(), matchers.ContainsAll(metadata.items()), 'Create snapshot metadata failed') # Update metadata body = self.snapshots_client.update_snapshot_metadata( self.snapshot['id'], metadata=update)['metadata'] self.assertEqual(update, body) body = self.snapshots_client.show_snapshot_metadata( self.snapshot['id'])['metadata'] self.assertEqual(update, body, 'Update snapshot metadata failed') # Delete one item metadata of the snapshot self.snapshots_client.delete_snapshot_metadata_item( self.snapshot['id'], "key3") body = self.snapshots_client.show_snapshot_metadata( self.snapshot['id'])['metadata'] self.assertThat(body.items(), matchers.ContainsAll(expect.items()), 'Delete one item metadata of the snapshot failed') self.assertNotIn("key3", body) @decorators.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed') def test_update_show_snapshot_metadata_item(self): # Update metadata item for the snapshot metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update_item = {"key3": "value3_update"} expect = {"key1": "value1", "key2": "value2", "key3": "value3_update"} # Create metadata for the snapshot self.snapshots_client.create_snapshot_metadata( self.snapshot['id'], metadata) # Get the metadata of the snapshot body = self.snapshots_client.show_snapshot_metadata( self.snapshot['id'])['metadata'] self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Update metadata item body = self.snapshots_client.update_snapshot_metadata_item( self.snapshot['id'], "key3", meta=update_item)['meta'] self.assertEqual(update_item, body) # Get a specific metadata item of the snapshot body = self.snapshots_client.show_snapshot_metadata_item( self.snapshot['id'], "key3")['meta'] self.assertEqual({"key3": expect['key3']}, body) # Get the metadata of the snapshot body = self.snapshots_client.show_snapshot_metadata( self.snapshot['id'])['metadata'] self.assertThat(body.items(), matchers.ContainsAll(expect.items())) tempest-17.2.0/tempest/api/volume/test_image_metadata.py0000666000175100017510000000570413207044712023412 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.volume import base from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumesImageMetadata(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesImageMetadata, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as Glance is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def resource_setup(cls): super(VolumesImageMetadata, cls).resource_setup() # Create a volume from image ID cls.volume = cls.create_volume(imageRef=CONF.compute.image_ref) @decorators.idempotent_id('03efff0b-5c75-4822-8f10-8789ac15b13e') @utils.services('image') def test_update_show_delete_image_metadata(self): # Update image metadata image_metadata = {'image_id': '5137a025-3c5f-43c1-bc64-5f41270040a5', 'image_name': 'image', 'kernel_id': '6ff710d2-942b-4d6b-9168-8c9cc2404ab1', 'ramdisk_id': 'somedisk'} self.volumes_client.update_volume_image_metadata(self.volume['id'], **image_metadata) # Fetch volume's image metadata by show_volume method volume_image_metadata = self.volumes_client.show_volume( self.volume['id'])['volume']['volume_image_metadata'] # Verify image metadata was updated self.assertThat(volume_image_metadata.items(), matchers.ContainsAll(image_metadata.items())) # Delete one item from image metadata of the volume self.volumes_client.delete_volume_image_metadata(self.volume['id'], 'ramdisk_id') del image_metadata['ramdisk_id'] # Fetch volume's image metadata by show_volume_image_metadata method volume_image_metadata = self.volumes_client.show_volume_image_metadata( self.volume['id'])['metadata'] # Verify image metadata was updated after item deletion self.assertThat(volume_image_metadata.items(), matchers.ContainsAll(image_metadata.items())) self.assertNotIn('ramdisk_id', volume_image_metadata) tempest-17.2.0/tempest/api/volume/test_volumes_get.py0000666000175100017510000001530213207044712023014 0ustar zuulzuul00000000000000# 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 from testtools import matchers from tempest.api.volume import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumesGetTest(base.BaseVolumeTest): def _volume_create_get_update_delete(self, **kwargs): # Create a volume, Get it's details and Delete the volume v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') metadata = {'Type': 'Test'} # Create a volume kwargs['name'] = v_name kwargs['metadata'] = metadata volume = self.volumes_client.create_volume(**kwargs)['volume'] self.assertIn('id', volume) self.addCleanup(self.delete_volume, self.volumes_client, volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') self.assertIn('name', volume) self.assertEqual(volume['name'], v_name, "The created volume name is not equal " "to the requested name") # Get Volume information fetched_volume = self.volumes_client.show_volume( volume['id'])['volume'] self.assertEqual(v_name, fetched_volume['name'], 'The fetched Volume name is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume id is different ' 'from the created Volume') self.assertThat(fetched_volume['metadata'].items(), matchers.ContainsAll(metadata.items()), 'The fetched Volume metadata misses data ' 'from the created Volume') if 'imageRef' in kwargs: self.assertEqual('true', fetched_volume['bootable']) else: self.assertEqual('false', fetched_volume['bootable']) # Update Volume # Test volume update when display_name is same with original value params = {'name': v_name} self.volumes_client.update_volume(volume['id'], **params) # Test volume update when display_name is new new_v_name = data_utils.rand_name( self.__class__.__name__ + '-new-Volume') new_desc = 'This is the new description of volume' params = {'name': new_v_name, 'description': new_desc} update_volume = self.volumes_client.update_volume( volume['id'], **params)['volume'] # Assert response body for update_volume method self.assertEqual(new_v_name, update_volume['name']) self.assertEqual(new_desc, update_volume['description']) # Assert response body for show_volume method updated_volume = self.volumes_client.show_volume( volume['id'])['volume'] self.assertEqual(volume['id'], updated_volume['id']) self.assertEqual(new_v_name, updated_volume['name']) self.assertEqual(new_desc, updated_volume['description']) self.assertThat(updated_volume['metadata'].items(), matchers.ContainsAll(metadata.items()), 'The fetched Volume metadata misses data ' 'from the created Volume') # Test volume create when display_name is none and display_description # contains specific characters, # then test volume update if display_name is duplicated new_v_desc = data_utils.rand_name('@#$%^* description') params = {'description': new_v_desc, 'availability_zone': volume['availability_zone'], 'size': CONF.volume.volume_size} new_volume = self.volumes_client.create_volume(**params)['volume'] self.assertIn('id', new_volume) self.addCleanup(self.delete_volume, self.volumes_client, new_volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, new_volume['id'], 'available') params = {'name': volume['name'], 'description': volume['description']} self.volumes_client.update_volume(new_volume['id'], **params) if 'imageRef' in kwargs: self.assertEqual('true', updated_volume['bootable']) else: self.assertEqual('false', updated_volume['bootable']) @decorators.attr(type='smoke') @decorators.idempotent_id('27fb0e9f-fb64-41dd-8bdb-1ffa762f0d51') def test_volume_create_get_update_delete(self): self._volume_create_get_update_delete(size=CONF.volume.volume_size) @decorators.attr(type='smoke') @decorators.idempotent_id('54a01030-c7fc-447c-86ee-c1182beae638') @utils.services('image') def test_volume_create_get_update_delete_from_image(self): image = self.images_client.show_image(CONF.compute.image_ref) min_disk = image['min_disk'] disk_size = max(min_disk, CONF.volume.volume_size) self._volume_create_get_update_delete( imageRef=CONF.compute.image_ref, size=disk_size) @decorators.idempotent_id('3f591b4a-7dc6-444c-bd51-77469506b3a1') @testtools.skipUnless(CONF.volume_feature_enabled.clone, 'Cinder volume clones are disabled') def test_volume_create_get_update_delete_as_clone(self): origin = self.create_volume() self._volume_create_get_update_delete(source_volid=origin['id'], size=CONF.volume.volume_size) class VolumesSummaryTest(base.BaseVolumeTest): _api_version = 3 min_microversion = '3.12' max_microversion = 'latest' @decorators.idempotent_id('c4f2431e-4920-4736-9e00-4040386b6feb') def test_show_volume_summary(self): volume_summary = \ self.volumes_client.show_volume_summary()['volume-summary'] for key in ['total_size', 'total_count']: self.assertIn(key, volume_summary) tempest-17.2.0/tempest/api/volume/test_volumes_snapshots_negative.py0000666000175100017510000000717113207044712026146 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumesSnapshotNegativeTestJSON(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesSnapshotNegativeTestJSON, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") @decorators.attr(type=['negative']) @decorators.idempotent_id('e3e466af-70ab-4f4b-a967-ab04e3532ea7') def test_create_snapshot_with_nonexistent_volume_id(self): # Create a snapshot with nonexistent volume id s_name = data_utils.rand_name(self.__class__.__name__ + '-snap') self.assertRaises(lib_exc.NotFound, self.snapshots_client.create_snapshot, volume_id=data_utils.rand_uuid(), display_name=s_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('bb9da53e-d335-4309-9c15-7e76fd5e4d6d') def test_create_snapshot_without_passing_volume_id(self): # Create a snapshot without passing volume id s_name = data_utils.rand_name(self.__class__.__name__ + '-snap') self.assertRaises(lib_exc.NotFound, self.snapshots_client.create_snapshot, volume_id=None, display_name=s_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('677863d1-34f9-456d-b6ac-9924f667a7f4') def test_volume_from_snapshot_decreasing_size(self): # Creates a volume a snapshot passing a size different from the source src_size = CONF.volume.volume_size + 1 src_vol = self.create_volume(size=src_size) src_snap = self.create_snapshot(src_vol['id']) # Destination volume smaller than source self.assertRaises(lib_exc.BadRequest, self.volumes_client.create_volume, size=src_size - 1, snapshot_id=src_snap['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('8fd92339-e22f-4591-86b4-1e2215372a40') def test_list_snapshot_invalid_param_limit(self): self.assertRaises(lib_exc.BadRequest, self.snapshots_client.list_snapshots, limit='invalid') @decorators.attr(type=['negative']) @decorators.idempotent_id('27b5f37f-bf69-4e8c-986e-c44f3d6819b8') def test_list_snapshots_invalid_param_sort(self): self.assertRaises(lib_exc.BadRequest, self.snapshots_client.list_snapshots, sort_key='invalid') @decorators.attr(type=['negative']) @decorators.idempotent_id('b68deeda-ca79-4a32-81af-5c51179e553a') def test_list_snapshots_invalid_param_marker(self): self.assertRaises(lib_exc.NotFound, self.snapshots_client.list_snapshots, marker=data_utils.rand_uuid()) tempest-17.2.0/tempest/api/volume/test_volume_delete_cascade.py0000666000175100017510000000716613207044712024770 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import operator import testtools from tempest.api.volume import base from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumesDeleteCascade(base.BaseVolumeTest): """Delete a volume with associated snapshots. Cinder provides the ability to delete a volume with its associated snapshots. It is allow a volume and its snapshots to be removed in one operation both for usability and performance reasons. """ @classmethod def skip_checks(cls): super(VolumesDeleteCascade, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder snapshot feature disabled") def _assert_cascade_delete(self, volume_id): # Fetch volume ids volume_list = [ vol['id'] for vol in self.volumes_client.list_volumes()['volumes'] ] # Verify the parent volume was deleted self.assertNotIn(volume_id, volume_list) # List snapshots snapshot_list = self.snapshots_client.list_snapshots()['snapshots'] # Verify snapshots were deleted self.assertNotIn(volume_id, map(operator.itemgetter('volume_id'), snapshot_list)) @decorators.idempotent_id('994e2d40-de37-46e8-b328-a58fba7e4a95') def test_volume_delete_cascade(self): # The case validates the ability to delete a volume # with associated snapshots. # Create a volume volume = self.create_volume() for _ in range(2): self.create_snapshot(volume['id']) # Delete the parent volume with associated snapshots self.volumes_client.delete_volume(volume['id'], cascade=True) self.volumes_client.wait_for_resource_deletion(volume['id']) # Verify volume parent was deleted with its associated snapshots self._assert_cascade_delete(volume['id']) @decorators.idempotent_id('59a77ede-609b-4ee8-9f68-fc3c6ffe97b5') @testtools.skipIf(CONF.volume.storage_protocol == 'ceph', 'Skip because of Bug#1677525') def test_volume_from_snapshot_cascade_delete(self): # The case validates the ability to delete a volume with # associated snapshot while there is another volume created # from that snapshot. # Create a volume volume = self.create_volume() snapshot = self.create_snapshot(volume['id']) # Create volume from snapshot volume_snap = self.create_volume(snapshot_id=snapshot['id']) volume_details = self.volumes_client.show_volume( volume_snap['id'])['volume'] self.assertEqual(snapshot['id'], volume_details['snapshot_id']) # Delete the parent volume with associated snapshot self.volumes_client.delete_volume(volume['id'], cascade=True) self.volumes_client.wait_for_resource_deletion(volume['id']) # Verify volume parent was deleted with its associated snapshot self._assert_cascade_delete(volume['id']) tempest-17.2.0/tempest/api/volume/admin/0000775000175100017510000000000013207045130020132 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/volume/admin/test_multi_backend.py0000666000175100017510000001275013207044712024360 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 six from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumeMultiBackendTest(base.BaseVolumeAdminTest): @classmethod def skip_checks(cls): super(VolumeMultiBackendTest, cls).skip_checks() if not CONF.volume_feature_enabled.multi_backend: raise cls.skipException("Cinder multi-backend feature disabled") @classmethod def resource_setup(cls): super(VolumeMultiBackendTest, cls).resource_setup() # read backend name from a list . backend_names = set(CONF.volume.backend_names) cls.volume_id_list_with_prefix = [] cls.volume_id_list_without_prefix = [] # Volume/Type creation (uses volume_backend_name) # It is not allowed to create the same backend name twice if len(backend_names) < 2: raise cls.skipException("Requires at least two different " "backend names") for backend_name in backend_names: # Volume/Type creation (uses backend_name) cls._create_type_and_volume(backend_name, False) # Volume/Type creation (uses capabilities:volume_backend_name) cls._create_type_and_volume(backend_name, True) @classmethod def _create_type_and_volume(cls, backend_name_key, with_prefix): # Volume/Type creation type_name = data_utils.rand_name(cls.__name__ + '-Type') vol_name = data_utils.rand_name(cls.__name__ + '-Volume') spec_key_with_prefix = "capabilities:volume_backend_name" spec_key_without_prefix = "volume_backend_name" if with_prefix: extra_specs = {spec_key_with_prefix: backend_name_key} else: extra_specs = {spec_key_without_prefix: backend_name_key} cls.create_volume_type(name=type_name, extra_specs=extra_specs) params = {'name': vol_name, 'volume_type': type_name, 'size': CONF.volume.volume_size} cls.volume = cls.create_volume(**params) if with_prefix: cls.volume_id_list_with_prefix.append(cls.volume['id']) else: cls.volume_id_list_without_prefix.append( cls.volume['id']) waiters.wait_for_volume_resource_status(cls.admin_volume_client, cls.volume['id'], 'available') @decorators.idempotent_id('c1a41f3f-9dad-493e-9f09-3ff197d477cc') def test_backend_name_reporting(self): # get volume id which created by type without prefix for volume_id in self.volume_id_list_without_prefix: self._test_backend_name_reporting_by_volume_id(volume_id) @decorators.idempotent_id('f38e647f-ab42-4a31-a2e7-ca86a6485215') def test_backend_name_reporting_with_prefix(self): # get volume id which created by type with prefix for volume_id in self.volume_id_list_with_prefix: self._test_backend_name_reporting_by_volume_id(volume_id) @decorators.idempotent_id('46435ab1-a0af-4401-8373-f14e66b0dd58') def test_backend_name_distinction(self): # get volume ids which created by type without prefix self._test_backend_name_distinction(self.volume_id_list_without_prefix) @decorators.idempotent_id('4236305b-b65a-4bfc-a9d2-69cb5b2bf2ed') def test_backend_name_distinction_with_prefix(self): # get volume ids which created by type without prefix self._test_backend_name_distinction(self.volume_id_list_with_prefix) def _get_volume_host(self, volume_id): return self.admin_volume_client.show_volume( volume_id)['volume']['os-vol-host-attr:host'] def _test_backend_name_reporting_by_volume_id(self, volume_id): # this test checks if os-vol-attr:host is populated correctly after # the multi backend feature has been enabled # if multi-backend is enabled: os-vol-attr:host should be like: # host@backend_name volume = self.admin_volume_client.show_volume(volume_id)['volume'] volume1_host = volume['os-vol-host-attr:host'] msg = ("multi-backend reporting incorrect values for volume %s" % volume_id) self.assertGreater(len(volume1_host.split("@")), 1, msg) def _test_backend_name_distinction(self, volume_id_list): # this test checks that the volumes created at setUp don't # belong to the same backend (if they are, than the # volume backend distinction is not working properly) volume_hosts = [self._get_volume_host(volume) for volume in volume_id_list] # assert that volumes are each created on separate hosts: msg = ("volumes %s were created in the same backend" % ", " .join(volume_hosts)) six.assertCountEqual(self, volume_hosts, set(volume_hosts), msg) tempest-17.2.0/tempest/api/volume/admin/test_volume_quotas.py0000666000175100017510000002005313207044712024455 0ustar zuulzuul00000000000000# Copyright (C) 2014 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import identity from tempest.common import tempest_fixtures as fixtures from tempest.common import waiters from tempest.lib.common.utils import data_utils from tempest.lib import decorators QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups', 'backup_gigabytes', 'per_volume_gigabytes'] QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use'] class BaseVolumeQuotasAdminTestJSON(base.BaseVolumeAdminTest): force_tenant_isolation = True credentials = ['primary', 'alt', 'admin'] def setUp(self): # NOTE(jeremy.zhang): Avoid conflicts with volume quota class tests. self.useFixture(fixtures.LockFixture('volume_quotas')) super(BaseVolumeQuotasAdminTestJSON, self).setUp() @classmethod def setup_credentials(cls): super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials() cls.demo_tenant_id = cls.os_primary.credentials.tenant_id cls.alt_client = cls.os_alt.volumes_client_latest @classmethod def setup_clients(cls): super(BaseVolumeQuotasAdminTestJSON, cls).setup_clients() cls.transfer_client = cls.os_primary.volume_transfers_v2_client cls.alt_transfer_client = cls.os_alt.volume_transfers_v2_client @decorators.idempotent_id('59eada70-403c-4cef-a2a3-a8ce2f1b07a0') def test_list_quotas(self): quotas = (self.admin_quotas_client.show_quota_set(self.demo_tenant_id) ['quota_set']) for key in QUOTA_KEYS: self.assertIn(key, quotas) @decorators.idempotent_id('2be020a2-5fdd-423d-8d35-a7ffbc36e9f7') def test_list_default_quotas(self): quotas = self.admin_quotas_client.show_default_quota_set( self.demo_tenant_id)['quota_set'] for key in QUOTA_KEYS: self.assertIn(key, quotas) @decorators.idempotent_id('3d45c99e-cc42-4424-a56e-5cbd212b63a6') def test_update_all_quota_resources_for_tenant(self): # Admin can update all the resource quota limits for a tenant default_quota_set = self.admin_quotas_client.show_default_quota_set( self.demo_tenant_id)['quota_set'] new_quota_set = {'gigabytes': 1009, 'volumes': 11, 'snapshots': 11, 'backups': 11, 'backup_gigabytes': 1009, 'per_volume_gigabytes': 1009} # Update limits for all quota resources quota_set = self.admin_quotas_client.update_quota_set( self.demo_tenant_id, **new_quota_set)['quota_set'] cleanup_quota_set = dict( (k, v) for k, v in default_quota_set.items() if k in QUOTA_KEYS) self.addCleanup(self.admin_quotas_client.update_quota_set, self.demo_tenant_id, **cleanup_quota_set) # test that the specific values we set are actually in # the final result. There is nothing here that ensures there # would be no other values in there. self.assertDictContainsSubset(new_quota_set, quota_set) @decorators.idempotent_id('18c51ae9-cb03-48fc-b234-14a19374dbed') def test_show_quota_usage(self): quota_usage = self.admin_quotas_client.show_quota_set( self.os_admin.credentials.tenant_id, params={'usage': True})['quota_set'] for key in QUOTA_KEYS: self.assertIn(key, quota_usage) for usage_key in QUOTA_USAGE_KEYS: self.assertIn(usage_key, quota_usage[key]) @decorators.idempotent_id('ae8b6091-48ad-4bfa-a188-bbf5cc02115f') def test_quota_usage(self): quota_usage = self.admin_quotas_client.show_quota_set( self.demo_tenant_id, params={'usage': True})['quota_set'] volume = self.create_volume() self.addCleanup(self.delete_volume, self.volumes_client, volume['id']) new_quota_usage = self.admin_quotas_client.show_quota_set( self.demo_tenant_id, params={'usage': True})['quota_set'] self.assertEqual(quota_usage['volumes']['in_use'] + 1, new_quota_usage['volumes']['in_use']) self.assertEqual(quota_usage['gigabytes']['in_use'] + volume["size"], new_quota_usage['gigabytes']['in_use']) @decorators.idempotent_id('874b35a9-51f1-4258-bec5-cd561b6690d3') def test_delete_quota(self): # Admin can delete the resource quota set for a project project_name = data_utils.rand_name('quota_tenant') description = data_utils.rand_name('desc_') project = identity.identity_utils(self.os_admin).create_project( project_name, description=description) project_id = project['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_project, project_id) quota_set_default = self.admin_quotas_client.show_default_quota_set( project_id)['quota_set'] volume_default = quota_set_default['volumes'] self.admin_quotas_client.update_quota_set( project_id, volumes=(volume_default + 5)) self.admin_quotas_client.delete_quota_set(project_id) quota_set_new = (self.admin_quotas_client.show_quota_set(project_id) ['quota_set']) self.assertEqual(volume_default, quota_set_new['volumes']) @decorators.idempotent_id('8911036f-9d54-4720-80cc-a1c9796a8805') def test_quota_usage_after_volume_transfer(self): # Create a volume for transfer volume = self.create_volume() self.addCleanup(self.delete_volume, self.admin_volume_client, volume['id']) # List of tenants quota usage pre-transfer primary_quota = self.admin_quotas_client.show_quota_set( self.demo_tenant_id, params={'usage': True})['quota_set'] alt_quota = self.admin_quotas_client.show_quota_set( self.alt_client.tenant_id, params={'usage': True})['quota_set'] # Creates a volume transfer transfer = self.transfer_client.create_volume_transfer( volume_id=volume['id'])['transfer'] transfer_id = transfer['id'] auth_key = transfer['auth_key'] # Accepts a volume transfer self.alt_transfer_client.accept_volume_transfer( transfer_id, auth_key=auth_key) # Verify volume transferred is available waiters.wait_for_volume_resource_status( self.alt_client, volume['id'], 'available') # List of tenants quota usage post transfer new_primary_quota = self.admin_quotas_client.show_quota_set( self.demo_tenant_id, params={'usage': True})['quota_set'] new_alt_quota = self.admin_quotas_client.show_quota_set( self.alt_client.tenant_id, params={'usage': True})['quota_set'] # Verify tenants quota usage was updated self.assertEqual(primary_quota['volumes']['in_use'] - new_primary_quota['volumes']['in_use'], new_alt_quota['volumes']['in_use'] - alt_quota['volumes']['in_use']) self.assertEqual(alt_quota['gigabytes']['in_use'] + volume['size'], new_alt_quota['gigabytes']['in_use']) self.assertEqual(primary_quota['gigabytes']['in_use'] - volume['size'], new_primary_quota['gigabytes']['in_use']) tempest-17.2.0/tempest/api/volume/admin/test_volume_manage.py0000666000175100017510000000667113207044712024403 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class VolumeManageAdminTest(base.BaseVolumeAdminTest): @classmethod def skip_checks(cls): super(VolumeManageAdminTest, cls).skip_checks() if not CONF.volume_feature_enabled.manage_volume: raise cls.skipException("Manage volume tests are disabled") if len(CONF.volume.manage_volume_ref) != 2: msg = ("Manage volume ref is not correctly configured, " "it should be a list of two elements") raise exceptions.InvalidConfiguration(msg) @decorators.idempotent_id('70076c71-0ce1-4208-a8ff-36a66e65cc1e') def test_unmanage_manage_volume(self): # Create original volume org_vol_id = self.create_volume()['id'] org_vol_info = self.admin_volume_client.show_volume( org_vol_id)['volume'] # Unmanage the original volume self.admin_volume_client.unmanage_volume(org_vol_id) self.admin_volume_client.wait_for_resource_deletion(org_vol_id) # Verify the original volume does not exist in volume list params = {'all_tenants': 1} all_tenants_volumes = self.admin_volume_client.list_volumes( detail=True, params=params)['volumes'] self.assertNotIn(org_vol_id, [v['id'] for v in all_tenants_volumes]) # Manage volume new_vol_name = data_utils.rand_name( self.__class__.__name__ + '-volume') new_vol_ref = { 'name': new_vol_name, 'host': org_vol_info['os-vol-host-attr:host'], 'ref': {CONF.volume.manage_volume_ref[0]: CONF.volume.manage_volume_ref[1] % org_vol_id}, 'volume_type': org_vol_info['volume_type'], 'availability_zone': org_vol_info['availability_zone']} new_vol_id = self.admin_volume_manage_client.manage_volume( **new_vol_ref)['volume']['id'] self.addCleanup(self.delete_volume, self.admin_volume_client, new_vol_id) waiters.wait_for_volume_resource_status(self.admin_volume_client, new_vol_id, 'available') # Compare the managed volume with the original new_vol_info = self.admin_volume_client.show_volume( new_vol_id)['volume'] self.assertNotIn(new_vol_id, [org_vol_id]) self.assertEqual(new_vol_info['name'], new_vol_name) for key in ['size', 'volume_type', 'availability_zone', 'os-vol-host-attr:host']: self.assertEqual(new_vol_info[key], org_vol_info[key]) tempest-17.2.0/tempest/api/volume/admin/test_volume_services.py0000666000175100017510000000752713207044712024777 0ustar zuulzuul00000000000000# 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.api.volume import base from tempest.lib import decorators def _get_host(host): return host.split('@')[0] class VolumesServicesTestJSON(base.BaseVolumeAdminTest): """Tests Volume Services API. volume service list requires admin privileges. """ @classmethod def resource_setup(cls): super(VolumesServicesTestJSON, cls).resource_setup() cls.services = (cls.admin_volume_services_client.list_services() ['services']) # NOTE: Cinder service-list API returns the list contains # "@" like "nova-compute01@lvmdriver-1". # So here picks up as a host. cls.host_name = _get_host(cls.services[0]['host']) cls.binary_name = cls.services[0]['binary'] @decorators.idempotent_id('e0218299-0a59-4f43-8b2b-f1c035b3d26d') def test_list_services(self): services = (self.admin_volume_services_client.list_services() ['services']) self.assertNotEmpty(services) @decorators.idempotent_id('63a3e1ca-37ee-4983-826d-83276a370d25') def test_get_service_by_service_binary_name(self): services = (self.admin_volume_services_client.list_services( binary=self.binary_name)['services']) self.assertNotEmpty(services) for service in services: self.assertEqual(self.binary_name, service['binary']) @decorators.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 _get_host(service['host']) == self.host_name] services = (self.admin_volume_services_client.list_services( host=self.host_name)['services']) # we could have a periodic job checkin between the 2 service # lookups, so only compare binary lists. s1 = map(lambda x: x['binary'], services) s2 = map(lambda x: x['binary'], services_on_host) # sort the lists before comparing, to take out dependency # on order. self.assertEqual(sorted(s1), sorted(s2)) @decorators.idempotent_id('67ec6902-f91d-4dec-91fa-338523208bbc') def test_get_service_by_volume_host_name(self): volume_id = self.create_volume()['id'] volume = self.admin_volume_client.show_volume(volume_id)['volume'] hostname = _get_host(volume['os-vol-host-attr:host']) services = (self.admin_volume_services_client.list_services( host=hostname, binary='cinder-volume')['services']) self.assertNotEmpty(services, 'cinder-volume not found on host %s' % hostname) self.assertEqual(hostname, _get_host(services[0]['host'])) self.assertEqual('cinder-volume', services[0]['binary']) @decorators.idempotent_id('ffa6167c-4497-4944-a464-226bbdb53908') def test_get_service_by_service_and_host_name(self): services = (self.admin_volume_services_client.list_services( host=self.host_name, binary=self.binary_name))['services'] self.assertNotEmpty(services) self.assertEqual(self.host_name, _get_host(services[0]['host'])) self.assertEqual(self.binary_name, services[0]['binary']) tempest-17.2.0/tempest/api/volume/admin/test_volume_pools.py0000666000175100017510000000275513207044712024306 0ustar zuulzuul00000000000000# Copyright 2016 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 tempest.api.volume import base from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumePoolsAdminTestsJSON(base.BaseVolumeAdminTest): def _assert_pools(self, with_detail=False): cinder_pools = self.admin_scheduler_stats_client.list_pools( detail=with_detail)['pools'] self.assertIn('name', cinder_pools[0]) if with_detail: self.assertIn(CONF.volume.vendor_name, [pool['capabilities']['vendor_name'] for pool in cinder_pools]) @decorators.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7') def test_get_pools_without_details(self): self._assert_pools() @decorators.idempotent_id('d4bb61f7-762d-4437-b8a4-5785759a0ced') def test_get_pools_with_details(self): self._assert_pools(with_detail=True) tempest-17.2.0/tempest/api/volume/admin/test_volume_quota_classes.py0000666000175100017510000001116113207044712026007 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import random from oslo_log import log as logging from testtools import matchers from tempest.api.volume import base from tempest.common import identity from tempest.common import tempest_fixtures as fixtures from tempest.lib.common.utils import data_utils from tempest.lib import decorators LOG = logging.getLogger(__name__) QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups', 'backup_gigabytes', 'per_volume_gigabytes'] class VolumeQuotaClassesTest(base.BaseVolumeAdminTest): def setUp(self): # Note(jeremy.zhang): All test cases in this class need to externally # lock on doing anything with default quota values. self.useFixture(fixtures.LockFixture('volume_quotas')) super(VolumeQuotaClassesTest, self).setUp() def _restore_default_quotas(self, original_defaults): LOG.debug("Restoring volume quota class defaults") self.admin_quota_classes_client.update_quota_class_set( 'default', **original_defaults) @decorators.idempotent_id('abb9198e-67d0-4b09-859f-4f4a1418f176') def test_show_default_quota(self): default_quotas = self.admin_quota_classes_client.show_quota_class_set( 'default')['quota_class_set'] self.assertIn('id', default_quotas) self.assertEqual('default', default_quotas.pop('id')) for key in QUOTA_KEYS: self.assertIn(key, default_quotas) @decorators.idempotent_id('a7644c63-2669-467a-b00e-452dd5c5397b') def test_update_default_quota(self): LOG.debug("Get the current default quota class values") body = self.admin_quota_classes_client.show_quota_class_set( 'default')['quota_class_set'] # Note(jeremyZ) Only include specified quota keys to avoid the conflict # that other tests may create/delete volume types or update volume # type's default quotas in concurrency running. update_kwargs = {key: body[key] for key in body if key in QUOTA_KEYS} # Restore the defaults when the test is done. self.addCleanup(self._restore_default_quotas, update_kwargs.copy()) # Note(jeremyZ) Increment some of the values for updating the default # quota class. For safety, only items with value >= 0 will be updated, # and items with value < 0 (-1 means unlimited) will be ignored. for quota, default in update_kwargs.items(): if default >= 0: update_kwargs[quota] = default + 1 # Create a volume type for updating default quotas class. volume_type_name = self.create_volume_type()['name'] for key in ['volumes', 'snapshots', 'gigabytes']: update_kwargs['%s_%s' % (key, volume_type_name)] = \ random.randint(1, 10) LOG.debug("Update limits for the default quota class set") update_body = self.admin_quota_classes_client.update_quota_class_set( 'default', **update_kwargs)['quota_class_set'] self.assertThat(update_body.items(), matchers.ContainsAll(update_kwargs.items())) # Verify current project's default quotas. default_quotas = self.admin_quotas_client.show_default_quota_set( self.os_admin.credentials.tenant_id)['quota_set'] self.assertThat(default_quotas.items(), matchers.ContainsAll(update_kwargs.items())) # Verify a new project's default quotas. project_name = data_utils.rand_name('quota_class_tenant') description = data_utils.rand_name('desc_') project_id = identity.identity_utils(self.os_admin).create_project( name=project_name, description=description)['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_project, project_id) default_quotas = self.admin_quotas_client.show_default_quota_set( project_id)['quota_set'] self.assertThat(default_quotas.items(), matchers.ContainsAll(update_kwargs.items())) tempest-17.2.0/tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py0000666000175100017510000000615313207044712030263 0ustar zuulzuul00000000000000# Copyright 2014 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 tempest.api.volume import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumeSnapshotQuotasNegativeTestJSON(base.BaseVolumeAdminTest): force_tenant_isolation = True @classmethod def skip_checks(cls): super(VolumeSnapshotQuotasNegativeTestJSON, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException('Cinder volume snapshots are disabled') @classmethod def setup_credentials(cls): super(VolumeSnapshotQuotasNegativeTestJSON, cls).setup_credentials() cls.demo_tenant_id = cls.os_primary.credentials.tenant_id @classmethod def resource_setup(cls): super(VolumeSnapshotQuotasNegativeTestJSON, cls).resource_setup() cls.default_volume_size = CONF.volume.volume_size cls.shared_quota_set = {'gigabytes': 3 * cls.default_volume_size, 'volumes': 1, 'snapshots': 1} # NOTE(gfidente): no need to restore original quota set # after the tests as they only work with tenant isolation. cls.admin_quotas_client.update_quota_set( cls.demo_tenant_id, **cls.shared_quota_set) # NOTE(gfidente): no need to delete in tearDown as # they are created using utility wrapper methods. cls.volume = cls.create_volume() cls.snapshot = cls.create_snapshot(volume_id=cls.volume['id']) @decorators.attr(type='negative') @decorators.idempotent_id('02bbf63f-6c05-4357-9d98-2926a94064ff') def test_quota_volume_snapshots(self): self.assertRaises(lib_exc.OverLimit, self.snapshots_client.create_snapshot, volume_id=self.volume['id']) @decorators.attr(type='negative') @decorators.idempotent_id('c99a1ca9-6cdf-498d-9fdf-25832babef27') def test_quota_volume_gigabytes_snapshots(self): self.addCleanup(self.admin_quotas_client.update_quota_set, self.demo_tenant_id, **self.shared_quota_set) new_quota_set = {'gigabytes': 2 * self.default_volume_size, 'volumes': 1, 'snapshots': 2} self.admin_quotas_client.update_quota_set( self.demo_tenant_id, **new_quota_set) self.assertRaises(lib_exc.OverLimit, self.snapshots_client.create_snapshot, volume_id=self.volume['id']) tempest-17.2.0/tempest/api/volume/admin/test_volume_retype_with_migration.py0000666000175100017510000001026513207044712027561 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF LOG = logging.getLogger(__name__) class VolumeRetypeWithMigrationTest(base.BaseVolumeAdminTest): @classmethod def skip_checks(cls): super(VolumeRetypeWithMigrationTest, cls).skip_checks() if not CONF.volume_feature_enabled.multi_backend: raise cls.skipException("Cinder multi-backend feature disabled.") if len(set(CONF.volume.backend_names)) < 2: raise cls.skipException("Requires at least two different " "backend names") @classmethod def resource_setup(cls): super(VolumeRetypeWithMigrationTest, cls).resource_setup() # read backend name from a list. backend_src = CONF.volume.backend_names[0] backend_dst = CONF.volume.backend_names[1] extra_specs_src = {"volume_backend_name": backend_src} extra_specs_dst = {"volume_backend_name": backend_dst} src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src) cls.dst_vol_type = cls.create_volume_type(extra_specs=extra_specs_dst) cls.src_vol = cls.create_volume(volume_type=src_vol_type['name']) @classmethod def resource_cleanup(cls): # When retyping a volume, Cinder creates an internal volume in the # target backend. The volume in the source backend is deleted after # the migration, so we need to wait for Cinder delete this volume # before deleting the types we've created. # This list should return 2 volumes until the copy and cleanup # process is finished. fetched_list = cls.admin_volume_client.list_volumes( params={'all_tenants': True, 'display_name': cls.src_vol['name']})['volumes'] for fetched_vol in fetched_list: if fetched_vol['id'] != cls.src_vol['id']: # This is the Cinder internal volume LOG.debug('Waiting for internal volume %s deletion', fetched_vol['id']) cls.admin_volume_client.wait_for_resource_deletion( fetched_vol['id']) break super(VolumeRetypeWithMigrationTest, cls).resource_cleanup() @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd') def test_available_volume_retype_with_migration(self): keys_with_no_change = ('id', 'size', 'description', 'name', 'user_id', 'os-vol-tenant-attr:tenant_id') keys_with_change = ('volume_type', 'os-vol-host-attr:host') volume_source = self.admin_volume_client.show_volume( self.src_vol['id'])['volume'] self.volumes_client.retype_volume( self.src_vol['id'], new_type=self.dst_vol_type['name'], migration_policy='on-demand') waiters.wait_for_volume_retype(self.volumes_client, self.src_vol['id'], self.dst_vol_type['name']) volume_dest = self.admin_volume_client.show_volume( self.src_vol['id'])['volume'] # Check the volume information after the migration. self.assertEqual('success', volume_dest['os-vol-mig-status-attr:migstat']) self.assertEqual('success', volume_dest['migration_status']) for key in keys_with_no_change: self.assertEqual(volume_source[key], volume_dest[key]) for key in keys_with_change: self.assertNotEqual(volume_source[key], volume_dest[key]) tempest-17.2.0/tempest/api/volume/admin/test_volume_types_negative.py0000666000175100017510000000470313207044712026173 0ustar zuulzuul00000000000000# 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 tempest.api.volume import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class VolumeTypesNegativeTest(base.BaseVolumeAdminTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('878b4e57-faa2-4659-b0d1-ce740a06ae81') def test_create_with_empty_name(self): # Should not be able to create volume type with an empty name. self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.create_volume_type, name='') @decorators.attr(type=['negative']) @decorators.idempotent_id('994610d6-0476-4018-a644-a2602ef5d4aa') def test_get_nonexistent_type_id(self): # Should not be able to get volume type with nonexistent type id. self.assertRaises(lib_exc.NotFound, self.admin_volume_types_client.show_volume_type, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('6b3926d2-7d73-4896-bc3d-e42dfd11a9f6') def test_delete_nonexistent_type_id(self): # Should not be able to delete volume type with nonexistent type id. self.assertRaises(lib_exc.NotFound, self.admin_volume_types_client.delete_volume_type, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('8c09f849-f225-4d78-ba87-bffd9a5e0c6f') def test_create_volume_with_private_volume_type(self): # Should not be able to create volume with private volume type. params = {'os-volume-type-access:is_public': False} volume_type = self.create_volume_type(**params) self.assertRaises(lib_exc.NotFound, self.create_volume, volume_type=volume_type['id']) tempest-17.2.0/tempest/api/volume/admin/test_snapshots_actions.py0000666000175100017510000001206413207044712025317 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class SnapshotsActionsTest(base.BaseVolumeAdminTest): @classmethod def skip_checks(cls): super(SnapshotsActionsTest, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder snapshot feature disabled") @classmethod def resource_setup(cls): super(SnapshotsActionsTest, cls).resource_setup() # Create a test shared volume for tests cls.volume = cls.create_volume() # Create a test shared snapshot for tests cls.snapshot = cls.create_snapshot(volume_id=cls.volume['id']) def tearDown(self): # Set snapshot's status to available after test status = 'available' snapshot_id = self.snapshot['id'] self.admin_snapshots_client.reset_snapshot_status(snapshot_id, status) waiters.wait_for_volume_resource_status(self.snapshots_client, snapshot_id, status) super(SnapshotsActionsTest, self).tearDown() def _create_reset_and_force_delete_temp_snapshot(self, status=None): # Create snapshot, reset snapshot status, # and force delete temp snapshot temp_snapshot = self.create_snapshot(volume_id=self.volume['id']) if status: self.admin_snapshots_client.reset_snapshot_status( temp_snapshot['id'], status) waiters.wait_for_volume_resource_status( self.snapshots_client, temp_snapshot['id'], status) self.admin_snapshots_client.force_delete_snapshot(temp_snapshot['id']) self.snapshots_client.wait_for_resource_deletion(temp_snapshot['id']) def _get_progress_alias(self): return 'os-extended-snapshot-attributes:progress' @decorators.idempotent_id('3e13ca2f-48ea-49f3-ae1a-488e9180d535') def test_reset_snapshot_status(self): # Reset snapshot status to creating status = 'creating' self.admin_snapshots_client.reset_snapshot_status( self.snapshot['id'], status) waiters.wait_for_volume_resource_status(self.snapshots_client, self.snapshot['id'], status) @decorators.idempotent_id('41288afd-d463-485e-8f6e-4eea159413eb') def test_update_snapshot_status(self): # Reset snapshot status to creating status = 'creating' self.admin_snapshots_client.reset_snapshot_status( self.snapshot['id'], status) waiters.wait_for_volume_resource_status(self.snapshots_client, self.snapshot['id'], status) # Update snapshot status to error progress = '80%' status = 'error' progress_alias = self._get_progress_alias() self.snapshots_client.update_snapshot_status(self.snapshot['id'], status=status, progress=progress) snapshot_get = self.admin_snapshots_client.show_snapshot( self.snapshot['id'])['snapshot'] self.assertEqual(status, snapshot_get['status']) self.assertEqual(progress, snapshot_get[progress_alias]) @decorators.idempotent_id('05f711b6-e629-4895-8103-7ca069f2073a') def test_snapshot_force_delete_when_snapshot_is_creating(self): # test force delete when status of snapshot is creating self._create_reset_and_force_delete_temp_snapshot('creating') @decorators.idempotent_id('92ce8597-b992-43a1-8868-6316b22a969e') def test_snapshot_force_delete_when_snapshot_is_deleting(self): # test force delete when status of snapshot is deleting self._create_reset_and_force_delete_temp_snapshot('deleting') @decorators.idempotent_id('645a4a67-a1eb-4e8e-a547-600abac1525d') def test_snapshot_force_delete_when_snapshot_is_error(self): # test force delete when status of snapshot is error self._create_reset_and_force_delete_temp_snapshot('error') @decorators.idempotent_id('bf89080f-8129-465e-9327-b2f922666ba5') def test_snapshot_force_delete_when_snapshot_is_error_deleting(self): # test force delete when status of snapshot is error_deleting self._create_reset_and_force_delete_temp_snapshot('error_deleting') tempest-17.2.0/tempest/api/volume/admin/test_volume_types_extra_specs.py0000666000175100017510000001053313207044712026707 0ustar zuulzuul00000000000000# 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 tempest.api.volume import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest): @classmethod def resource_setup(cls): super(VolumeTypesExtraSpecsTest, cls).resource_setup() cls.volume_type = cls.create_volume_type() @decorators.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60') def test_volume_type_extra_specs_list(self): # List Volume types extra specs. extra_specs = {"spec1": "val1"} body = self.admin_volume_types_client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs)['extra_specs'] self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") body = self.admin_volume_types_client.list_volume_types_extra_specs( self.volume_type['id'])['extra_specs'] self.assertIsInstance(body, dict) self.assertIn('spec1', body) @decorators.idempotent_id('0806db36-b4a0-47a1-b6f3-c2e7f194d017') def test_volume_type_extra_specs_update(self): # Update volume type extra specs extra_specs = {"spec2": "val1"} body = self.admin_volume_types_client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs)['extra_specs'] self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") # Only update an extra spec spec_key = "spec2" extra_spec = {spec_key: "val2"} body = self.admin_volume_types_client.update_volume_type_extra_specs( self.volume_type['id'], spec_key, extra_spec) self.assertIn(spec_key, body) self.assertEqual(extra_spec[spec_key], body[spec_key]) body = self.admin_volume_types_client.show_volume_type_extra_specs( self.volume_type['id'], spec_key) self.assertIn(spec_key, body) self.assertEqual(extra_spec[spec_key], body[spec_key], "Volume type extra spec incorrectly updated") # Update an existing extra spec and create a new extra spec extra_specs = {spec_key: "val3", "spec4": "val4"} body = self.admin_volume_types_client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs)['extra_specs'] self.assertEqual(extra_specs, body) body = self.admin_volume_types_client.list_volume_types_extra_specs( self.volume_type['id'])['extra_specs'] for key in extra_specs: self.assertIn(key, body) self.assertEqual(extra_specs[key], body[key], "Volume type extra spec incorrectly created") @decorators.idempotent_id('d4772798-601f-408a-b2a5-29e8a59d1220') def test_volume_type_extra_spec_create_get_delete(self): # Create/Get/Delete volume type extra spec. spec_key = "spec3" extra_specs = {spec_key: "val1"} body = self.admin_volume_types_client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs)['extra_specs'] self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") body = self.admin_volume_types_client.show_volume_type_extra_specs( self.volume_type['id'], spec_key) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly fetched") self.admin_volume_types_client.delete_volume_type_extra_specs( self.volume_type['id'], spec_key) self.assertRaises( lib_exc.NotFound, self.admin_volume_types_client.show_volume_type_extra_specs, self.volume_type['id'], spec_key) tempest-17.2.0/tempest/api/volume/admin/test_group_types.py0000666000175100017510000000450513207044712024136 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class GroupTypesTest(base.BaseVolumeAdminTest): _api_version = 3 min_microversion = '3.11' max_microversion = 'latest' @decorators.idempotent_id('dd71e5f9-393e-4d4f-90e9-fa1b8d278864') def test_group_type_create_list_show(self): # Create/list/show group type. name = data_utils.rand_name(self.__class__.__name__ + '-group-type') description = data_utils.rand_name("group-type-description") group_specs = {"consistent_group_snapshot_enabled": " False"} params = {'name': name, 'description': description, 'group_specs': group_specs, 'is_public': True} body = self.create_group_type(**params) self.assertIn('name', body) err_msg = ("The created group_type %(var)s is not equal to the " "requested %(var)s") self.assertEqual(name, body['name'], err_msg % {"var": "name"}) self.assertEqual(description, body['description'], err_msg % {"var": "description"}) group_list = ( self.admin_group_types_client.list_group_types()['group_types']) self.assertIsInstance(group_list, list) self.assertNotEmpty(group_list) fetched_group_type = self.admin_group_types_client.show_group_type( body['id'])['group_type'] for key in params.keys(): self.assertEqual(params[key], fetched_group_type[key], '%s of the fetched group_type is different ' 'from the created group_type' % key) tempest-17.2.0/tempest/api/volume/admin/__init__.py0000666000175100017510000000000013207044712022240 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/volume/admin/test_volume_hosts.py0000666000175100017510000000574513207044712024314 0ustar zuulzuul00000000000000# 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. from tempest.api.volume import base from tempest.lib import decorators class VolumeHostsAdminTestsJSON(base.BaseVolumeAdminTest): @decorators.idempotent_id('d5f3efa2-6684-4190-9ced-1c2f526352ad') def test_list_hosts(self): hosts = self.admin_hosts_client.list_hosts()['hosts'] self.assertGreaterEqual(len(hosts), 2, "The count of volume hosts is < 2, " "response of list hosts is: %s" % hosts) # Check elements in volume hosts list host_list_keys = ['service', 'host_name', 'last-update', 'zone', 'service-status', 'service-state'] for host in hosts: for key in host_list_keys: self.assertIn(key, host) @decorators.idempotent_id('21168d57-b373-4b71-a3ac-f2c88f0c5d31') def test_show_host(self): hosts = self.admin_hosts_client.list_hosts()['hosts'] self.assertGreaterEqual(len(hosts), 2, "The count of volume hosts is < 2, " "response of list hosts is: %s" % hosts) # Note(jeremyZ): The show host API is to show volume usage info on the # specified cinder-volume host. If the host does not run cinder-volume # service, or the cinder-volume service is disabled on the host, the # show host API should fail (return code: 404). The cinder-volume host # is presented in format: @driver-name. c_vol_hosts = [host['host_name'] for host in hosts if (host['service'] == 'cinder-volume' and host['service-state'] == 'enabled')] self.assertNotEmpty(c_vol_hosts, "No available cinder-volume host is found, " "all hosts that found are: %s" % hosts) # Check each cinder-volume host. host_detail_keys = ['project', 'volume_count', 'snapshot_count', 'host', 'total_volume_gb', 'total_snapshot_gb'] for host in c_vol_hosts: host_details = self.admin_hosts_client.show_host(host)['host'] self.assertNotEmpty(host_details) for detail in host_details: self.assertIn('resource', detail) for key in host_detail_keys: self.assertIn(key, detail['resource']) tempest-17.2.0/tempest/api/volume/admin/test_qos.py0000666000175100017510000001464213207044712022363 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import waiters from tempest.lib.common.utils import data_utils as utils from tempest.lib import decorators class QosSpecsTestJSON(base.BaseVolumeAdminTest): """Test the Cinder QoS-specs. Tests for create, list, delete, show, associate, disassociate, set/unset key APIs. """ @classmethod def resource_setup(cls): super(QosSpecsTestJSON, cls).resource_setup() # Create admin qos client # Create a test shared qos-specs for tests cls.qos_name = utils.rand_name(cls.__name__ + '-QoS') cls.qos_consumer = 'front-end' cls.created_qos = cls.create_test_qos_specs(cls.qos_name, cls.qos_consumer, read_iops_sec='2000') def _create_delete_test_qos_with_given_consumer(self, consumer): name = utils.rand_name(self.__class__.__name__ + '-qos') qos = {'name': name, 'consumer': consumer} body = self.create_test_qos_specs(name, consumer) for key in ['name', 'consumer']: self.assertEqual(qos[key], body[key]) self.admin_volume_qos_client.delete_qos(body['id']) self.admin_volume_qos_client.wait_for_resource_deletion(body['id']) # validate the deletion list_qos = self.admin_volume_qos_client.list_qos()['qos_specs'] self.assertNotIn(body, list_qos) def _test_associate_qos(self, vol_type_id): self.admin_volume_qos_client.associate_qos( self.created_qos['id'], vol_type_id) @decorators.idempotent_id('7e15f883-4bef-49a9-95eb-f94209a1ced1') def test_create_delete_qos_with_front_end_consumer(self): """Tests the creation and deletion of QoS specs With consumer as front end """ self._create_delete_test_qos_with_given_consumer('front-end') @decorators.idempotent_id('b115cded-8f58-4ee4-aab5-9192cfada08f') def test_create_delete_qos_with_back_end_consumer(self): """Tests the creation and deletion of QoS specs With consumer as back-end """ self._create_delete_test_qos_with_given_consumer('back-end') @decorators.idempotent_id('f88d65eb-ea0d-487d-af8d-71f4011575a4') def test_create_delete_qos_with_both_consumer(self): """Tests the creation and deletion of QoS specs With consumer as both front end and back end """ self._create_delete_test_qos_with_given_consumer('both') @decorators.idempotent_id('7aa214cc-ac1a-4397-931f-3bb2e83bb0fd') def test_get_qos(self): """Tests the detail of a given qos-specs""" body = self.admin_volume_qos_client.show_qos( self.created_qos['id'])['qos_specs'] self.assertEqual(self.qos_name, body['name']) self.assertEqual(self.qos_consumer, body['consumer']) @decorators.idempotent_id('75e04226-bcf7-4595-a34b-fdf0736f38fc') def test_list_qos(self): """Tests the list of all qos-specs""" body = self.admin_volume_qos_client.list_qos()['qos_specs'] self.assertIn(self.created_qos, body) @decorators.idempotent_id('ed00fd85-4494-45f2-8ceb-9e2048919aed') def test_set_unset_qos_key(self): """Test the addition of a specs key to qos-specs""" args = {'iops_bytes': '500'} body = self.admin_volume_qos_client.set_qos_key( self.created_qos['id'], iops_bytes='500')['qos_specs'] self.assertEqual(args, body) body = self.admin_volume_qos_client.show_qos( self.created_qos['id'])['qos_specs'] self.assertEqual(args['iops_bytes'], body['specs']['iops_bytes']) # test the deletion of a specs key from qos-specs keys = ['iops_bytes'] self.admin_volume_qos_client.unset_qos_key(self.created_qos['id'], keys) operation = 'qos-key-unset' waiters.wait_for_qos_operations(self.admin_volume_qos_client, self.created_qos['id'], operation, keys) body = self.admin_volume_qos_client.show_qos( self.created_qos['id'])['qos_specs'] self.assertNotIn(keys[0], body['specs']) @decorators.idempotent_id('1dd93c76-6420-485d-a771-874044c416ac') def test_associate_disassociate_qos(self): """Test the following operations : 1. associate_qos 2. get_association_qos 3. disassociate_qos 4. disassociate_all_qos """ # create a test volume-type vol_type = [] for _ in range(0, 3): vol_type.append(self.create_volume_type()) # associate the qos-specs with volume-types for i in range(0, 3): self._test_associate_qos(vol_type[i]['id']) # get the association of the qos-specs body = self.admin_volume_qos_client.show_association_qos( self.created_qos['id'])['qos_associations'] associations = [association['id'] for association in body] for i in range(0, 3): self.assertIn(vol_type[i]['id'], associations) # disassociate a volume-type with qos-specs self.admin_volume_qos_client.disassociate_qos( self.created_qos['id'], vol_type[0]['id']) operation = 'disassociate' waiters.wait_for_qos_operations(self.admin_volume_qos_client, self.created_qos['id'], operation, vol_type[0]['id']) # disassociate all volume-types from qos-specs self.admin_volume_qos_client.disassociate_all_qos( self.created_qos['id']) operation = 'disassociate-all' waiters.wait_for_qos_operations(self.admin_volume_qos_client, self.created_qos['id'], operation) tempest-17.2.0/tempest/api/volume/admin/test_user_messages.py0000666000175100017510000000647413207044712024432 0ustar zuulzuul00000000000000# Copyright 2016 Andrew Kerr # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF MESSAGE_KEYS = [ 'created_at', 'event_id', 'guaranteed_until', 'id', 'message_level', 'request_id', 'resource_type', 'resource_uuid', 'user_message', 'links'] class UserMessagesTest(base.BaseVolumeAdminTest): _api_version = 3 min_microversion = '3.3' max_microversion = 'latest' def _create_user_message(self): """Trigger a 'no valid host' situation to generate a message.""" bad_protocol = data_utils.rand_name('storage_protocol') bad_vendor = data_utils.rand_name('vendor_name') extra_specs = {'storage_protocol': bad_protocol, 'vendor_name': bad_vendor} vol_type_name = data_utils.rand_name( self.__class__.__name__ + '-volume-type') bogus_type = self.create_volume_type( name=vol_type_name, extra_specs=extra_specs) params = {'volume_type': bogus_type['id'], 'size': CONF.volume.volume_size} volume = self.create_volume(wait_until="error", **params) messages = self.messages_client.list_messages()['messages'] message_id = None for message in messages: if message['resource_uuid'] == volume['id']: message_id = message['id'] break self.assertIsNotNone(message_id, 'No user message generated for ' 'volume %s' % volume['id']) return message_id @decorators.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a') def test_list_messages(self): self._create_user_message() messages = self.messages_client.list_messages()['messages'] self.assertIsInstance(messages, list) for message in messages: for key in MESSAGE_KEYS: self.assertIn(key, message.keys(), 'Missing expected key %s' % key) @decorators.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070') def test_show_message(self): message_id = self._create_user_message() self.addCleanup(self.messages_client.delete_message, message_id) message = self.messages_client.show_message(message_id)['message'] for key in MESSAGE_KEYS: self.assertIn(key, message.keys(), 'Missing expected key %s' % key) @decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed') def test_delete_message(self): message_id = self._create_user_message() self.messages_client.delete_message(message_id) self.messages_client.wait_for_resource_deletion(message_id) tempest-17.2.0/tempest/api/volume/admin/test_backends_capabilities.py0000666000175100017510000000564213207044712026044 0ustar zuulzuul00000000000000# Copyright 2016 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 operator from tempest.api.volume import base from tempest.lib import decorators class BackendsCapabilitiesAdminTestsJSON(base.BaseVolumeAdminTest): CAPABILITIES = ('namespace', 'vendor_name', 'volume_backend_name', 'pool_name', 'driver_version', 'storage_protocol', 'display_name', 'description', 'visibility', 'properties') @classmethod def resource_setup(cls): super(BackendsCapabilitiesAdminTestsJSON, cls).resource_setup() # Get host list, formation: host@backend-name cls.hosts = [ pool['name'] for pool in cls.admin_scheduler_stats_client.list_pools()['pools'] ] @decorators.idempotent_id('3750af44-5ea2-4cd4-bc3e-56e7e6caf854') def test_get_capabilities_backend(self): # Test backend properties backend = self.admin_capabilities_client.show_backend_capabilities( self.hosts[0]) # Verify getting capabilities parameters from a backend for key in self.CAPABILITIES: self.assertIn(key, backend) @decorators.idempotent_id('a9035743-d46a-47c5-9cb7-3c80ea16dea0') def test_compare_volume_stats_values(self): # Test values comparison between show_backend_capabilities # to show_pools VOLUME_STATS = ('vendor_name', 'volume_backend_name', 'storage_protocol') # Get list backend capabilities using show_pools cinder_pools = [ pool['capabilities'] for pool in self.admin_scheduler_stats_client.list_pools(detail=True)['pools'] ] # Get list backends capabilities using show_backend_capabilities capabilities = [ self.admin_capabilities_client.show_backend_capabilities( host=host) for host in self.hosts ] # Returns a tuple of VOLUME_STATS values expected_list = list(map(operator.itemgetter(*VOLUME_STATS), cinder_pools)) observed_list = list(map(operator.itemgetter(*VOLUME_STATS), capabilities)) self.assertEqual(expected_list, observed_list) tempest-17.2.0/tempest/api/volume/admin/test_volume_type_access.py0000666000175100017510000000761713207044712025456 0ustar zuulzuul00000000000000# Copyright 2016 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 operator from tempest.api.volume import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumeTypesAccessTest(base.BaseVolumeAdminTest): credentials = ['primary', 'alt', 'admin'] @classmethod def setup_clients(cls): super(VolumeTypesAccessTest, cls).setup_clients() cls.alt_client = cls.os_alt.volumes_client_latest @decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184') def test_volume_type_access_add(self): # Creating a NON public volume type params = {'os-volume-type-access:is_public': False} volume_type = self.create_volume_type(**params) # Try creating a volume from volume type in primary tenant self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume, volume_type=volume_type['id'], size=CONF.volume.volume_size) # Adding volume type access for primary tenant self.admin_volume_types_client.add_type_access( volume_type['id'], project=self.volumes_client.tenant_id) self.addCleanup(self.admin_volume_types_client.remove_type_access, volume_type['id'], project=self.volumes_client.tenant_id) # Creating a volume from primary tenant volume = self.create_volume(volume_type=volume_type['id']) # Validating the created volume is based on the volume type self.assertEqual(volume_type['name'], volume['volume_type']) @decorators.idempotent_id('5220eb28-a435-43ce-baaf-ed46f0e95159') def test_volume_type_access_list(self): # Creating a NON public volume type params = {'os-volume-type-access:is_public': False} volume_type = self.create_volume_type(**params) # Adding volume type access for primary tenant self.admin_volume_types_client.add_type_access( volume_type['id'], project=self.volumes_client.tenant_id) self.addCleanup(self.admin_volume_types_client.remove_type_access, volume_type['id'], project=self.volumes_client.tenant_id) # Adding volume type access for alt tenant self.admin_volume_types_client.add_type_access( volume_type['id'], project=self.alt_client.tenant_id) self.addCleanup(self.admin_volume_types_client.remove_type_access, volume_type['id'], project=self.alt_client.tenant_id) # List tenant access for the given volume type type_access_list = self.admin_volume_types_client.list_type_access( volume_type['id'])['volume_type_access'] volume_type_ids = [ vol_type['volume_type_id'] for vol_type in type_access_list ] # Validating volume type available for only two tenants self.assertEqual(2, volume_type_ids.count(volume_type['id'])) # Validating the permitted tenants are the expected tenants self.assertIn(self.volumes_client.tenant_id, map(operator.itemgetter('project_id'), type_access_list)) self.assertIn(self.alt_client.tenant_id, map(operator.itemgetter('project_id'), type_access_list)) tempest-17.2.0/tempest/api/volume/admin/test_volume_quotas_negative.py0000666000175100017510000000563013207044712026343 0ustar zuulzuul00000000000000# Copyright 2014 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 tempest.api.volume import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class BaseVolumeQuotasNegativeTestJSON(base.BaseVolumeAdminTest): force_tenant_isolation = True @classmethod def setup_credentials(cls): super(BaseVolumeQuotasNegativeTestJSON, cls).setup_credentials() cls.demo_tenant_id = cls.os_primary.credentials.tenant_id @classmethod def resource_setup(cls): super(BaseVolumeQuotasNegativeTestJSON, cls).resource_setup() cls.shared_quota_set = {'gigabytes': 2 * CONF.volume.volume_size, 'volumes': 1} # NOTE(gfidente): no need to restore original quota set # after the tests as they only work with dynamic credentials. cls.admin_quotas_client.update_quota_set( cls.demo_tenant_id, **cls.shared_quota_set) # NOTE(gfidente): no need to delete in tearDown as # they are created using utility wrapper methods. cls.volume = cls.create_volume() @decorators.attr(type='negative') @decorators.idempotent_id('bf544854-d62a-47f2-a681-90f7a47d86b6') def test_quota_volumes(self): self.assertRaises(lib_exc.OverLimit, self.volumes_client.create_volume, size=CONF.volume.volume_size) @decorators.attr(type='negative') @decorators.idempotent_id('2dc27eee-8659-4298-b900-169d71a91374') def test_quota_volume_gigabytes(self): # NOTE(gfidente): quota set needs to be changed for this test # or we may be limited by the volumes or snaps quota number, not by # actual gigs usage; next line ensures shared set is restored. self.addCleanup(self.admin_quotas_client.update_quota_set, self.demo_tenant_id, **self.shared_quota_set) new_quota_set = {'gigabytes': CONF.volume.volume_size, 'volumes': 2, 'snapshots': 1} self.admin_quotas_client.update_quota_set( self.demo_tenant_id, **new_quota_set) self.assertRaises(lib_exc.OverLimit, self.volumes_client.create_volume, size=CONF.volume.volume_size) tempest-17.2.0/tempest/api/volume/admin/test_groups.py0000666000175100017510000003777213207044712023111 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class BaseGroupsTest(base.BaseVolumeAdminTest): def _delete_group(self, grp_id, delete_volumes=True): self.groups_client.delete_group(grp_id, delete_volumes) vols = self.volumes_client.list_volumes(detail=True)['volumes'] for vol in vols: if vol['group_id'] == grp_id: self.volumes_client.wait_for_resource_deletion(vol['id']) self.groups_client.wait_for_resource_deletion(grp_id) def _delete_group_snapshot(self, group_snapshot_id, grp_id): self.group_snapshots_client.delete_group_snapshot(group_snapshot_id) vols = self.volumes_client.list_volumes(detail=True)['volumes'] snapshots = self.snapshots_client.list_snapshots( detail=True)['snapshots'] for vol in vols: for snap in snapshots: if (vol['group_id'] == grp_id and vol['id'] == snap['volume_id']): self.snapshots_client.wait_for_resource_deletion( snap['id']) self.group_snapshots_client.wait_for_resource_deletion( group_snapshot_id) def _create_group(self, group_type, volume_type, grp_name=None): if not grp_name: grp_name = data_utils.rand_name('Group') grp = self.groups_client.create_group( group_type=group_type['id'], volume_types=[volume_type['id']], name=grp_name)['group'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self._delete_group, grp['id']) waiters.wait_for_volume_resource_status( self.groups_client, grp['id'], 'available') self.assertEqual(grp_name, grp['name']) return grp class GroupsTest(BaseGroupsTest): _api_version = 3 min_microversion = '3.14' max_microversion = 'latest' @decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4') def test_group_create_show_list_delete(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create group grp1_name = data_utils.rand_name('Group1') grp1 = self._create_group(group_type, volume_type, grp_name=grp1_name) grp1_id = grp1['id'] grp2_name = data_utils.rand_name('Group2') grp2 = self._create_group(group_type, volume_type, grp_name=grp2_name) grp2_id = grp2['id'] # Create volume vol1_name = data_utils.rand_name("volume") params = {'name': vol1_name, 'volume_type': volume_type['id'], 'group_id': grp1['id'], 'size': CONF.volume.volume_size} vol1 = self.volumes_client.create_volume(**params)['volume'] self.assertEqual(grp1['id'], vol1['group_id']) waiters.wait_for_volume_resource_status( self.volumes_client, vol1['id'], 'available') vol1_id = vol1['id'] # Get a given group grp1 = self.groups_client.show_group(grp1['id'])['group'] self.assertEqual(grp1_name, grp1['name']) self.assertEqual(grp1_id, grp1['id']) grp2 = self.groups_client.show_group(grp2['id'])['group'] self.assertEqual(grp2_name, grp2['name']) self.assertEqual(grp2_id, grp2['id']) # Get all groups with detail grps = self.groups_client.list_groups(detail=True)['groups'] for grp_id in [grp1_id, grp2_id]: filtered_grps = [g for g in grps if g['id'] == grp_id] self.assertEqual(1, len(filtered_grps)) self.assertEqual([volume_type['id']], filtered_grps[0]['volume_types']) self.assertEqual(group_type['id'], filtered_grps[0]['group_type']) vols = self.volumes_client.list_volumes(detail=True)['volumes'] filtered_vols = [v for v in vols if v['id'] in [vol1_id]] self.assertEqual(1, len(filtered_vols)) for vol in filtered_vols: self.assertEqual(grp1_id, vol['group_id']) # Delete group # grp1 has a volume so delete_volumes flag is set to True by default self._delete_group(grp1_id) # grp2 is empty so delete_volumes flag can be set to False self._delete_group(grp2_id, delete_volumes=False) grps = self.groups_client.list_groups(detail=True)['groups'] self.assertEmpty(grps) @decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897') def test_group_snapshot_create_show_list_delete(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create group grp = self._create_group(group_type, volume_type) # Create volume vol = self.create_volume(volume_type=volume_type['id'], group_id=grp['id']) # Create group snapshot group_snapshot_name = data_utils.rand_name('group_snapshot') group_snapshot = ( self.group_snapshots_client.create_group_snapshot( group_id=grp['id'], name=group_snapshot_name)['group_snapshot']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self._delete_group_snapshot, group_snapshot['id'], grp['id']) snapshots = self.snapshots_client.list_snapshots( detail=True)['snapshots'] for snap in snapshots: if vol['id'] == snap['volume_id']: waiters.wait_for_volume_resource_status( self.snapshots_client, snap['id'], 'available') waiters.wait_for_volume_resource_status( self.group_snapshots_client, group_snapshot['id'], 'available') self.assertEqual(group_snapshot_name, group_snapshot['name']) # Get a given group snapshot group_snapshot = self.group_snapshots_client.show_group_snapshot( group_snapshot['id'])['group_snapshot'] self.assertEqual(group_snapshot_name, group_snapshot['name']) # Get all group snapshots with details, check some detail-specific # elements, and look for the created group snapshot group_snapshots = (self.group_snapshots_client.list_group_snapshots( detail=True)['group_snapshots']) for grp_snapshot in group_snapshots: self.assertIn('created_at', grp_snapshot) self.assertIn('group_id', grp_snapshot) self.assertIn((group_snapshot['name'], group_snapshot['id']), [(m['name'], m['id']) for m in group_snapshots]) # Delete group snapshot self._delete_group_snapshot(group_snapshot['id'], grp['id']) group_snapshots = (self.group_snapshots_client.list_group_snapshots() ['group_snapshots']) self.assertEmpty(group_snapshots) @decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81') def test_create_group_from_group_snapshot(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create Group grp = self._create_group(group_type, volume_type) # Create volume vol = self.create_volume(volume_type=volume_type['id'], group_id=grp['id']) # Create group_snapshot group_snapshot_name = data_utils.rand_name('group_snapshot') group_snapshot = ( self.group_snapshots_client.create_group_snapshot( group_id=grp['id'], name=group_snapshot_name)['group_snapshot']) self.addCleanup(self._delete_group_snapshot, group_snapshot['id'], grp['id']) self.assertEqual(group_snapshot_name, group_snapshot['name']) snapshots = self.snapshots_client.list_snapshots( detail=True)['snapshots'] for snap in snapshots: if vol['id'] == snap['volume_id']: waiters.wait_for_volume_resource_status( self.snapshots_client, snap['id'], 'available') waiters.wait_for_volume_resource_status( self.group_snapshots_client, group_snapshot['id'], 'available') # Create Group from Group snapshot grp_name2 = data_utils.rand_name('Group_from_snap') grp2 = self.groups_client.create_group_from_source( group_snapshot_id=group_snapshot['id'], name=grp_name2)['group'] self.addCleanup(self._delete_group, grp2['id']) self.assertEqual(grp_name2, grp2['name']) vols = self.volumes_client.list_volumes(detail=True)['volumes'] for vol in vols: if vol['group_id'] == grp2['id']: waiters.wait_for_volume_resource_status( self.volumes_client, vol['id'], 'available') waiters.wait_for_volume_resource_status( self.groups_client, grp2['id'], 'available') @decorators.idempotent_id('2424af8c-7851-4888-986a-794b10c3210e') def test_create_group_from_group(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create Group grp = self._create_group(group_type, volume_type) # Create volume self.create_volume(volume_type=volume_type['id'], group_id=grp['id']) # Create Group from Group grp_name2 = data_utils.rand_name('Group_from_grp') grp2 = self.groups_client.create_group_from_source( source_group_id=grp['id'], name=grp_name2)['group'] self.addCleanup(self._delete_group, grp2['id']) self.assertEqual(grp_name2, grp2['name']) vols = self.volumes_client.list_volumes(detail=True)['volumes'] for vol in vols: if vol['group_id'] == grp2['id']: waiters.wait_for_volume_resource_status( self.volumes_client, vol['id'], 'available') waiters.wait_for_volume_resource_status( self.groups_client, grp2['id'], 'available') @decorators.idempotent_id('4a8a6fd2-8b3b-4641-8f54-6a6f99320006') def test_group_update(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create Group grp = self._create_group(group_type, volume_type) # Create volumes grp_vols = [] for _ in range(2): vol = self.create_volume(volume_type=volume_type['id'], group_id=grp['id']) grp_vols.append(vol) vol2 = grp_vols[1] # Remove a volume from group and update name and description new_grp_name = 'new_group' new_desc = 'This is a new group' grp_params = {'name': new_grp_name, 'description': new_desc, 'remove_volumes': vol2['id']} self.groups_client.update_group(grp['id'], **grp_params) # Wait for group status to become available waiters.wait_for_volume_resource_status( self.groups_client, grp['id'], 'available') # Get the updated Group grp = self.groups_client.show_group(grp['id'])['group'] self.assertEqual(new_grp_name, grp['name']) self.assertEqual(new_desc, grp['description']) # Get volumes in the group vols = self.volumes_client.list_volumes(detail=True)['volumes'] grp_vols = [v for v in vols if v['group_id'] == grp['id']] self.assertEqual(1, len(grp_vols)) # Add a volume to the group grp_params = {'add_volumes': vol2['id']} self.groups_client.update_group(grp['id'], **grp_params) # Wait for group status to become available waiters.wait_for_volume_resource_status( self.groups_client, grp['id'], 'available') # Get volumes in the group vols = self.volumes_client.list_volumes(detail=True)['volumes'] grp_vols = [v for v in vols if v['group_id'] == grp['id']] self.assertEqual(2, len(grp_vols)) class GroupsV319Test(BaseGroupsTest): _api_version = 3 min_microversion = '3.19' max_microversion = 'latest' @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40') def test_reset_group_snapshot_status(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create group group = self._create_group(group_type, volume_type) # Create volume volume = self.create_volume(volume_type=volume_type['id'], group_id=group['id']) # Create group snapshot group_snapshot_name = data_utils.rand_name('group_snapshot') group_snapshot = (self.group_snapshots_client.create_group_snapshot( group_id=group['id'], name=group_snapshot_name)['group_snapshot']) self.addCleanup(self._delete_group_snapshot, group_snapshot['id'], group['id']) snapshots = self.snapshots_client.list_snapshots( detail=True)['snapshots'] for snap in snapshots: if volume['id'] == snap['volume_id']: waiters.wait_for_volume_resource_status( self.snapshots_client, snap['id'], 'available') waiters.wait_for_volume_resource_status( self.group_snapshots_client, group_snapshot['id'], 'available') # Reset group snapshot status self.addCleanup(waiters.wait_for_volume_resource_status, self.group_snapshots_client, group_snapshot['id'], 'available') self.addCleanup( self.admin_group_snapshots_client.reset_group_snapshot_status, group_snapshot['id'], 'available') for status in ['creating', 'available', 'error']: self.admin_group_snapshots_client.reset_group_snapshot_status( group_snapshot['id'], status) waiters.wait_for_volume_resource_status( self.group_snapshots_client, group_snapshot['id'], status) class GroupsV320Test(BaseGroupsTest): _api_version = 3 min_microversion = '3.20' max_microversion = 'latest' @decorators.idempotent_id('b20c696b-0cbc-49a5-8b3a-b1fb9338f45c') def test_reset_group_status(self): # Create volume type volume_type = self.create_volume_type() # Create group type group_type = self.create_group_type() # Create group group = self._create_group(group_type, volume_type) # Reset group status self.addCleanup(waiters.wait_for_volume_resource_status, self.groups_client, group['id'], 'available') self.addCleanup(self.admin_groups_client.reset_group_status, group['id'], 'available') for status in ['creating', 'available', 'error']: self.admin_groups_client.reset_group_status(group['id'], status) waiters.wait_for_volume_resource_status( self.groups_client, group['id'], status) tempest-17.2.0/tempest/api/volume/admin/test_volume_types.py0000666000175100017510000002166413207044712024316 0ustar zuulzuul00000000000000# 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 tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumeTypesTest(base.BaseVolumeAdminTest): @decorators.idempotent_id('9d9b28e3-1b2e-4483-a2cc-24aa0ea1de54') def test_volume_type_list(self): # List volume types. body = \ self.admin_volume_types_client.list_volume_types()['volume_types'] self.assertIsInstance(body, list) @decorators.idempotent_id('c03cc62c-f4e9-4623-91ec-64ce2f9c1260') def test_volume_crud_with_volume_type_and_extra_specs(self): # Create/update/get/delete volume with volume_type and extra spec. volume_types = list() vol_name = data_utils.rand_name(self.__class__.__name__ + '-volume') proto = CONF.volume.storage_protocol vendor = CONF.volume.vendor_name extra_specs = {"storage_protocol": proto, "vendor_name": vendor} # Create two volume_types for _ in range(2): vol_type = self.create_volume_type( extra_specs=extra_specs) volume_types.append(vol_type) params = {'name': vol_name, 'volume_type': volume_types[0]['id'], 'size': CONF.volume.volume_size} # Create volume volume = self.create_volume(**params) self.assertEqual(volume_types[0]['name'], volume["volume_type"]) self.assertEqual(volume['name'], vol_name, "The created volume name is not equal " "to the requested name") self.assertIsNotNone(volume['id'], "Field volume id is empty or not found.") # Update volume with new volume_type self.volumes_client.retype_volume(volume['id'], new_type=volume_types[1]['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # Get volume details and Verify fetched_volume = self.volumes_client.show_volume( volume['id'])['volume'] self.assertEqual(volume_types[1]['name'], fetched_volume['volume_type'], 'The fetched Volume type is different ' 'from updated volume type') self.assertEqual(vol_name, fetched_volume['name'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') @decorators.idempotent_id('4e955c3b-49db-4515-9590-0c99f8e471ad') def test_volume_type_create_get_delete(self): # Create/get volume type. name = data_utils.rand_name(self.__class__.__name__ + '-volume-type') description = data_utils.rand_name("volume-type-description") proto = CONF.volume.storage_protocol vendor = CONF.volume.vendor_name extra_specs = {"storage_protocol": proto, "vendor_name": vendor} params = {'name': name, 'description': description, 'extra_specs': extra_specs, 'os-volume-type-access:is_public': True} body = self.create_volume_type(**params) self.assertIn('name', body) self.assertEqual(name, body['name'], "The created volume_type name is not equal " "to the requested name") self.assertEqual(description, body['description'], "The created volume_type_description name is " "not equal to the requested name") self.assertIsNotNone(body['id'], "Field volume_type id is empty or not found.") fetched_volume_type = self.admin_volume_types_client.show_volume_type( body['id'])['volume_type'] self.assertEqual(name, fetched_volume_type['name'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(str(body['id']), fetched_volume_type['id'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(extra_specs, fetched_volume_type['extra_specs'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(description, fetched_volume_type['description']) self.assertEqual(body['is_public'], fetched_volume_type['is_public']) self.assertEqual( body['os-volume-type-access:is_public'], fetched_volume_type['os-volume-type-access:is_public']) @decorators.idempotent_id('7830abd0-ff99-4793-a265-405684a54d46') def test_volume_type_encryption_create_get_update_delete(self): # Create/get/update/delete encryption type. create_kwargs = {'provider': 'LuksEncryptor', 'control_location': 'front-end'} volume_type_id = self.create_volume_type()['id'] # Create encryption type encryption_type = \ self.admin_encryption_types_client.create_encryption_type( volume_type_id, **create_kwargs)['encryption'] self.assertIn('volume_type_id', encryption_type) for key in create_kwargs: self.assertEqual(create_kwargs[key], encryption_type[key], 'The created encryption_type %s is different ' 'from the requested encryption_type' % key) # Get encryption type encrypt_type_id = encryption_type['volume_type_id'] fetched_encryption_type = ( self.admin_encryption_types_client.show_encryption_type( encrypt_type_id)) for key in create_kwargs: self.assertEqual(create_kwargs[key], fetched_encryption_type[key], 'The fetched encryption_type %s is different ' 'from the created encryption_type' % key) # Update encryption type update_kwargs = {'key_size': 128, 'provider': 'SomeProvider', 'cipher': 'aes-xts-plain64', 'control_location': 'back-end'} self.admin_encryption_types_client.update_encryption_type( encrypt_type_id, **update_kwargs) updated_encryption_type = ( self.admin_encryption_types_client.show_encryption_type( encrypt_type_id)) for key in update_kwargs: self.assertEqual(update_kwargs[key], updated_encryption_type[key], 'The fetched encryption_type %s is different ' 'from the updated encryption_type' % key) # Delete encryption type self.admin_encryption_types_client.delete_encryption_type( encrypt_type_id) self.admin_encryption_types_client.wait_for_resource_deletion( encrypt_type_id) deleted_encryption_type = ( self.admin_encryption_types_client.show_encryption_type( encrypt_type_id)) self.assertEmpty(deleted_encryption_type) @decorators.idempotent_id('cf9f07c6-db9e-4462-a243-5933ad65e9c8') def test_volume_type_update(self): # Create volume type volume_type = self.create_volume_type() # New volume type details name = data_utils.rand_name("volume-type") description = data_utils.rand_name("volume-type-description") is_public = not volume_type['is_public'] # Update volume type details kwargs = {'name': name, 'description': description, 'is_public': is_public} updated_vol_type = self.admin_volume_types_client.update_volume_type( volume_type['id'], **kwargs)['volume_type'] # Verify volume type details were updated self.assertEqual(name, updated_vol_type['name']) self.assertEqual(description, updated_vol_type['description']) self.assertEqual(is_public, updated_vol_type['is_public']) tempest-17.2.0/tempest/api/volume/admin/test_volumes_backup.py0000666000175100017510000001400413207044712024570 0ustar zuulzuul00000000000000# Copyright 2014 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 base64 from oslo_serialization import jsonutils as json from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumesBackupsAdminTest(base.BaseVolumeAdminTest): @classmethod def skip_checks(cls): super(VolumesBackupsAdminTest, cls).skip_checks() if not CONF.volume_feature_enabled.backup: raise cls.skipException("Cinder backup feature disabled") def _delete_backup(self, backup_id): self.admin_backups_client.delete_backup(backup_id) self.admin_backups_client.wait_for_resource_deletion(backup_id) def _decode_url(self, backup_url): return json.loads(base64.decode_as_text(backup_url)) def _encode_backup(self, backup): retval = json.dumps(backup) return base64.encode_as_text(retval) def _modify_backup_url(self, backup_url, changes): backup = self._decode_url(backup_url) backup.update(changes) return self._encode_backup(backup) @decorators.idempotent_id('a99c54a1-dd80-4724-8a13-13bf58d4068d') def test_volume_backup_export_import(self): """Test backup export import functionality. Cinder allows exporting DB backup information through its API so it can be imported back in case of a DB loss. """ volume = self.create_volume() # Create backup backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup') backup = (self.create_backup(backup_client=self.admin_backups_client, volume_id=volume['id'], name=backup_name)) self.assertEqual(backup_name, backup['name']) # Export Backup export_backup = (self.admin_backups_client.export_backup(backup['id']) ['backup-record']) self.assertIn('backup_service', export_backup) self.assertIn('backup_url', export_backup) self.assertTrue(export_backup['backup_service'].startswith( 'cinder.backup.drivers')) self.assertIsNotNone(export_backup['backup_url']) # NOTE(geguileo): Backups are imported with the same backup id # (important for incremental backups among other things), so we cannot # import the exported backup information as it is, because that Backup # ID already exists. So we'll fake the data by changing the backup id # in the exported backup DB info we have retrieved before importing it # back. new_id = data_utils.rand_uuid() new_url = self._modify_backup_url( export_backup['backup_url'], {'id': new_id}) # Import Backup import_backup = self.admin_backups_client.import_backup( backup_service=export_backup['backup_service'], backup_url=new_url)['backup'] # NOTE(geguileo): We delete both backups, but only one of those # deletions will delete data from the backup back-end because they # were both pointing to the same backend data. self.addCleanup(self._delete_backup, new_id) self.assertIn("id", import_backup) self.assertEqual(new_id, import_backup['id']) waiters.wait_for_volume_resource_status(self.admin_backups_client, import_backup['id'], 'available') # Verify Import Backup backups = self.admin_backups_client.list_backups()['backups'] self.assertIn(new_id, [b['id'] for b in backups]) # Restore backup restore = self.admin_backups_client.restore_backup( backup['id'])['restore'] self.addCleanup(self.admin_volume_client.delete_volume, restore['volume_id']) self.assertEqual(backup['id'], restore['backup_id']) waiters.wait_for_volume_resource_status(self.admin_volume_client, restore['volume_id'], 'available') # Verify if restored volume is there in volume list volumes = self.admin_volume_client.list_volumes()['volumes'] self.assertIn(restore['volume_id'], [v['id'] for v in volumes]) waiters.wait_for_volume_resource_status(self.admin_backups_client, import_backup['id'], 'available') @decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94') def test_volume_backup_reset_status(self): # Create a volume volume = self.create_volume() # Create a backup backup_name = data_utils.rand_name( self.__class__.__name__ + '-Backup') backup = self.create_backup(backup_client=self.admin_backups_client, volume_id=volume['id'], name=backup_name) self.assertEqual(backup_name, backup['name']) # Reset backup status to error self.admin_backups_client.reset_backup_status(backup_id=backup['id'], status="error") waiters.wait_for_volume_resource_status(self.admin_backups_client, backup['id'], 'error') tempest-17.2.0/tempest/api/volume/admin/test_snapshot_manage.py0000666000175100017510000001021413207044712024717 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class SnapshotManageAdminTest(base.BaseVolumeAdminTest): """Unmanage & manage snapshots This feature provides the ability to import/export volume snapshot from one Cinder to another and to import snapshots that have not been managed by Cinder from a storage back end to Cinder """ @classmethod def skip_checks(cls): super(SnapshotManageAdminTest, cls).skip_checks() if not CONF.volume_feature_enabled.manage_snapshot: raise cls.skipException("Manage snapshot tests are disabled") if len(CONF.volume.manage_snapshot_ref) != 2: msg = ("Manage snapshot ref is not correctly configured, " "it should be a list of two elements") raise exceptions.InvalidConfiguration(msg) @decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810') def test_unmanage_manage_snapshot(self): # Create a volume volume = self.create_volume() # Create a snapshot snapshot = self.create_snapshot(volume_id=volume['id']) # Unmanage the snapshot # Unmanage snapshot function works almost the same as delete snapshot, # but it does not delete the snapshot data self.admin_snapshots_client.unmanage_snapshot(snapshot['id']) self.admin_snapshots_client.wait_for_resource_deletion(snapshot['id']) # Verify the original snapshot does not exist in snapshot list params = {'all_tenants': 1} all_snapshots = self.admin_snapshots_client.list_snapshots( detail=True, params=params)['snapshots'] self.assertNotIn(snapshot['id'], [v['id'] for v in all_snapshots]) # Manage the snapshot name = data_utils.rand_name(self.__class__.__name__ + '-Managed-Snapshot') description = data_utils.rand_name(self.__class__.__name__ + '-Managed-Snapshot-Description') metadata = {"manage-snap-meta1": "value1", "manage-snap-meta2": "value2", "manage-snap-meta3": "value3"} snapshot_ref = { 'volume_id': volume['id'], 'ref': {CONF.volume.manage_snapshot_ref[0]: CONF.volume.manage_snapshot_ref[1] % snapshot['id']}, 'name': name, 'description': description, 'metadata': metadata } new_snapshot = self.admin_snapshot_manage_client.manage_snapshot( **snapshot_ref)['snapshot'] self.addCleanup(self.delete_snapshot, new_snapshot['id'], self.admin_snapshots_client) # Wait for the snapshot to be available after manage operation waiters.wait_for_volume_resource_status(self.admin_snapshots_client, new_snapshot['id'], 'available') # Verify the managed snapshot has the expected parent volume # and the expected field values. new_snapshot_info = self.admin_snapshots_client.show_snapshot( new_snapshot['id'])['snapshot'] self.assertEqual(snapshot['size'], new_snapshot_info['size']) for key in ['volume_id', 'name', 'description', 'metadata']: self.assertEqual(snapshot_ref[key], new_snapshot_info[key]) tempest-17.2.0/tempest/api/volume/admin/test_volumes_list.py0000666000175100017510000000563713207044712024312 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import operator from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumesListAdminTestJSON(base.BaseVolumeAdminTest): @classmethod def resource_setup(cls): super(VolumesListAdminTestJSON, cls).resource_setup() # Create 3 test volumes # NOTE(zhufl): When using pre-provisioned credentials, the project # may have volumes other than those created below. cls.volume_list = cls.volumes_client.list_volumes()['volumes'] for _ in range(3): volume = cls.create_volume() # Fetch volume details volume_details = cls.volumes_client.show_volume( volume['id'])['volume'] cls.volume_list.append(volume_details) @decorators.idempotent_id('5866286f-3290-4cfd-a414-088aa6cdc469') def test_volume_list_param_tenant(self): # Test to list volumes from single tenant # Create a volume in admin tenant adm_vol = self.admin_volume_client.create_volume( size=CONF.volume.volume_size)['volume'] self.addCleanup(self.admin_volume_client.delete_volume, adm_vol['id']) waiters.wait_for_volume_resource_status(self.admin_volume_client, adm_vol['id'], 'available') params = {'all_tenants': 1, 'project_id': self.volumes_client.tenant_id} # Getting volume list from primary tenant using admin credentials fetched_list = self.admin_volume_client.list_volumes( detail=True, params=params)['volumes'] # Verifying fetched volume ids list is related to primary tenant fetched_list_ids = map(operator.itemgetter('id'), fetched_list) expected_list_ids = map(operator.itemgetter('id'), self.volume_list) self.assertEqual(sorted(expected_list_ids), sorted(fetched_list_ids)) # Verifying tenant id of volumes fetched list is related to # primary tenant fetched_tenant_id = [operator.itemgetter( 'os-vol-tenant-attr:tenant_id')(item) for item in fetched_list] expected_tenant_id = [self.volumes_client.tenant_id] * \ len(self.volume_list) self.assertEqual(expected_tenant_id, fetched_tenant_id) tempest-17.2.0/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py0000666000175100017510000001344013207044712030571 0ustar zuulzuul00000000000000# 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 tempest.api.volume import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest): @classmethod def resource_setup(cls): super(ExtraSpecsNegativeTest, cls).resource_setup() extra_specs = {"spec1": "val1"} cls.volume_type = cls.create_volume_type(extra_specs=extra_specs) @decorators.attr(type=['negative']) @decorators.idempotent_id('08961d20-5cbb-4910-ac0f-89ad6dbb2da1') def test_update_no_body(self): # Should not update volume type extra specs with no body self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.update_volume_type_extra_specs, self.volume_type['id'], "spec1", None) @decorators.attr(type=['negative']) @decorators.idempotent_id('25e5a0ee-89b3-4c53-8310-236f76c75365') def test_update_nonexistent_extra_spec_id(self): # Should not update volume type extra specs with nonexistent id. extra_spec = {"spec1": "val2"} self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.update_volume_type_extra_specs, self.volume_type['id'], data_utils.rand_uuid(), extra_spec) @decorators.attr(type=['negative']) @decorators.idempotent_id('9bf7a657-b011-4aec-866d-81c496fbe5c8') def test_update_none_extra_spec_id(self): # Should not update volume type extra specs with none id. extra_spec = {"spec1": "val2"} self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.update_volume_type_extra_specs, self.volume_type['id'], None, extra_spec) @decorators.attr(type=['negative']) @decorators.idempotent_id('a77dfda2-9100-448e-9076-ed1711f4bdfc') def test_update_multiple_extra_spec(self): # Should not update volume type extra specs with multiple specs as # body. extra_spec = {"spec1": "val2", "spec2": "val1"} self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.update_volume_type_extra_specs, self.volume_type['id'], list(extra_spec)[0], extra_spec) @decorators.attr(type=['negative']) @decorators.idempotent_id('49d5472c-a53d-4eab-a4d3-450c4db1c545') def test_create_nonexistent_type_id(self): # Should not create volume type extra spec for nonexistent volume # type id. extra_specs = {"spec2": "val1"} self.assertRaises( lib_exc.NotFound, self.admin_volume_types_client.create_volume_type_extra_specs, data_utils.rand_uuid(), extra_specs) @decorators.attr(type=['negative']) @decorators.idempotent_id('c821bdc8-43a4-4bf4-86c8-82f3858d5f7d') def test_create_none_body(self): # Should not create volume type extra spec for none POST body. self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.create_volume_type_extra_specs, self.volume_type['id'], None) @decorators.attr(type=['negative']) @decorators.idempotent_id('bc772c71-1ed4-4716-b945-8b5ed0f15e87') def test_create_invalid_body(self): # Should not create volume type extra spec for invalid POST body. self.assertRaises( lib_exc.BadRequest, self.admin_volume_types_client.create_volume_type_extra_specs, self.volume_type['id'], extra_specs=['invalid']) @decorators.attr(type=['negative']) @decorators.idempotent_id('031cda8b-7d23-4246-8bf6-bbe73fd67074') def test_delete_nonexistent_volume_type_id(self): # Should not delete volume type extra spec for nonexistent # type id. self.assertRaises( lib_exc.NotFound, self.admin_volume_types_client.delete_volume_type_extra_specs, data_utils.rand_uuid(), "spec1") @decorators.attr(type=['negative']) @decorators.idempotent_id('dee5cf0c-cdd6-4353-b70c-e847050d71fb') def test_list_nonexistent_volume_type_id(self): # Should not list volume type extra spec for nonexistent type id. self.assertRaises( lib_exc.NotFound, self.admin_volume_types_client.list_volume_types_extra_specs, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('9f402cbd-1838-4eb4-9554-126a6b1908c9') def test_get_nonexistent_volume_type_id(self): # Should not get volume type extra spec for nonexistent type id. self.assertRaises( lib_exc.NotFound, self.admin_volume_types_client.show_volume_type_extra_specs, data_utils.rand_uuid(), "spec1") @decorators.attr(type=['negative']) @decorators.idempotent_id('c881797d-12ff-4f1a-b09d-9f6212159753') def test_get_nonexistent_extra_spec_name(self): # Should not get volume type extra spec for nonexistent extra spec # name. self.assertRaises( lib_exc.NotFound, self.admin_volume_types_client.show_volume_type_extra_specs, self.volume_type['id'], "nonexistent_extra_spec_name") tempest-17.2.0/tempest/api/volume/admin/test_volumes_actions.py0000666000175100017510000001154013207044712024765 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumesActionsTest(base.BaseVolumeAdminTest): def _create_reset_and_force_delete_temp_volume(self, status=None): # Create volume, reset volume status, and force delete temp volume temp_volume = self.create_volume() if status: self.admin_volume_client.reset_volume_status( temp_volume['id'], status=status) waiters.wait_for_volume_resource_status( self.volumes_client, temp_volume['id'], status) self.admin_volume_client.force_delete_volume(temp_volume['id']) self.volumes_client.wait_for_resource_deletion(temp_volume['id']) @decorators.idempotent_id('d063f96e-a2e0-4f34-8b8a-395c42de1845') def test_volume_reset_status(self): # test volume reset status : available->error->available->maintenance volume = self.create_volume() self.addCleanup(waiters.wait_for_volume_resource_status, self.volumes_client, volume['id'], 'available') self.addCleanup(self.admin_volume_client.reset_volume_status, volume['id'], status='available') for status in ['error', 'available', 'maintenance']: self.admin_volume_client.reset_volume_status( volume['id'], status=status) waiters.wait_for_volume_resource_status( self.volumes_client, volume['id'], status) @decorators.idempotent_id('21737d5a-92f2-46d7-b009-a0cc0ee7a570') def test_volume_force_delete_when_volume_is_creating(self): # test force delete when status of volume is creating self._create_reset_and_force_delete_temp_volume('creating') @decorators.idempotent_id('db8d607a-aa2e-4beb-b51d-d4005c232011') def test_volume_force_delete_when_volume_is_attaching(self): # test force delete when status of volume is attaching self._create_reset_and_force_delete_temp_volume('attaching') @decorators.idempotent_id('3e33a8a8-afd4-4d64-a86b-c27a185c5a4a') def test_volume_force_delete_when_volume_is_error(self): # test force delete when status of volume is error self._create_reset_and_force_delete_temp_volume('error') @decorators.idempotent_id('b957cabd-1486-4e21-90cf-a9ed3c39dfb2') def test_volume_force_delete_when_volume_is_maintenance(self): # test force delete when status of volume is maintenance self._create_reset_and_force_delete_temp_volume('maintenance') @decorators.idempotent_id('d38285d9-929d-478f-96a5-00e66a115b81') @utils.services('compute') def test_force_detach_volume(self): # Create a server and a volume server_id = self.create_server()['id'] volume_id = self.create_volume()['id'] # Attach volume self.volumes_client.attach_volume( volume_id, instance_uuid=server_id, mountpoint='/dev/%s' % CONF.compute.volume_device_name) waiters.wait_for_volume_resource_status(self.volumes_client, volume_id, 'in-use') self.addCleanup(waiters.wait_for_volume_resource_status, self.volumes_client, volume_id, 'available') self.addCleanup(self.volumes_client.detach_volume, volume_id) attachment = self.volumes_client.show_volume( volume_id)['volume']['attachments'][0] # Reset volume's status to error self.admin_volume_client.reset_volume_status(volume_id, status='error') waiters.wait_for_volume_resource_status(self.volumes_client, volume_id, 'error') # Force detach volume self.admin_volume_client.force_detach_volume( volume_id, connector=None, attachment_id=attachment['attachment_id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume_id, 'available') vol_info = self.volumes_client.show_volume(volume_id)['volume'] self.assertIn('attachments', vol_info) self.assertEmpty(vol_info['attachments']) tempest-17.2.0/tempest/api/volume/test_extensions.py0000666000175100017510000000331313207044712022661 0ustar zuulzuul00000000000000# 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_log import log as logging from tempest.api.volume import base from tempest import config from tempest.lib import decorators CONF = config.CONF LOG = logging.getLogger(__name__) class ExtensionsTestJSON(base.BaseVolumeTest): @decorators.idempotent_id('94607eb0-43a5-47ca-82aa-736b41bd2e2c') def test_list_extensions(self): # List of all extensions extensions = (self.volumes_extension_client.list_extensions() ['extensions']) if not CONF.volume_feature_enabled.api_extensions: raise self.skipException('There are not any extensions configured') extension_list = [extension.get('alias') for extension in extensions] LOG.debug("Cinder extensions: %s", ','.join(extension_list)) ext = CONF.volume_feature_enabled.api_extensions[0] if ext == 'all': self.assertIn('Hosts', map(lambda x: x['name'], extensions)) elif ext: self.assertIn(ext, map(lambda x: x['alias'], extensions)) else: raise self.skipException('There are not any extensions configured') tempest-17.2.0/tempest/api/volume/test_versions.py0000666000175100017510000000210013207044712022323 0ustar zuulzuul00000000000000# Copyright 2017 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.api.volume import base from tempest.lib import decorators class VersionsTest(base.BaseVolumeTest): _api_version = 3 @decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369') @decorators.attr(type='smoke') def test_list_versions(self): # NOTE: The version data is checked on service client side # with JSON-Schema validation. It is enough to just call # the API here. self.versions_client.list_versions() tempest-17.2.0/tempest/api/volume/test_availability_zone.py0000666000175100017510000000216113207044712024167 0ustar zuulzuul00000000000000# 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.api.volume import base from tempest.lib import decorators class AvailabilityZoneTestJSON(base.BaseVolumeTest): """Tests Availability Zone API List""" @decorators.idempotent_id('01f1ae88-eba9-4c6b-a011-6f7ace06b725') def test_get_availability_zone_list(self): # List of availability zone availability_zone = ( self.availability_zone_client.list_availability_zones() ['availabilityZoneInfo']) self.assertNotEmpty(availability_zone) tempest-17.2.0/tempest/api/volume/test_volume_absolute_limits.py0000666000175100017510000000367713207044712025265 0ustar zuulzuul00000000000000# Copyright 2016 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 tempest.api.volume import base from tempest import config from tempest.lib import decorators CONF = config.CONF # NOTE(zhufl): This inherits from BaseVolumeAdminTest because # it requires force_tenant_isolation=True, which need admin # credentials to create non-admin users for the tests. class AbsoluteLimitsTests(base.BaseVolumeAdminTest): # noqa # avoid existing volumes of pre-defined tenant force_tenant_isolation = True @classmethod def resource_setup(cls): super(AbsoluteLimitsTests, cls).resource_setup() # Create a shared volume for tests cls.volume = cls.create_volume() @decorators.idempotent_id('8e943f53-e9d6-4272-b2e9-adcf2f7c29ad') def test_get_volume_absolute_limits(self): # get volume limit for a tenant absolute_limits = \ self.volume_limits_client.show_limits( )['limits']['absolute'] # verify volume limits and defaults per tenants self.assertEqual(absolute_limits['totalGigabytesUsed'], CONF.volume.volume_size) self.assertEqual(absolute_limits['totalVolumesUsed'], 1) self.assertEqual(absolute_limits['totalSnapshotsUsed'], 0) self.assertEqual(absolute_limits['totalBackupsUsed'], 0) self.assertEqual(absolute_limits['totalBackupGigabytesUsed'], 0) tempest-17.2.0/tempest/api/volume/__init__.py0000666000175100017510000000000013207044712021150 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/volume/base.py0000666000175100017510000003304213207044712020337 0ustar zuulzuul00000000000000# 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 tempest.api.volume import api_microversion_fixture from tempest.common import compute from tempest.common import waiters from tempest import config from tempest.lib.common import api_version_utils from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import exceptions import tempest.test CONF = config.CONF class BaseVolumeTest(api_version_utils.BaseMicroversionTest, tempest.test.BaseTestCase): """Base test case class for all Cinder API tests.""" _api_version = 2 credentials = ['primary'] @classmethod def skip_checks(cls): super(BaseVolumeTest, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) if cls._api_version == 2: if not CONF.volume_feature_enabled.api_v2: msg = "Volume API v2 is disabled" raise cls.skipException(msg) elif cls._api_version == 3: if not CONF.volume_feature_enabled.api_v3: msg = "Volume API v3 is disabled" raise cls.skipException(msg) else: msg = ("Invalid Cinder API version (%s)" % cls._api_version) raise exceptions.InvalidConfiguration(msg) api_version_utils.check_skip_with_microversion( cls.min_microversion, cls.max_microversion, CONF.volume.min_microversion, CONF.volume.max_microversion) @classmethod def setup_credentials(cls): cls.set_network_resources() super(BaseVolumeTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(BaseVolumeTest, cls).setup_clients() cls.servers_client = cls.os_primary.servers_client if CONF.service_available.glance: cls.images_client = cls.os_primary.image_client_v2 if cls._api_version == 3: cls.backups_client = cls.os_primary.backups_v3_client cls.volumes_client = cls.os_primary.volumes_v3_client cls.messages_client = cls.os_primary.volume_v3_messages_client cls.versions_client = cls.os_primary.volume_v3_versions_client cls.groups_client = cls.os_primary.groups_v3_client cls.group_snapshots_client = ( cls.os_primary.group_snapshots_v3_client) else: cls.backups_client = cls.os_primary.backups_v2_client cls.volumes_client = cls.os_primary.volumes_v2_client cls.snapshots_client = cls.os_primary.snapshots_v2_client cls.volumes_extension_client =\ cls.os_primary.volumes_v2_extension_client cls.availability_zone_client = ( cls.os_primary.volume_v2_availability_zone_client) cls.volume_limits_client = cls.os_primary.volume_v2_limits_client def setUp(self): super(BaseVolumeTest, self).setUp() self.useFixture(api_microversion_fixture.APIMicroversionFixture( self.request_microversion)) @classmethod def resource_setup(cls): super(BaseVolumeTest, cls).resource_setup() cls.request_microversion = ( api_version_utils.select_request_microversion( cls.min_microversion, CONF.volume.min_microversion)) cls.snapshots = [] cls.volumes = [] cls.image_ref = CONF.compute.image_ref cls.flavor_ref = CONF.compute.flavor_ref cls.build_interval = CONF.volume.build_interval cls.build_timeout = CONF.volume.build_timeout @classmethod def resource_cleanup(cls): cls.clear_snapshots() cls.clear_volumes() super(BaseVolumeTest, cls).resource_cleanup() @classmethod def create_volume(cls, wait_until='available', **kwargs): """Wrapper utility that returns a test volume. :param wait_until: wait till volume status. """ if 'size' not in kwargs: kwargs['size'] = CONF.volume.volume_size if 'imageRef' in kwargs: image = cls.images_client.show_image(kwargs['imageRef']) min_disk = image['min_disk'] kwargs['size'] = max(kwargs['size'], min_disk) if 'name' not in kwargs: name = data_utils.rand_name(cls.__name__ + '-Volume') kwargs['name'] = name volume = cls.volumes_client.create_volume(**kwargs)['volume'] cls.volumes.append(volume) waiters.wait_for_volume_resource_status(cls.volumes_client, volume['id'], wait_until) return volume @classmethod def create_snapshot(cls, volume_id=1, **kwargs): """Wrapper utility that returns a test snapshot.""" if 'name' not in kwargs: name = data_utils.rand_name(cls.__name__ + '-Snapshot') kwargs['name'] = name snapshot = cls.snapshots_client.create_snapshot( volume_id=volume_id, **kwargs)['snapshot'] cls.snapshots.append(snapshot['id']) waiters.wait_for_volume_resource_status(cls.snapshots_client, snapshot['id'], 'available') return snapshot def create_backup(self, volume_id, backup_client=None, **kwargs): """Wrapper utility that returns a test backup.""" if backup_client is None: backup_client = self.backups_client if 'name' not in kwargs: name = data_utils.rand_name(self.__class__.__name__ + '-Backup') kwargs['name'] = name backup = backup_client.create_backup( volume_id=volume_id, **kwargs)['backup'] self.addCleanup(backup_client.delete_backup, backup['id']) waiters.wait_for_volume_resource_status(backup_client, backup['id'], 'available') return backup # NOTE(afazekas): these create_* and clean_* could be defined # only in a single location in the source, and could be more general. @staticmethod def delete_volume(client, volume_id): """Delete volume by the given client""" client.delete_volume(volume_id) client.wait_for_resource_deletion(volume_id) def delete_snapshot(self, snapshot_id, snapshots_client=None): """Delete snapshot by the given client""" if snapshots_client is None: snapshots_client = self.snapshots_client snapshots_client.delete_snapshot(snapshot_id) snapshots_client.wait_for_resource_deletion(snapshot_id) if snapshot_id in self.snapshots: self.snapshots.remove(snapshot_id) def attach_volume(self, server_id, volume_id): """Attach a volume to a server""" self.servers_client.attach_volume( server_id, volumeId=volume_id, device='/dev/%s' % CONF.compute.volume_device_name) waiters.wait_for_volume_resource_status(self.volumes_client, volume_id, 'in-use') self.addCleanup(waiters.wait_for_volume_resource_status, self.volumes_client, volume_id, 'available') self.addCleanup(self.servers_client.detach_volume, server_id, volume_id) @classmethod def clear_volumes(cls): for volume in cls.volumes: try: cls.volumes_client.delete_volume(volume['id']) except Exception: pass for volume in cls.volumes: try: cls.volumes_client.wait_for_resource_deletion(volume['id']) except Exception: pass @classmethod def clear_snapshots(cls): for snapshot in cls.snapshots: test_utils.call_and_ignore_notfound_exc( cls.snapshots_client.delete_snapshot, snapshot) for snapshot in cls.snapshots: test_utils.call_and_ignore_notfound_exc( cls.snapshots_client.wait_for_resource_deletion, snapshot) def create_server(self, wait_until='ACTIVE', **kwargs): name = kwargs.pop( 'name', data_utils.rand_name(self.__class__.__name__ + '-instance')) tenant_network = self.get_tenant_network() body, _ = compute.create_test_server( self.os_primary, tenant_network=tenant_network, name=name, wait_until=wait_until, **kwargs) self.addCleanup(test_utils.call_and_ignore_notfound_exc, waiters.wait_for_server_termination, self.servers_client, body['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, body['id']) return body class BaseVolumeAdminTest(BaseVolumeTest): """Base test case class for all Volume Admin API tests.""" credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(BaseVolumeAdminTest, cls).setup_clients() cls.admin_volume_qos_client = cls.os_admin.volume_qos_v2_client cls.admin_volume_services_client = \ cls.os_admin.volume_services_v2_client cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client cls.admin_volume_manage_client = cls.os_admin.volume_manage_v2_client cls.admin_volume_client = cls.os_admin.volumes_v2_client if cls._api_version == 3: cls.admin_volume_client = cls.os_admin.volumes_v3_client cls.admin_groups_client = cls.os_admin.groups_v3_client cls.admin_messages_client = cls.os_admin.volume_v3_messages_client cls.admin_group_snapshots_client = \ cls.os_admin.group_snapshots_v3_client cls.admin_group_types_client = cls.os_admin.group_types_v3_client cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client cls.admin_snapshot_manage_client = \ cls.os_admin.snapshot_manage_v2_client cls.admin_snapshots_client = cls.os_admin.snapshots_v2_client cls.admin_backups_client = cls.os_admin.backups_v2_client cls.admin_encryption_types_client = \ cls.os_admin.encryption_types_v2_client cls.admin_quota_classes_client = \ cls.os_admin.volume_quota_classes_v2_client cls.admin_quotas_client = cls.os_admin.volume_quotas_v2_client cls.admin_volume_limits_client = cls.os_admin.volume_v2_limits_client cls.admin_capabilities_client = \ cls.os_admin.volume_capabilities_v2_client cls.admin_scheduler_stats_client = \ cls.os_admin.volume_scheduler_stats_v2_client @classmethod def resource_setup(cls): super(BaseVolumeAdminTest, cls).resource_setup() cls.qos_specs = [] cls.volume_types = [] @classmethod def resource_cleanup(cls): cls.clear_qos_specs() super(BaseVolumeAdminTest, cls).resource_cleanup() cls.clear_volume_types() @classmethod def create_test_qos_specs(cls, name=None, consumer=None, **kwargs): """create a test Qos-Specs.""" name = name or data_utils.rand_name(cls.__name__ + '-QoS') consumer = consumer or 'front-end' qos_specs = cls.admin_volume_qos_client.create_qos( name=name, consumer=consumer, **kwargs)['qos_specs'] cls.qos_specs.append(qos_specs['id']) return qos_specs @classmethod def create_volume_type(cls, name=None, **kwargs): """Create a test volume-type""" name = name or data_utils.rand_name(cls.__name__ + '-volume-type') volume_type = cls.admin_volume_types_client.create_volume_type( name=name, **kwargs)['volume_type'] cls.volume_types.append(volume_type['id']) return volume_type def create_group_type(self, name=None, **kwargs): """Create a test group-type""" name = name or data_utils.rand_name( self.__class__.__name__ + '-group-type') group_type = self.admin_group_types_client.create_group_type( name=name, **kwargs)['group_type'] self.addCleanup(self.admin_group_types_client.delete_group_type, group_type['id']) return group_type @classmethod def clear_qos_specs(cls): for qos_id in cls.qos_specs: test_utils.call_and_ignore_notfound_exc( cls.admin_volume_qos_client.delete_qos, qos_id) for qos_id in cls.qos_specs: test_utils.call_and_ignore_notfound_exc( cls.admin_volume_qos_client.wait_for_resource_deletion, qos_id) @classmethod def clear_volume_types(cls): for vol_type in cls.volume_types: test_utils.call_and_ignore_notfound_exc( cls.admin_volume_types_client.delete_volume_type, vol_type) for vol_type in cls.volume_types: test_utils.call_and_ignore_notfound_exc( cls.admin_volume_types_client.wait_for_resource_deletion, vol_type) tempest-17.2.0/tempest/api/volume/test_volumes_snapshots_list.py0000666000175100017510000001647613207044712025327 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.lib import decorators CONF = config.CONF class VolumesSnapshotListTestJSON(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesSnapshotListTestJSON, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") @classmethod def resource_setup(cls): super(VolumesSnapshotListTestJSON, cls).resource_setup() cls.snapshot_id_list = [] volume_origin = cls.create_volume() # Create snapshots with params for _ in range(3): snapshot = cls.create_snapshot(volume_origin['id']) cls.snapshot_id_list.append(snapshot['id']) cls.snapshot = snapshot def _list_by_param_values_and_assert(self, with_detail=False, **params): """list or list_details with given params and validates result.""" fetched_snap_list = self.snapshots_client.list_snapshots( detail=with_detail, **params)['snapshots'] # Validating params of fetched snapshots for snap in fetched_snap_list: for key in params: msg = "Failed to list snapshots %s by %s" % \ ('details' if with_detail else '', key) self.assertEqual(params[key], snap[key], msg) def _list_snapshots_by_param_limit(self, limit, expected_elements): """list snapshots by limit param""" # Get snapshots list using limit parameter fetched_snap_list = self.snapshots_client.list_snapshots( limit=limit)['snapshots'] # Validating filtered snapshots length equals to expected_elements self.assertEqual(expected_elements, len(fetched_snap_list)) @decorators.idempotent_id('59f41f43-aebf-48a9-ab5d-d76340fab32b') def test_snapshots_list_with_params(self): """list snapshots with params.""" # Verify list snapshots by display_name filter params = {'name': self.snapshot['name']} self._list_by_param_values_and_assert(**params) # Verify list snapshots by status filter params = {'status': 'available'} self._list_by_param_values_and_assert(**params) # Verify list snapshots by status and display name filter params = {'status': 'available', 'name': self.snapshot['name']} self._list_by_param_values_and_assert(**params) @decorators.idempotent_id('220a1022-1fcd-4a74-a7bd-6b859156cda2') def test_snapshots_list_details_with_params(self): """list snapshot details with params.""" # Verify list snapshot details by display_name filter params = {'name': self.snapshot['name']} self._list_by_param_values_and_assert(with_detail=True, **params) # Verify list snapshot details by status filter params = {'status': 'available'} self._list_by_param_values_and_assert(with_detail=True, **params) # Verify list snapshot details by status and display name filter params = {'status': 'available', 'name': self.snapshot['name']} self._list_by_param_values_and_assert(with_detail=True, **params) @decorators.idempotent_id('db4d8e0a-7a2e-41cc-a712-961f6844e896') def test_snapshot_list_param_limit(self): # List returns limited elements self._list_snapshots_by_param_limit(limit=1, expected_elements=1) @decorators.idempotent_id('a1427f61-420e-48a5-b6e3-0b394fa95400') def test_snapshot_list_param_limit_equals_infinite(self): # List returns all elements when request limit exceeded # snapshots number snap_list = self.snapshots_client.list_snapshots()['snapshots'] self._list_snapshots_by_param_limit(limit=100000, expected_elements=len(snap_list)) @decorators.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a') def test_snapshot_list_param_limit_equals_zero(self): # List returns zero elements self._list_snapshots_by_param_limit(limit=0, expected_elements=0) def _list_snapshots_param_sort(self, sort_key, sort_dir): """list snapshots by sort param""" snap_list = self.snapshots_client.list_snapshots( sort_key=sort_key, sort_dir=sort_dir)['snapshots'] self.assertNotEmpty(snap_list) if sort_key is 'display_name': sort_key = 'name' # Note: On Cinder API, 'display_name' works as a sort key # on a request, a volume name appears as 'name' on the response. # So Tempest needs to change the key name here for this inconsistent # API behavior. sorted_list = [snapshot[sort_key] for snapshot in snap_list] msg = 'The list of snapshots was not sorted correctly.' self.assertEqual(sorted(sorted_list, reverse=(sort_dir == 'desc')), sorted_list, msg) @decorators.idempotent_id('c5513ada-64c1-4d28-83b9-af3307ec1388') def test_snapshot_list_param_sort_id_asc(self): self._list_snapshots_param_sort(sort_key='id', sort_dir='asc') @decorators.idempotent_id('8a7fe058-0b41-402a-8afd-2dbc5a4a718b') def test_snapshot_list_param_sort_id_desc(self): self._list_snapshots_param_sort(sort_key='id', sort_dir='desc') @decorators.idempotent_id('4052c3a0-2415-440a-a8cc-305a875331b0') def test_snapshot_list_param_sort_created_at_asc(self): self._list_snapshots_param_sort(sort_key='created_at', sort_dir='asc') @decorators.idempotent_id('dcbbe24a-f3c0-4ec8-9274-55d48db8d1cf') def test_snapshot_list_param_sort_created_at_desc(self): self._list_snapshots_param_sort(sort_key='created_at', sort_dir='desc') @decorators.idempotent_id('d58b5fed-0c37-42d3-8c5d-39014ac13c00') def test_snapshot_list_param_sort_name_asc(self): self._list_snapshots_param_sort(sort_key='display_name', sort_dir='asc') @decorators.idempotent_id('96ba6f4d-1f18-47e1-b4bc-76edc6c21250') def test_snapshot_list_param_sort_name_desc(self): self._list_snapshots_param_sort(sort_key='display_name', sort_dir='desc') @decorators.idempotent_id('05489dde-44bc-4961-a1f5-3ce7ee7824f7') def test_snapshot_list_param_marker(self): # The list of snapshots should end before the provided marker params = {'marker': self.snapshot_id_list[1]} snap_list = self.snapshots_client.list_snapshots(**params)['snapshots'] fetched_list_id = [snap['id'] for snap in snap_list] # Verify the list of snapshots ends before the provided # marker(second snapshot), therefore only the first snapshot # should displayed. self.assertEqual(self.snapshot_id_list[:1], fetched_list_id) tempest-17.2.0/tempest/api/volume/test_volume_metadata.py0000666000175100017510000001020313207044712023625 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.volume import base from tempest.lib import decorators class VolumesMetadataTest(base.BaseVolumeTest): @classmethod def resource_setup(cls): super(VolumesMetadataTest, cls).resource_setup() # Create a volume cls.volume = cls.create_volume() def tearDown(self): # Update the metadata to {} self.volumes_client.update_volume_metadata(self.volume['id'], {}) super(VolumesMetadataTest, self).tearDown() @decorators.idempotent_id('6f5b125b-f664-44bf-910f-751591fe5769') def test_crud_volume_metadata(self): # Create metadata for the volume metadata = {"key1": "value1", "key2": "value2", "key3": "value3", "key4": ""} update = {"key4": "value4", "key1": "value1_update"} expected = {"key4": "value4"} body = self.volumes_client.create_volume_metadata(self.volume['id'], metadata)['metadata'] self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Get the metadata of the volume body = self.volumes_client.show_volume_metadata( self.volume['id'])['metadata'] self.assertThat(body.items(), matchers.ContainsAll(metadata.items()), 'Create metadata for the volume failed') # Update metadata body = self.volumes_client.update_volume_metadata( self.volume['id'], update)['metadata'] self.assertEqual(update, body) body = self.volumes_client.show_volume_metadata( self.volume['id'])['metadata'] self.assertEqual(update, body, 'Update metadata failed') # Delete one item metadata of the volume self.volumes_client.delete_volume_metadata_item( self.volume['id'], "key1") body = self.volumes_client.show_volume_metadata( self.volume['id'])['metadata'] self.assertNotIn("key1", body) self.assertThat(body.items(), matchers.ContainsAll(expected.items()), 'Delete one item metadata of the volume failed') @decorators.idempotent_id('862261c5-8df4-475a-8c21-946e50e36a20') def test_update_show_volume_metadata_item(self): # Update metadata item for the volume metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update_item = {"key3": "value3_update"} expect = {"key1": "value1", "key2": "value2", "key3": "value3_update"} # Create metadata for the volume body = self.volumes_client.create_volume_metadata( self.volume['id'], metadata)['metadata'] self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Update metadata item body = self.volumes_client.update_volume_metadata_item( self.volume['id'], "key3", update_item)['meta'] self.assertEqual(update_item, body) # Get a specific metadata item of the volume body = self.volumes_client.show_volume_metadata_item( self.volume['id'], "key3")['meta'] self.assertEqual({"key3": expect['key3']}, body) # Get the metadata of the volume body = self.volumes_client.show_volume_metadata( self.volume['id'])['metadata'] self.assertThat(body.items(), matchers.ContainsAll(expect.items())) tempest-17.2.0/tempest/api/volume/test_volumes_negative.py0000666000175100017510000003475313207044712024052 0ustar zuulzuul00000000000000# 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 six from tempest.api.volume import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumesNegativeTest(base.BaseVolumeTest): @classmethod def resource_setup(cls): super(VolumesNegativeTest, cls).resource_setup() # Create a test shared instance and volume for attach/detach tests cls.volume = cls.create_volume() def create_image(self): # Create image image_name = data_utils.rand_name(self.__class__.__name__ + "-image") image = self.images_client.create_image( name=image_name, container_format=CONF.image.container_formats[0], disk_format=CONF.image.disk_formats[0], visibility='private', min_disk=CONF.volume.volume_size + 1) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.images_client.delete_image, image['id']) # Upload image with 1KB data image_file = six.BytesIO(data_utils.random_bytes()) self.images_client.store_image_file(image['id'], image_file) waiters.wait_for_image_status(self.images_client, image['id'], 'active') return image @decorators.attr(type=['negative']) @decorators.idempotent_id('f131c586-9448-44a4-a8b0-54ca838aa43e') def test_volume_get_nonexistent_volume_id(self): # Should not be able to get a non-existent volume self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('555efa6e-efcd-44ef-8a3b-4a7ca4837a29') def test_volume_delete_nonexistent_volume_id(self): # Should not be able to delete a non-existent Volume self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('1ed83a8a-682d-4dfb-a30e-ee63ffd6c049') def test_create_volume_with_invalid_size(self): # Should not be able to create volume with invalid size in request self.assertRaises(lib_exc.BadRequest, self.volumes_client.create_volume, size='#$%') @decorators.attr(type=['negative']) @decorators.idempotent_id('9387686f-334f-4d31-a439-33494b9e2683') def test_create_volume_without_passing_size(self): # Should not be able to create volume without passing size # in request self.assertRaises(lib_exc.BadRequest, self.volumes_client.create_volume, size='') @decorators.attr(type=['negative']) @decorators.idempotent_id('41331caa-eaf4-4001-869d-bc18c1869360') def test_create_volume_with_size_zero(self): # Should not be able to create volume with size zero self.assertRaises(lib_exc.BadRequest, self.volumes_client.create_volume, size='0') @decorators.attr(type=['negative']) @decorators.idempotent_id('8b472729-9eba-446e-a83b-916bdb34bef7') def test_create_volume_with_size_negative(self): # Should not be able to create volume with size negative self.assertRaises(lib_exc.BadRequest, self.volumes_client.create_volume, size='-1') @decorators.attr(type=['negative']) @decorators.idempotent_id('10254ed8-3849-454e-862e-3ab8e6aa01d2') def test_create_volume_with_nonexistent_volume_type(self): # Should not be able to create volume with non-existent volume type self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume, size='1', volume_type=data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('0c36f6ae-4604-4017-b0a9-34fdc63096f9') def test_create_volume_with_nonexistent_snapshot_id(self): # Should not be able to create volume with non-existent snapshot self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume, size='1', snapshot_id=data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('47c73e08-4be8-45bb-bfdf-0c4e79b88344') def test_create_volume_with_nonexistent_source_volid(self): # Should not be able to create volume with non-existent source volume self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume, size='1', source_volid=data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('0186422c-999a-480e-a026-6a665744c30c') def test_update_volume_with_nonexistent_volume_id(self): self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume, volume_id=data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d') def test_update_volume_with_invalid_volume_id(self): self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume, volume_id=data_utils.rand_name('invalid')) @decorators.attr(type=['negative']) @decorators.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b') def test_update_volume_with_empty_volume_id(self): self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume, volume_id='') @decorators.attr(type=['negative']) @decorators.idempotent_id('30799cfd-7ee4-446c-b66c-45b383ed211b') def test_get_invalid_volume_id(self): # Should not be able to get volume with invalid id self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume, data_utils.rand_name('invalid')) @decorators.attr(type=['negative']) @decorators.idempotent_id('c6c3db06-29ad-4e91-beb0-2ab195fe49e3') def test_get_volume_without_passing_volume_id(self): # Should not be able to get volume when empty ID is passed self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume, '') @decorators.attr(type=['negative']) @decorators.idempotent_id('1f035827-7c32-4019-9240-b4ec2dbd9dfd') def test_delete_invalid_volume_id(self): # Should not be able to delete volume when invalid ID is passed self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume, data_utils.rand_name('invalid')) @decorators.attr(type=['negative']) @decorators.idempotent_id('441a1550-5d44-4b30-af0f-a6d402f52026') def test_delete_volume_without_passing_volume_id(self): # Should not be able to delete volume when empty ID is passed self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume, '') @decorators.attr(type=['negative']) @decorators.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6') @utils.services('compute') def test_attach_volumes_with_nonexistent_volume_id(self): server = self.create_server() self.assertRaises(lib_exc.NotFound, self.volumes_client.attach_volume, data_utils.rand_uuid(), instance_uuid=server['id'], mountpoint="/dev/vdc") @decorators.attr(type=['negative']) @decorators.idempotent_id('9f9c24e4-011d-46b5-b992-952140ce237a') def test_detach_volumes_with_invalid_volume_id(self): self.assertRaises(lib_exc.NotFound, self.volumes_client.detach_volume, 'xxx') @decorators.attr(type=['negative']) @decorators.idempotent_id('e0c75c74-ee34-41a9-9288-2a2051452854') def test_volume_extend_with_size_smaller_than_original_size(self): # Extend volume with smaller size than original size. extend_size = 0 self.assertRaises(lib_exc.BadRequest, self.volumes_client.extend_volume, self.volume['id'], new_size=extend_size) @decorators.attr(type=['negative']) @decorators.idempotent_id('5d0b480d-e833-439f-8a5a-96ad2ed6f22f') def test_volume_extend_with_non_number_size(self): # Extend volume when size is non number. extend_size = 'abc' self.assertRaises(lib_exc.BadRequest, self.volumes_client.extend_volume, self.volume['id'], new_size=extend_size) @decorators.attr(type=['negative']) @decorators.idempotent_id('355218f1-8991-400a-a6bb-971239287d92') def test_volume_extend_with_None_size(self): # Extend volume with None size. extend_size = None self.assertRaises(lib_exc.BadRequest, self.volumes_client.extend_volume, self.volume['id'], new_size=extend_size) @decorators.attr(type=['negative']) @decorators.idempotent_id('8f05a943-013c-4063-ac71-7baf561e82eb') def test_volume_extend_with_nonexistent_volume_id(self): # Extend volume size when volume is nonexistent. extend_size = self.volume['size'] + 1 self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume, data_utils.rand_uuid(), new_size=extend_size) @decorators.attr(type=['negative']) @decorators.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115') def test_volume_extend_without_passing_volume_id(self): # Extend volume size when passing volume id is None. extend_size = self.volume['size'] + 1 self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume, None, new_size=extend_size) @decorators.attr(type=['negative']) @decorators.idempotent_id('ac6084c0-0546-45f9-b284-38a367e0e0e2') def test_reserve_volume_with_nonexistent_volume_id(self): self.assertRaises(lib_exc.NotFound, self.volumes_client.reserve_volume, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('eb467654-3dc1-4a72-9b46-47c29d22654c') def test_unreserve_volume_with_nonexistent_volume_id(self): self.assertRaises(lib_exc.NotFound, self.volumes_client.unreserve_volume, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c') def test_reserve_volume_with_negative_volume_status(self): # Mark volume as reserved. self.volumes_client.reserve_volume(self.volume['id']) # Mark volume which is marked as reserved before self.assertRaises(lib_exc.BadRequest, self.volumes_client.reserve_volume, self.volume['id']) # Unmark volume as reserved. self.volumes_client.unreserve_volume(self.volume['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('0f4aa809-8c7b-418f-8fb3-84c7a5dfc52f') def test_list_volumes_with_nonexistent_name(self): v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') params = {'name': v_name} fetched_volume = self.volumes_client.list_volumes( params=params)['volumes'] self.assertEmpty(fetched_volume) @decorators.attr(type=['negative']) @decorators.idempotent_id('9ca17820-a0e7-4cbd-a7fa-f4468735e359') def test_list_volumes_detail_with_nonexistent_name(self): v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') params = {'name': v_name} fetched_volume = \ self.volumes_client.list_volumes( detail=True, params=params)['volumes'] self.assertEmpty(fetched_volume) @decorators.attr(type=['negative']) @decorators.idempotent_id('143b279b-7522-466b-81be-34a87d564a7c') def test_list_volumes_with_invalid_status(self): params = {'status': 'null'} fetched_volume = self.volumes_client.list_volumes( params=params)['volumes'] self.assertEmpty(fetched_volume) @decorators.attr(type=['negative']) @decorators.idempotent_id('ba94b27b-be3f-496c-a00e-0283b373fa75') def test_list_volumes_detail_with_invalid_status(self): params = {'status': 'null'} fetched_volume = \ self.volumes_client.list_volumes(detail=True, params=params)['volumes'] self.assertEmpty(fetched_volume) @decorators.attr(type=['negative']) @decorators.idempotent_id('5b810c91-0ad1-47ce-aee8-615f789be78f') @utils.services('image') def test_create_volume_from_image_with_decreasing_size(self): # Create image image = self.create_image() # Note(jeremyZ): To shorten the test time (uploading a big size image # is time-consuming), here just consider the scenario that volume size # is smaller than the min_disk of image. self.assertRaises(lib_exc.BadRequest, self.volumes_client.create_volume, size=CONF.volume.volume_size, imageRef=image['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('d15e7f35-2cfc-48c8-9418-c8223a89bcbb') @utils.services('image') def test_create_volume_from_deactivated_image(self): # Create image image = self.create_image() # Deactivate the image self.images_client.deactivate_image(image['id']) body = self.images_client.show_image(image['id']) self.assertEqual("deactivated", body['status']) # Try creating a volume from deactivated image self.assertRaises(lib_exc.BadRequest, self.create_volume, imageRef=image['id']) tempest-17.2.0/tempest/api/volume/test_volumes_backup.py0000666000175100017510000001642013207044712023504 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from testtools import matchers from tempest.api.volume import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumesBackupsTest(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesBackupsTest, cls).skip_checks() if not CONF.volume_feature_enabled.backup: raise cls.skipException("Cinder backup feature disabled") def restore_backup(self, backup_id): # Restore a backup restored_volume = self.backups_client.restore_backup( backup_id)['restore'] # Delete backup self.addCleanup(self.volumes_client.delete_volume, restored_volume['volume_id']) self.assertEqual(backup_id, restored_volume['backup_id']) waiters.wait_for_volume_resource_status(self.backups_client, backup_id, 'available') waiters.wait_for_volume_resource_status(self.volumes_client, restored_volume['volume_id'], 'available') return restored_volume @testtools.skipIf(CONF.volume.storage_protocol == 'ceph', 'ceph does not support arbitrary container names') @decorators.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6') def test_volume_backup_create_get_detailed_list_restore_delete(self): # Create a volume with metadata metadata = {"vol-meta1": "value1", "vol-meta2": "value2", "vol-meta3": "value3"} volume = self.create_volume(metadata=metadata) self.addCleanup(self.volumes_client.delete_volume, volume['id']) # Create a backup backup_name = data_utils.rand_name( self.__class__.__name__ + '-Backup') description = data_utils.rand_name("volume-backup-description") backup = self.create_backup(volume_id=volume['id'], name=backup_name, description=description, container='container') self.assertEqual(backup_name, backup['name']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # Get a given backup backup = self.backups_client.show_backup(backup['id'])['backup'] self.assertEqual(backup_name, backup['name']) self.assertEqual(description, backup['description']) self.assertEqual('container', backup['container']) # Get all backups with detail backups = self.backups_client.list_backups( detail=True)['backups'] for backup_info in backups: self.assertIn('created_at', backup_info) self.assertIn('links', backup_info) self.assertIn((backup['name'], backup['id']), [(m['name'], m['id']) for m in backups]) restored_volume = self.restore_backup(backup['id']) restored_volume_metadata = self.volumes_client.show_volume( restored_volume['volume_id'])['volume']['metadata'] # Verify the backups has been restored successfully # with the metadata of the source volume. self.assertThat(restored_volume_metadata.items(), matchers.ContainsAll(metadata.items())) @decorators.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6') @utils.services('compute') def test_backup_create_attached_volume(self): """Test backup create using force flag. Cinder allows to create a volume backup, whether the volume status is "available" or "in-use". """ # Create a server volume = self.create_volume() self.addCleanup(self.volumes_client.delete_volume, volume['id']) server = self.create_server() # Attach volume to instance self.attach_volume(server['id'], volume['id']) # Create backup using force flag backup_name = data_utils.rand_name( self.__class__.__name__ + '-Backup') backup = self.create_backup(volume_id=volume['id'], name=backup_name, force=True) self.assertEqual(backup_name, backup['name']) @decorators.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15') @utils.services('image') def test_bootable_volume_backup_and_restore(self): # Create volume from image img_uuid = CONF.compute.image_ref volume = self.create_volume(imageRef=img_uuid) volume_details = self.volumes_client.show_volume( volume['id'])['volume'] self.assertEqual('true', volume_details['bootable']) # Create a backup backup = self.create_backup(volume_id=volume['id']) # Restore the backup restored_volume_id = self.restore_backup(backup['id'])['volume_id'] # Verify the restored backup volume is bootable restored_volume_info = self.volumes_client.show_volume( restored_volume_id)['volume'] self.assertEqual('true', restored_volume_info['bootable']) class VolumesBackupsV39Test(base.BaseVolumeTest): _api_version = 3 min_microversion = '3.9' max_microversion = 'latest' @classmethod def skip_checks(cls): super(VolumesBackupsV39Test, cls).skip_checks() if not CONF.volume_feature_enabled.backup: raise cls.skipException("Cinder backup feature disabled") @decorators.idempotent_id('9b374cbc-be5f-4d37-8848-7efb8a873dcc') def test_update_backup(self): # Create volume and backup volume = self.create_volume() backup = self.create_backup(volume_id=volume['id']) # Update backup and assert response body for update_backup method update_kwargs = { 'name': data_utils.rand_name(self.__class__.__name__ + '-Backup'), 'description': data_utils.rand_name("volume-backup-description") } update_backup = self.backups_client.update_backup( backup['id'], **update_kwargs)['backup'] self.assertEqual(backup['id'], update_backup['id']) self.assertEqual(update_kwargs['name'], update_backup['name']) self.assertIn('links', update_backup) # Assert response body for show_backup method retrieved_backup = self.backups_client.show_backup( backup['id'])['backup'] for key in update_kwargs: self.assertEqual(update_kwargs[key], retrieved_backup[key]) tempest-17.2.0/tempest/api/volume/test_volumes_snapshots.py0000666000175100017510000001657013207044712024267 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 testtools import matchers from tempest.api.volume import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumesSnapshotTestJSON(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesSnapshotTestJSON, cls).skip_checks() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") @classmethod def resource_setup(cls): super(VolumesSnapshotTestJSON, cls).resource_setup() cls.volume_origin = cls.create_volume() @decorators.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2') @utils.services('compute') def test_snapshot_create_delete_with_volume_in_use(self): # Create a test instance server = self.create_server() self.attach_volume(server['id'], self.volume_origin['id']) # Snapshot a volume which attached to an instance with force=False self.assertRaises(lib_exc.BadRequest, self.create_snapshot, self.volume_origin['id'], force=False) # Snapshot a volume attached to an instance snapshot1 = self.create_snapshot(self.volume_origin['id'], force=True) snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True) snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True) # Delete the snapshots. Some snapshot implementations can take # different paths according to order they are deleted. self.delete_snapshot(snapshot1['id']) self.delete_snapshot(snapshot3['id']) self.delete_snapshot(snapshot2['id']) @decorators.idempotent_id('5210a1de-85a0-11e6-bb21-641c676a5d61') @utils.services('compute') def test_snapshot_create_offline_delete_online(self): # Create a snapshot while it is not attached snapshot1 = self.create_snapshot(self.volume_origin['id']) # Create a server and attach it server = self.create_server() self.attach_volume(server['id'], self.volume_origin['id']) # Now that the volume is attached, create another snapshots snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True) snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True) # Delete the snapshots. Some snapshot implementations can take # different paths according to order they are deleted. self.delete_snapshot(snapshot3['id']) self.delete_snapshot(snapshot1['id']) self.delete_snapshot(snapshot2['id']) @decorators.idempotent_id('2a8abbe4-d871-46db-b049-c41f5af8216e') def test_snapshot_create_get_list_update_delete(self): # Create a snapshot with metadata metadata = {"snap-meta1": "value1", "snap-meta2": "value2", "snap-meta3": "value3"} snapshot = self.create_snapshot(self.volume_origin['id'], metadata=metadata) # Get the snap and check for some of its details snap_get = self.snapshots_client.show_snapshot( snapshot['id'])['snapshot'] self.assertEqual(self.volume_origin['id'], snap_get['volume_id'], "Referred volume origin mismatch") self.assertEqual(self.volume_origin['size'], snap_get['size']) # Verify snapshot metadata self.assertThat(snap_get['metadata'].items(), matchers.ContainsAll(metadata.items())) # Compare also with the output from the list action tracking_data = (snapshot['id'], snapshot['name']) snaps_list = self.snapshots_client.list_snapshots()['snapshots'] snaps_data = [(f['id'], f['name']) for f in snaps_list] self.assertIn(tracking_data, snaps_data) # Updates snapshot with new values new_s_name = data_utils.rand_name( self.__class__.__name__ + '-new-snap') new_desc = 'This is the new description of snapshot.' params = {'name': new_s_name, 'description': new_desc} update_snapshot = self.snapshots_client.update_snapshot( snapshot['id'], **params)['snapshot'] # Assert response body for update_snapshot method self.assertEqual(new_s_name, update_snapshot['name']) self.assertEqual(new_desc, update_snapshot['description']) # Assert response body for show_snapshot method updated_snapshot = self.snapshots_client.show_snapshot( snapshot['id'])['snapshot'] self.assertEqual(new_s_name, updated_snapshot['name']) self.assertEqual(new_desc, updated_snapshot['description']) # Delete the snapshot self.delete_snapshot(snapshot['id']) @decorators.idempotent_id('677863d1-3142-456d-b6ac-9924f667a7f4') def test_volume_from_snapshot(self): # Creates a volume from a snapshot passing a size # different from the source src_size = CONF.volume.volume_size src_vol = self.create_volume(size=src_size) src_snap = self.create_snapshot(src_vol['id']) # Destination volume bigger than source snapshot dst_vol = self.create_volume(snapshot_id=src_snap['id'], size=src_size + 1) # NOTE(zhufl): dst_vol is created based on snapshot, so dst_vol # should be deleted before deleting snapshot, otherwise deleting # snapshot will end with status 'error-deleting'. This depends on # the implementation mechanism of vendors, generally speaking, # some verdors will use "virtual disk clone" which will promote # disk clone speed, and in this situation the "disk clone" # is just a relationship between volume and snapshot. self.addCleanup(self.delete_volume, self.volumes_client, dst_vol['id']) volume = self.volumes_client.show_volume(dst_vol['id'])['volume'] # Should allow self.assertEqual(volume['snapshot_id'], src_snap['id']) self.assertEqual(volume['size'], src_size + 1) @decorators.idempotent_id('bbcfa285-af7f-479e-8c1a-8c34fc16543c') @testtools.skipUnless(CONF.volume_feature_enabled.backup, "Cinder backup is disabled") def test_snapshot_backup(self): # Create a snapshot snapshot = self.create_snapshot(volume_id=self.volume_origin['id']) backup = self.create_backup(volume_id=self.volume_origin['id'], snapshot_id=snapshot['id']) backup_info = self.backups_client.show_backup(backup['id'])['backup'] self.assertEqual(self.volume_origin['id'], backup_info['volume_id']) self.assertEqual(snapshot['id'], backup_info['snapshot_id']) tempest-17.2.0/tempest/api/volume/test_volumes_clone.py0000666000175100017510000000504613207044712023341 0ustar zuulzuul00000000000000# Copyright 2016 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 tempest.api.volume import base from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumesCloneTest(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesCloneTest, cls).skip_checks() if not CONF.volume_feature_enabled.clone: raise cls.skipException("Cinder volume clones are disabled") def _verify_volume_clone(self, source_volume, cloned_volume, bootable='false', extra_size=0): cloned_vol_details = self.volumes_client.show_volume( cloned_volume['id'])['volume'] self.assertEqual(source_volume['id'], cloned_vol_details['source_volid']) self.assertEqual(source_volume['size'] + extra_size, cloned_vol_details['size']) self.assertEqual(bootable, cloned_vol_details['bootable']) @decorators.idempotent_id('9adae371-a257-43a5-9555-dc7c88e66e0e') def test_create_from_volume(self): # Creates a volume from another volume passing a size different from # the source volume. src_size = CONF.volume.volume_size src_vol = self.create_volume(size=src_size) # Destination volume bigger than source dst_vol = self.create_volume(source_volid=src_vol['id'], size=src_size + 1) self._verify_volume_clone(src_vol, dst_vol, extra_size=1) @decorators.idempotent_id('cbbcd7c6-5a6c-481a-97ac-ca55ab715d16') @utils.services('image') def test_create_from_bootable_volume(self): # Create volume from image img_uuid = CONF.compute.image_ref src_vol = self.create_volume(imageRef=img_uuid) # Create a volume from the bootable volume cloned_vol = self.create_volume(source_volid=src_vol['id']) self._verify_volume_clone(src_vol, cloned_vol, bootable='true') tempest-17.2.0/tempest/api/volume/test_volumes_extend.py0000666000175100017510000002134713207044725023536 0ustar zuulzuul00000000000000# 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 time import testtools from tempest.api.volume import base from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumesExtendTest(base.BaseVolumeTest): @decorators.idempotent_id('9a36df71-a257-43a5-9555-dc7c88e66e0e') def test_volume_extend(self): # Extend Volume Test. volume = self.create_volume() extend_size = volume['size'] + 1 self.volumes_client.extend_volume(volume['id'], new_size=extend_size) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') volume = self.volumes_client.show_volume(volume['id'])['volume'] self.assertEqual(volume['size'], extend_size) @decorators.idempotent_id('86be1cba-2640-11e5-9c82-635fb964c912') @testtools.skipUnless(CONF.volume_feature_enabled.snapshot, "Cinder volume snapshots are disabled") @decorators.skip_because(bug='1687044') def test_volume_extend_when_volume_has_snapshot(self): volume = self.create_volume() self.create_snapshot(volume['id']) extend_size = volume['size'] + 1 self.volumes_client.extend_volume(volume['id'], new_size=extend_size) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') resized_volume = self.volumes_client.show_volume( volume['id'])['volume'] self.assertEqual(extend_size, resized_volume['size']) class VolumesExtendAttachedTest(base.BaseVolumeTest): """Tests extending the size of an attached volume.""" # We need admin credentials for getting instance action event details. By # default a non-admin can list and show instance actions if they own the # server instance, but since the event details can contain error messages # and tracebacks, like an instance fault, those are not viewable by # non-admins. This is obviously not a great user experience since the user # may not know when the operation is actually complete. A microversion in # the compute API will be added so that non-admins can see instance action # events but will continue to hide the traceback field. # TODO(mriedem): Change this to not rely on the admin user to get the event # details once that microversion is available in Nova. credentials = ['primary', 'admin'] _api_version = 3 # NOTE(mriedem): The minimum required volume API version is 3.42 and the # minimum required compute API microversion is 2.51, but the compute call # is implicit - Cinder calls Nova at that microversion, Tempest does not. min_microversion = '3.42' @classmethod def setup_clients(cls): super(VolumesExtendAttachedTest, cls).setup_clients() cls.admin_servers_client = cls.os_admin.servers_client def _find_extend_volume_instance_action(self, server_id): actions = self.servers_client.list_instance_actions( server_id)['instanceActions'] for action in actions: if action['action'] == 'extend_volume': return action def _find_extend_volume_instance_action_finish_event(self, action): # This has to be called by an admin client otherwise # the events don't show up. action = self.admin_servers_client.show_instance_action( action['instance_uuid'], action['request_id'])['instanceAction'] for event in action['events']: if (event['event'] == 'compute_extend_volume' and event['finish_time']): return event @decorators.idempotent_id('301f5a30-1c6f-4ea0-be1a-91fd28d44354') @testtools.skipUnless(CONF.volume_feature_enabled.extend_attached_volume, "Attached volume extend is disabled.") def test_extend_attached_volume(self): """This is a happy path test which does the following: * Create a volume at the configured volume_size. * Create a server instance. * Attach the volume to the server. * Wait for the volume status to be "in-use". * Extend the size of the volume and wait for the volume status to go back to "in-use". * Assert the volume size change is reflected in the volume API. * Wait for the "compute_extend_volume" instance action event to show up in the compute API with the success or failure status. We fail if we timeout waiting for the instance action event to show up, or if the action on the server fails. """ # Create a test volume. Will be automatically cleaned up on teardown. volume = self.create_volume() # Create a test server. Will be automatically cleaned up on teardown. server = self.create_server() # Attach the volume to the server and wait for the volume status to be # "in-use". self.attach_volume(server['id'], volume['id']) # Extend the size of the volume. If this is successful, the volume API # will change the status on the volume to "extending" before doing an # RPC cast to the volume manager on the backend. Note that we multiply # the size of the volume since certain Cinder backends, e.g. ScaleIO, # require multiples of 8GB. extend_size = volume['size'] * 2 self.volumes_client.extend_volume(volume['id'], new_size=extend_size) # The volume status should go back to in-use since it is still attached # to the server instance. waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'in-use') # Assert that the volume size has changed in the volume API. volume = self.volumes_client.show_volume(volume['id'])['volume'] self.assertEqual(extend_size, volume['size']) # Now we wait for the "compute_extend_volume" instance action event # to show up for the server instance. This is our indication that the # asynchronous operation is complete on the compute side. start_time = int(time.time()) timeout = self.servers_client.build_timeout action = self._find_extend_volume_instance_action(server['id']) while action is None and int(time.time()) - start_time < timeout: time.sleep(self.servers_client.build_interval) action = self._find_extend_volume_instance_action(server['id']) if action is None: msg = ("Timed out waiting to get 'extend_volume' instance action " "record for server %(server)s after %(timeout)s seconds." % {'server': server['id'], 'timeout': timeout}) raise lib_exc.TimeoutException(msg) # Now that we found the extend_volume instance action, we can wait for # the compute_extend_volume instance action event to show up to # indicate the operation is complete. start_time = int(time.time()) event = self._find_extend_volume_instance_action_finish_event(action) while event is None and int(time.time()) - start_time < timeout: time.sleep(self.servers_client.build_interval) event = self._find_extend_volume_instance_action_finish_event( action) if event is None: msg = ("Timed out waiting to get 'compute_extend_volume' instance " "action event record for server %(server)s and request " "%(request_id)s after %(timeout)s seconds." % {'server': server['id'], 'request_id': action['request_id'], 'timeout': timeout}) raise lib_exc.TimeoutException(msg) # Finally, assert that the action completed successfully. self.assertTrue( event['result'].lower() == 'success', "Unexpected compute_extend_volume result '%(result)s' for request " "%(request_id)s." % {'result': event['result'], 'request_id': action['request_id']}) tempest-17.2.0/tempest/api/volume/test_volume_transfers.py0000666000175100017510000001027513207044712024065 0ustar zuulzuul00000000000000# 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. from tempest.api.volume import base from tempest.common import waiters from tempest.lib import decorators class VolumesTransfersTest(base.BaseVolumeTest): credentials = ['primary', 'alt', 'admin'] @classmethod def setup_clients(cls): super(VolumesTransfersTest, cls).setup_clients() cls.client = cls.os_primary.volume_transfers_v2_client cls.alt_client = cls.os_alt.volume_transfers_v2_client cls.alt_volumes_client = cls.os_alt.volumes_v2_client cls.adm_volumes_client = cls.os_admin.volumes_v2_client @decorators.idempotent_id('4d75b645-a478-48b1-97c8-503f64242f1a') def test_create_get_list_accept_volume_transfer(self): # Create a volume first volume = self.create_volume() self.addCleanup(self.delete_volume, self.adm_volumes_client, volume['id']) # Create a volume transfer transfer = self.client.create_volume_transfer( volume_id=volume['id'])['transfer'] transfer_id = transfer['id'] auth_key = transfer['auth_key'] waiters.wait_for_volume_resource_status( self.volumes_client, volume['id'], 'awaiting-transfer') # Get a volume transfer body = self.client.show_volume_transfer(transfer_id)['transfer'] self.assertEqual(volume['id'], body['volume_id']) # List volume transfers, the result should be greater than # or equal to 1 body = self.client.list_volume_transfers()['transfers'] self.assertNotEmpty(body) # Accept a volume transfer by alt_tenant body = self.alt_client.accept_volume_transfer( transfer_id, auth_key=auth_key)['transfer'] for key in ['id', 'name', 'links', 'volume_id']: self.assertIn(key, body) waiters.wait_for_volume_resource_status(self.alt_volumes_client, volume['id'], 'available') accepted_volume = self.alt_volumes_client.show_volume( volume['id'])['volume'] self.assertEqual(self.os_alt.credentials.user_id, accepted_volume['user_id']) self.assertEqual(self.os_alt.credentials.project_id, accepted_volume['os-vol-tenant-attr:tenant_id']) @decorators.idempotent_id('ab526943-b725-4c07-b875-8e8ef87a2c30') def test_create_list_delete_volume_transfer(self): # Create a volume first volume = self.create_volume() self.addCleanup(self.delete_volume, self.adm_volumes_client, volume['id']) # Create a volume transfer transfer_id = self.client.create_volume_transfer( volume_id=volume['id'])['transfer']['id'] waiters.wait_for_volume_resource_status( self.volumes_client, volume['id'], 'awaiting-transfer') # List all volume transfers with details, check the detail-specific # elements, and look for the created transfer. transfers = self.client.list_volume_transfers(detail=True)['transfers'] self.assertNotEmpty(transfers) for transfer in transfers: self.assertIn('created_at', transfer) volume_list = [transfer['volume_id'] for transfer in transfers] self.assertIn(volume['id'], volume_list, 'Transfer not found for volume %s' % volume['id']) # Delete a volume transfer self.client.delete_volume_transfer(transfer_id) waiters.wait_for_volume_resource_status( self.volumes_client, volume['id'], 'available') tempest-17.2.0/tempest/api/volume/test_volumes_clone_negative.py0000666000175100017510000000324413207044712025221 0ustar zuulzuul00000000000000# Copyright 2016 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 tempest.api.volume import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class VolumesCloneNegativeTest(base.BaseVolumeTest): @classmethod def skip_checks(cls): super(VolumesCloneNegativeTest, cls).skip_checks() if not CONF.volume_feature_enabled.clone: raise cls.skipException("Cinder volume clones are disabled") @decorators.attr(type=['negative']) @decorators.idempotent_id('9adae371-a257-43a5-459a-dc7c88e66e0e') def test_create_from_volume_decreasing_size(self): # Creates a volume from another volume passing a size different from # the source volume. src_size = CONF.volume.volume_size + 1 src_vol = self.create_volume(size=src_size) # Destination volume smaller than source self.assertRaises(exceptions.BadRequest, self.volumes_client.create_volume, size=src_size - 1, source_volid=src_vol['id']) tempest-17.2.0/tempest/api/volume/test_volumes_list.py0000666000175100017510000004050613207044712023214 0ustar zuulzuul00000000000000# 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 operator import random from six.moves.urllib import parse from testtools import matchers from tempest.api.volume import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class VolumesListTestJSON(base.BaseVolumeTest): # NOTE: This test creates a number of 1G volumes. To run successfully, # ensure that the backing file for the volume group that Nova uses # has space for at least 3 1G volumes! # If you are running a Devstack environment, ensure that the # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc VOLUME_FIELDS = ('id', 'name') def assertVolumesIn(self, fetched_list, expected_list, fields=None): """Check out the list. This function is aim at check out whether all of the volumes in expected_list are in fetched_list. """ if fields: fieldsgetter = operator.itemgetter(*fields) expected_list = map(fieldsgetter, expected_list) fetched_list = [fieldsgetter(item) for item in fetched_list] missing_vols = [v for v in expected_list if v not in fetched_list] if not missing_vols: return def str_vol(vol): return "%s:%s" % (vol['id'], vol['name']) raw_msg = "Could not find volumes %s in expected list %s; fetched %s" self.fail(raw_msg % ([str_vol(v) for v in missing_vols], [str_vol(v) for v in expected_list], [str_vol(v) for v in fetched_list])) @classmethod def resource_setup(cls): super(VolumesListTestJSON, cls).resource_setup() existing_volumes = cls.volumes_client.list_volumes()['volumes'] cls.volume_id_list = [vol['id'] for vol in existing_volumes] # Create 3 test volumes cls.volume_list = [] cls.metadata = {'Type': 'work'} for _ in range(3): volume = cls.create_volume(metadata=cls.metadata) volume = cls.volumes_client.show_volume(volume['id'])['volume'] cls.volume_list.append(volume) cls.volume_id_list.append(volume['id']) def _list_by_param_value_and_assert(self, params, with_detail=False): """list or list_details with given params and validates result""" if with_detail: fetched_vol_list = \ self.volumes_client.list_volumes(detail=True, params=params)['volumes'] else: fetched_vol_list = self.volumes_client.list_volumes( params=params)['volumes'] # Validating params of fetched volumes if with_detail: for volume in fetched_vol_list: for key in params: msg = "Failed to list volumes %s by %s" % \ ('details' if with_detail else '', key) if key == 'metadata': self.assertThat( volume[key].items(), matchers.ContainsAll(params[key].items()), msg) else: self.assertEqual(params[key], volume[key], msg) @decorators.attr(type='smoke') @decorators.idempotent_id('0b6ddd39-b948-471f-8038-4787978747c4') def test_volume_list(self): # Get a list of Volumes # Fetch all volumes fetched_list = self.volumes_client.list_volumes()['volumes'] self.assertVolumesIn(fetched_list, self.volume_list, fields=self.VOLUME_FIELDS) @decorators.idempotent_id('adcbb5a7-5ad8-4b61-bd10-5380e111a877') def test_volume_list_with_details(self): # Get a list of Volumes with details # Fetch all Volumes fetched_list = self.volumes_client.list_volumes(detail=True)['volumes'] self.assertVolumesIn(fetched_list, self.volume_list) @decorators.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02') def test_volume_list_by_name(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name']} fetched_vol = self.volumes_client.list_volumes( params=params)['volumes'] self.assertEqual(1, len(fetched_vol), str(fetched_vol)) self.assertEqual(fetched_vol[0]['name'], volume['name']) @decorators.idempotent_id('2de3a6d4-12aa-403b-a8f2-fdeb42a89623') def test_volume_list_details_by_name(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name']} fetched_vol = self.volumes_client.list_volumes( detail=True, params=params)['volumes'] self.assertEqual(1, len(fetched_vol), str(fetched_vol)) self.assertEqual(fetched_vol[0]['name'], volume['name']) @decorators.idempotent_id('39654e13-734c-4dab-95ce-7613bf8407ce') def test_volumes_list_by_status(self): params = {'status': 'available'} fetched_list = self.volumes_client.list_volumes( params=params)['volumes'] self._list_by_param_value_and_assert(params) self.assertVolumesIn(fetched_list, self.volume_list, fields=self.VOLUME_FIELDS) @decorators.idempotent_id('2943f712-71ec-482a-bf49-d5ca06216b9f') def test_volumes_list_details_by_status(self): params = {'status': 'available'} fetched_list = self.volumes_client.list_volumes( detail=True, params=params)['volumes'] for volume in fetched_list: self.assertEqual('available', volume['status']) self.assertVolumesIn(fetched_list, self.volume_list) @decorators.idempotent_id('2016a942-3020-40d7-95ce-7613bf8407ce') def test_volumes_list_by_bootable(self): """Check out volumes. This test function is aim at check out whether all of the volumes in volume_list are not a bootable volume. """ params = {'bootable': 'false'} fetched_list = self.volumes_client.list_volumes( params=params)['volumes'] self._list_by_param_value_and_assert(params) self.assertVolumesIn(fetched_list, self.volume_list, fields=self.VOLUME_FIELDS) @decorators.idempotent_id('2016a939-72ec-482a-bf49-d5ca06216b9f') def test_volumes_list_details_by_bootable(self): params = {'bootable': 'false'} fetched_list = self.volumes_client.list_volumes( detail=True, params=params)['volumes'] for volume in fetched_list: self.assertEqual('false', volume['bootable']) self.assertVolumesIn(fetched_list, self.volume_list) @decorators.idempotent_id('c0cfa863-3020-40d7-b587-e35f597d5d87') def test_volumes_list_by_availability_zone(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] zone = volume['availability_zone'] params = {'availability_zone': zone} fetched_list = self.volumes_client.list_volumes( params=params)['volumes'] self._list_by_param_value_and_assert(params) self.assertVolumesIn(fetched_list, self.volume_list, fields=self.VOLUME_FIELDS) @decorators.idempotent_id('e1b80d13-94f0-4ba2-a40e-386af29f8db1') def test_volumes_list_details_by_availability_zone(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] zone = volume['availability_zone'] params = {'availability_zone': zone} fetched_list = self.volumes_client.list_volumes( detail=True, params=params)['volumes'] for volume in fetched_list: self.assertEqual(zone, volume['availability_zone']) self.assertVolumesIn(fetched_list, self.volume_list) @decorators.idempotent_id('b5ebea1b-0603-40a0-bb41-15fcd0a53214') def test_volume_list_with_param_metadata(self): # Test to list volumes when metadata param is given params = {'metadata': self.metadata} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('1ca92d3c-4a8e-4b43-93f5-e4c7fb3b291d') def test_volume_list_with_detail_param_metadata(self): # Test to list volumes details when metadata param is given params = {'metadata': self.metadata} self._list_by_param_value_and_assert(params, with_detail=True) @decorators.idempotent_id('777c87c1-2fc4-4883-8b8e-5c0b951d1ec8') def test_volume_list_param_display_name_and_status(self): # Test to list volume when display name and status param is given volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name'], 'status': 'available'} self._list_by_param_value_and_assert(params) @decorators.idempotent_id('856ab8ca-6009-4c37-b691-be1065528ad4') def test_volume_list_with_detail_param_display_name_and_status(self): # Test to list volume when name and status param is given volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name'], 'status': 'available'} self._list_by_param_value_and_assert(params, with_detail=True) @decorators.idempotent_id('2a7064eb-b9c3-429b-b888-33928fc5edd3') def test_volume_list_details_with_multiple_params(self): # List volumes detail using combined condition def _list_details_with_multiple_params(limit=2, status='available', sort_dir='asc', sort_key='id'): params = {'limit': limit, 'status': status, 'sort_dir': sort_dir, 'sort_key': sort_key } fetched_volume = self.volumes_client.list_volumes( detail=True, params=params)['volumes'] self.assertEqual(limit, len(fetched_volume), "The count of volumes is %s, expected:%s " % (len(fetched_volume), limit)) self.assertEqual(status, fetched_volume[0]['status']) self.assertEqual(status, fetched_volume[1]['status']) val0 = fetched_volume[0][sort_key] val1 = fetched_volume[1][sort_key] if sort_dir == 'asc': self.assertLess(val0, val1, "list is not in asc order with sort_key: %s." " %s" % (sort_key, fetched_volume)) elif sort_dir == 'desc': self.assertGreater(val0, val1, "list is not in desc order with sort_key: " "%s. %s" % (sort_key, fetched_volume)) _list_details_with_multiple_params() _list_details_with_multiple_params(sort_dir='desc') def _test_pagination(self, resource, ids=None, limit=1, **kwargs): """Check list pagination functionality for a resource. This method requests the list of resources and follows pagination links. If an iterable is supplied in ids it will check that all ids are retrieved and that only those are listed, that we will get a next link for an empty page if the number of items is divisible by used limit (this is expected behavior). We can specify number of items per request using limit argument. """ # Get list method for the type of resource from the client client = getattr(self, resource + '_client') method = getattr(client, 'list_' + resource) # Include limit in params for list request params = kwargs.pop('params', {}) params['limit'] = limit # Store remaining items we are expecting from list if ids is not None: remaining = list(ids) else: remaining = None # Mark that the current iteration is not from a 'next' link next = None while True: # Get a list page response = method(params=params, **kwargs) # If we have to check ids if remaining is not None: # Confirm we receive expected number of elements num_expected = min(len(remaining), limit) self.assertEqual(num_expected, len(response[resource]), 'Requested %(#expect)d but got %(#received)d ' % {'#expect': num_expected, '#received': len(response[resource])}) # For each received element for element in response[resource]: element_id = element['id'] # Check it's one of expected ids self.assertIn(element_id, ids, 'Id %(id)s is not in expected ids %(ids)s' % {'id': element_id, 'ids': ids}) # If not in remaining, we have received it twice self.assertIn(element_id, remaining, 'Id %s was received twice' % element_id) # We no longer expect it remaining.remove(element_id) # If the current iteration is from a 'next' link, check that the # absolute url is the same as the one used for this request if next: self.assertEqual(next, response.response['content-location']) # Get next from response next = None for link in response.get(resource + '_links', ()): if link['rel'] == 'next': next = link['href'] break # Check if we have next and we shouldn't or the other way around if remaining is not None: if remaining or (num_expected and len(ids) % limit == 0): self.assertIsNotNone(next, 'Missing link to next page') else: self.assertIsNone(next, 'Unexpected link to next page') # If we can follow to the next page, get params from url to make # request in the form of a relative URL if next: params = parse.urlparse(next).query # If cannot follow make sure it's because we have finished else: self.assertEmpty(remaining or [], 'No more pages reported, but still ' 'missing ids %s' % remaining) break @decorators.idempotent_id('e9138a2c-f67b-4796-8efa-635c196d01de') def test_volume_list_details_pagination(self): self._test_pagination('volumes', ids=self.volume_id_list, detail=True) @decorators.idempotent_id('af55e775-8e4b-4feb-8719-215c43b0238c') def test_volume_list_pagination(self): self._test_pagination('volumes', ids=self.volume_id_list, detail=False) @decorators.idempotent_id('46eff077-100b-427f-914e-3db2abcdb7e2') def test_volume_list_with_detail_param_marker(self): # Choosing a random volume from a list of volumes for 'marker' # parameter marker = random.choice(self.volume_id_list) # Though Cinder volumes are returned sorted by ID by default # this is implicit. Let make this explicit in case Cinder # folks change their minds. params = {'marker': marker, 'sort': 'id:asc'} # Running volume list using marker parameter vol_with_marker = self.volumes_client.list_volumes( detail=True, params=params)['volumes'] expected_volumes_id = { id for id in self.volume_id_list if id > marker } self.assertEqual( expected_volumes_id, {v['id'] for v in vol_with_marker} ) tempest-17.2.0/tempest/api/volume/test_volumes_actions.py0000666000175100017510000001600513207044712023676 0ustar zuulzuul00000000000000# 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 tempest.api.volume import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class VolumesActionsTest(base.BaseVolumeTest): @classmethod def resource_setup(cls): super(VolumesActionsTest, cls).resource_setup() # Create a test shared volume for attach/detach tests cls.volume = cls.create_volume() @decorators.idempotent_id('fff42874-7db5-4487-a8e1-ddda5fb5288d') @decorators.attr(type='smoke') @utils.services('compute') def test_attach_detach_volume_to_instance(self): # Create a server server = self.create_server() # Volume is attached and detached successfully from an instance self.volumes_client.attach_volume(self.volume['id'], instance_uuid=server['id'], mountpoint='/dev/%s' % CONF.compute.volume_device_name) waiters.wait_for_volume_resource_status(self.volumes_client, self.volume['id'], 'in-use') self.volumes_client.detach_volume(self.volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, self.volume['id'], 'available') @decorators.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599') def test_volume_bootable(self): # Verify that a volume bootable flag is retrieved for bool_bootable in [True, False]: self.volumes_client.set_bootable_volume(self.volume['id'], bootable=bool_bootable) fetched_volume = self.volumes_client.show_volume( self.volume['id'])['volume'] # Get Volume information # NOTE(masayukig): 'bootable' is "true" or "false" in the current # cinder implementation. So we need to cast boolean values to str # and make it lower to compare here. self.assertEqual(str(bool_bootable).lower(), fetched_volume['bootable']) @decorators.idempotent_id('9516a2c8-9135-488c-8dd6-5677a7e5f371') @utils.services('compute') def test_get_volume_attachment(self): # Create a server server = self.create_server() # Verify that a volume's attachment information is retrieved self.volumes_client.attach_volume(self.volume['id'], instance_uuid=server['id'], mountpoint='/dev/%s' % CONF.compute.volume_device_name) waiters.wait_for_volume_resource_status(self.volumes_client, self.volume['id'], 'in-use') self.addCleanup(waiters.wait_for_volume_resource_status, self.volumes_client, self.volume['id'], 'available') self.addCleanup(self.volumes_client.detach_volume, self.volume['id']) volume = self.volumes_client.show_volume(self.volume['id'])['volume'] self.assertIn('attachments', volume) attachment = volume['attachments'][0] self.assertEqual('/dev/%s' % CONF.compute.volume_device_name, attachment['device']) self.assertEqual(server['id'], attachment['server_id']) self.assertEqual(self.volume['id'], attachment['id']) self.assertEqual(self.volume['id'], attachment['volume_id']) @decorators.idempotent_id('d8f1ca95-3d5b-44a3-b8ca-909691c9532d') @utils.services('image') def test_volume_upload(self): # NOTE(gfidente): the volume uploaded in Glance comes from setUpClass, # it is shared with the other tests. After it is uploaded in Glance, # there is no way to delete it from Cinder, so we delete it from Glance # using the Glance images_client and from Cinder via tearDownClass. image_name = data_utils.rand_name(self.__class__.__name__ + '-Image') body = self.volumes_client.upload_volume( self.volume['id'], image_name=image_name, disk_format=CONF.volume.disk_format)['os-volume_upload_image'] image_id = body["image_id"] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.images_client.delete_image, image_id) waiters.wait_for_image_status(self.images_client, image_id, 'active') waiters.wait_for_volume_resource_status(self.volumes_client, self.volume['id'], 'available') image_info = self.images_client.show_image(image_id) self.assertEqual(image_name, image_info['name']) self.assertEqual(CONF.volume.disk_format, image_info['disk_format']) @decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33') def test_reserve_unreserve_volume(self): # Mark volume as reserved. self.volumes_client.reserve_volume(self.volume['id']) # To get the volume info body = self.volumes_client.show_volume(self.volume['id'])['volume'] self.assertIn('attaching', body['status']) # Unmark volume as reserved. self.volumes_client.unreserve_volume(self.volume['id']) # To get the volume info body = self.volumes_client.show_volume(self.volume['id'])['volume'] self.assertIn('available', body['status']) @decorators.idempotent_id('fff74e1e-5bd3-4b33-9ea9-24c103bc3f59') def test_volume_readonly_update(self): for readonly in [True, False]: # Update volume readonly self.volumes_client.update_volume_readonly(self.volume['id'], readonly=readonly) # Get Volume information fetched_volume = self.volumes_client.show_volume( self.volume['id'])['volume'] # NOTE(masayukig): 'readonly' is "True" or "False" in the current # cinder implementation. So we need to cast boolean values to str # to compare here. self.assertEqual(str(readonly), fetched_volume['metadata']['readonly']) tempest-17.2.0/tempest/api/object_storage/0000775000175100017510000000000013207045130020525 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/object_storage/test_container_acl_negative.py0000666000175100017510000002511413207044712026633 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ObjectACLsNegativeTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] @classmethod def setup_credentials(cls): super(ObjectACLsNegativeTest, cls).setup_credentials() cls.os_operator = cls.os_roles_operator_alt @classmethod def resource_setup(cls): super(ObjectACLsNegativeTest, cls).resource_setup() cls.test_auth_data = cls.os_operator.auth_provider.auth_data def setUp(self): super(ObjectACLsNegativeTest, self).setUp() self.container_name = data_utils.rand_name(name='TestContainer') self.container_client.update_container(self.container_name) def tearDown(self): self.delete_containers([self.container_name]) super(ObjectACLsNegativeTest, self).tearDown() @decorators.attr(type=['negative']) @decorators.idempotent_id('af587587-0c24-4e15-9822-8352ce711013') def test_write_object_without_using_creds(self): # trying to create object with empty headers # X-Auth-Token is not provided object_name = data_utils.rand_name(name='Object') self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) self.assertRaises(lib_exc.Unauthorized, self.object_client.create_object, self.container_name, object_name, 'data', headers={}) @decorators.attr(type=['negative']) @decorators.idempotent_id('af85af0b-a025-4e72-a90e-121babf55720') def test_delete_object_without_using_creds(self): # create object object_name = data_utils.rand_name(name='Object') self.object_client.create_object(self.container_name, object_name, 'data') # trying to delete object with empty headers # X-Auth-Token is not provided self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) self.assertRaises(lib_exc.Unauthorized, self.object_client.delete_object, self.container_name, object_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('63d84e37-55a6-42e2-9e5f-276e60e26a00') def test_write_object_with_non_authorized_user(self): # attempt to upload another file using non-authorized user # User provided token is forbidden. ACL are not set object_name = data_utils.rand_name(name='Object') # trying to create object with non-authorized user self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(lib_exc.Forbidden, self.object_client.create_object, self.container_name, object_name, 'data', headers={}) @decorators.attr(type=['negative']) @decorators.idempotent_id('abf63359-be52-4feb-87dd-447689fc77fd') def test_read_object_with_non_authorized_user(self): # attempt to read object using non-authorized user # User provided token is forbidden. ACL are not set object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object( self.container_name, object_name, 'data') self.assertHeaders(resp, 'Object', 'PUT') # trying to get object with non authorized user token self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(lib_exc.Forbidden, self.object_client.get_object, self.container_name, object_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('7343ac3d-cfed-4198-9bb0-00149741a492') def test_delete_object_with_non_authorized_user(self): # attempt to delete object using non-authorized user # User provided token is forbidden. ACL are not set object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object( self.container_name, object_name, 'data') self.assertHeaders(resp, 'Object', 'PUT') # trying to delete object with non-authorized user token self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(lib_exc.Forbidden, self.object_client.delete_object, self.container_name, object_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('9ed01334-01e9-41ea-87ea-e6f465582823') def test_read_object_without_rights(self): # attempt to read object using non-authorized user # update X-Container-Read metadata ACL cont_headers = {'X-Container-Read': 'badtenant:baduser'} resp_meta, _ = ( self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object(self.container_name, object_name, 'data') self.assertHeaders(resp, 'Object', 'PUT') # Trying to read the object without rights self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(lib_exc.Forbidden, self.object_client.get_object, self.container_name, object_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('a3a585a7-d8cf-4b65-a1a0-edc2b1204f85') def test_write_object_without_rights(self): # attempt to write object using non-authorized user # update X-Container-Write metadata ACL cont_headers = {'X-Container-Write': 'badtenant:baduser'} resp_meta, _ = ( self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # Trying to write the object without rights self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) object_name = data_utils.rand_name(name='Object') self.assertRaises(lib_exc.Forbidden, self.object_client.create_object, self.container_name, object_name, 'data', headers={}) @decorators.attr(type=['negative']) @decorators.idempotent_id('8ba512ad-aa6e-444e-b882-2906a0ea2052') def test_write_object_without_write_rights(self): # attempt to write object using non-authorized user # update X-Container-Read and X-Container-Write metadata ACL tenant_name = self.os_operator.credentials.tenant_name username = self.os_operator.credentials.username cont_headers = {'X-Container-Read': tenant_name + ':' + username, 'X-Container-Write': ''} resp_meta, _ = ( self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # Trying to write the object without write rights self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) object_name = data_utils.rand_name(name='Object') self.assertRaises(lib_exc.Forbidden, self.object_client.create_object, self.container_name, object_name, 'data', headers={}) @decorators.attr(type=['negative']) @decorators.idempotent_id('b4e366f8-f185-47ab-b789-df4416f9ecdb') def test_delete_object_without_write_rights(self): # attempt to delete object using non-authorized user # update X-Container-Read and X-Container-Write metadata ACL tenant_name = self.os_operator.credentials.tenant_name username = self.os_operator.credentials.username cont_headers = {'X-Container-Read': tenant_name + ':' + username, 'X-Container-Write': ''} resp_meta, _ = ( self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object(self.container_name, object_name, 'data') self.assertHeaders(resp, 'Object', 'PUT') # Trying to delete the object without write rights self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(lib_exc.Forbidden, self.object_client.delete_object, self.container_name, object_name) tempest-17.2.0/tempest/api/object_storage/test_object_expiry.py0000666000175100017510000000727113207044712025022 0ustar zuulzuul00000000000000# 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 time from tempest.api.object_storage import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ObjectExpiryTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(ObjectExpiryTest, cls).resource_setup() cls.container_name = cls.create_container() def setUp(self): super(ObjectExpiryTest, self).setUp() # create object self.object_name, _ = self.create_object( self.container_name) @classmethod def resource_cleanup(cls): cls.delete_containers() super(ObjectExpiryTest, cls).resource_cleanup() def _test_object_expiry(self, metadata): # update object metadata resp, _ = \ self.object_client.create_or_update_object_metadata( self.container_name, self.object_name, headers=metadata) # verify object metadata resp, _ = \ self.object_client.list_object_metadata(self.container_name, self.object_name) self.assertHeaders(resp, 'Object', 'HEAD') self.assertIn('x-delete-at', resp) # we want to ensure that we will sleep long enough for things to # actually expire, so figure out how many secs in the future that is. sleepy_time = int(resp['x-delete-at']) - int(time.time()) sleepy_time = sleepy_time if sleepy_time > 0 else 0 resp, _ = self.object_client.get_object(self.container_name, self.object_name) self.assertHeaders(resp, 'Object', 'GET') self.assertIn('x-delete-at', resp) # add several seconds for safety. time.sleep(sleepy_time) # Checking whether object still exists for several seconds: # sometimes object is not deleted immediately, so we are making # get calls for an approximately 1 minute in a total. Get calls # can take 3s each sometimes so we are making the requests in # exponential periodicity for i in range(1, 6): time.sleep(2 ** i) try: self.object_client.get_object(self.container_name, self.object_name) except lib_exc.NotFound: break # object should not be there anymore self.assertRaises(lib_exc.NotFound, self.object_client.get_object, self.container_name, self.object_name) @decorators.idempotent_id('fb024a42-37f3-4ba5-9684-4f40a7910b41') def test_get_object_after_expiry_time(self): # the 10s is important, because the get calls can take 3s each # some times metadata = {'X-Delete-After': '10'} self._test_object_expiry(metadata) @decorators.idempotent_id('e592f18d-679c-48fe-9e36-4be5f47102c5') def test_get_object_at_expiry_time(self): metadata = {'X-Delete-At': str(int(time.time()) + 10)} self._test_object_expiry(metadata) tempest-17.2.0/tempest/api/object_storage/test_container_acl.py0000666000175100017510000001005313207044712024745 0ustar zuulzuul00000000000000# 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 tempest.api.object_storage import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ObjectTestACLs(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] def setUp(self): super(ObjectTestACLs, self).setUp() self.container_name = self.create_container() def tearDown(self): self.delete_containers() super(ObjectTestACLs, self).tearDown() @decorators.idempotent_id('a3270f3f-7640-4944-8448-c7ea783ea5b6') def test_read_object_with_rights(self): # attempt to read object using authorized user # update X-Container-Read metadata ACL tenant_name = self.os_roles_operator_alt.credentials.tenant_name username = self.os_roles_operator_alt.credentials.username cont_headers = {'X-Container-Read': tenant_name + ':' + username} container_client = self.os_roles_operator.container_client resp_meta, _ = ( container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.os_roles_operator.object_client.create_object( self.container_name, object_name, 'data') self.assertHeaders(resp, 'Object', 'PUT') # set alternative authentication data; cannot simply use the # other object client. self.os_roles_operator.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.os_roles_operator_alt.object_client.auth_provider. auth_data) resp, _ = self.os_roles_operator.object_client.get_object( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'GET') @decorators.idempotent_id('aa58bfa5-40d9-4bc3-82b4-d07f4a9e392a') def test_write_object_with_rights(self): # attempt to write object using authorized user # update X-Container-Write metadata ACL tenant_name = self.os_roles_operator_alt.credentials.tenant_name username = self.os_roles_operator_alt.credentials.username cont_headers = {'X-Container-Write': tenant_name + ':' + username} container_client = self.os_roles_operator.container_client resp_meta, _ = ( container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # set alternative authentication data; cannot simply use the # other object client. self.os_roles_operator.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.os_roles_operator_alt.object_client.auth_provider. auth_data) # Trying to write the object with rights object_name = data_utils.rand_name(name='Object') resp, _ = self.os_roles_operator.object_client.create_object( self.container_name, object_name, 'data', headers={}) self.assertHeaders(resp, 'Object', 'PUT') tempest-17.2.0/tempest/api/object_storage/test_object_formpost.py0000666000175100017510000001067013207044712025350 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 hashlib import hmac import time from six.moves.urllib import parse as urlparse from tempest.api.object_storage import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ObjectFormPostTest(base.BaseObjectTest): metadata = {} containers = [] @classmethod def resource_setup(cls): super(ObjectFormPostTest, cls).resource_setup() cls.container_name = cls.create_container() cls.object_name = data_utils.rand_name(name='ObjectTemp') cls.key = 'Meta' cls.metadata = {'Temp-URL-Key': cls.key} cls.account_client.create_update_or_delete_account_metadata( create_update_metadata=cls.metadata) def setUp(self): super(ObjectFormPostTest, self).setUp() # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key'], self.key) @classmethod def resource_cleanup(cls): cls.account_client.create_update_or_delete_account_metadata( delete_metadata=cls.metadata) cls.delete_containers() super(ObjectFormPostTest, cls).resource_cleanup() def get_multipart_form(self, expires=600): path = "%s/%s/%s" % ( urlparse.urlparse(self.container_client.base_url).path, self.container_name, self.object_name) redirect = '' max_file_size = 104857600 max_file_count = 10 expires += int(time.time()) hmac_body = '%s\n%s\n%s\n%s\n%s' % (path, redirect, max_file_size, max_file_count, expires) signature = hmac.new( self.key.encode(), hmac_body.encode(), hashlib.sha1 ).hexdigest() fields = {'redirect': redirect, 'max_file_size': str(max_file_size), 'max_file_count': str(max_file_count), 'expires': str(expires), 'signature': signature} boundary = '--boundary--' data = [] for (key, value) in fields.items(): data.append('--' + boundary) data.append('Content-Disposition: form-data; name="%s"' % key) data.append('') data.append(value) data.append('--' + boundary) data.append('Content-Disposition: form-data; ' 'name="file1"; filename="testfile"') data.append('Content-Type: application/octet-stream') data.append('') data.append('hello world') data.append('--' + boundary + '--') data.append('') body = '\r\n'.join(data) content_type = 'multipart/form-data; boundary=%s' % boundary return body, content_type @decorators.idempotent_id('80fac02b-6e54-4f7b-be0d-a965b5cbef76') @utils.requires_ext(extension='formpost', service='object') def test_post_object_using_form(self): body, content_type = self.get_multipart_form() headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} url = "%s/%s" % (self.container_name, self.object_name) resp, body = self.object_client.post(url, body, headers=headers) self.assertHeaders(resp, "Object", "POST") # Ensure object is available resp, body = self.object_client.get("%s/%s%s" % ( self.container_name, self.object_name, "testfile")) self.assertHeaders(resp, "Object", "GET") self.assertEqual(body.decode(), "hello world") tempest-17.2.0/tempest/api/object_storage/test_account_quotas.py0000666000175100017510000001042713207044712025201 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class AccountQuotasTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['reseller', CONF.object_storage.reseller_admin_role]] @classmethod def setup_credentials(cls): super(AccountQuotasTest, cls).setup_credentials() cls.os_reselleradmin = cls.os_roles_reseller @classmethod def resource_setup(cls): super(AccountQuotasTest, cls).resource_setup() cls.container_name = cls.create_container() # Retrieve a ResellerAdmin auth data and use it to set a quota # on the client's account cls.reselleradmin_auth_data = \ cls.os_reselleradmin.auth_provider.auth_data def setUp(self): super(AccountQuotasTest, self).setUp() # Set the reselleradmin auth in headers for next account_client # request self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # Set a quota of 20 bytes on the user's account before each test headers = {"X-Account-Meta-Quota-Bytes": "20"} self.os_roles_operator.account_client.request( "POST", url="", headers=headers, body="") def tearDown(self): # Set the reselleradmin auth in headers for next account_client # request self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # remove the quota from the container headers = {"X-Remove-Account-Meta-Quota-Bytes": "x"} self.os_roles_operator.account_client.request( "POST", url="", headers=headers, body="") super(AccountQuotasTest, self).tearDown() @classmethod def resource_cleanup(cls): cls.delete_containers() super(AccountQuotasTest, cls).resource_cleanup() @decorators.attr(type="smoke") @decorators.idempotent_id('a22ef352-a342-4587-8f47-3bbdb5b039c4') @utils.requires_ext(extension='account_quotas', service='object') def test_upload_valid_object(self): object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertHeaders(resp, 'Object', 'PUT') @decorators.attr(type=["smoke"]) @decorators.idempotent_id('63f51f9f-5f1d-4fc6-b5be-d454d70949d6') @utils.requires_ext(extension='account_quotas', service='object') def test_admin_modify_quota(self): """Test ResellerAdmin can modify/remove the quota on a user's account Using the account client, the test modifies the quota successively to: * "25": a random value different from the initial quota value. * "" : an empty value, equivalent to the removal of the quota. * "20": set the quota to its initial value. """ for quota in ("25", "", "20"): self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) headers = {"X-Account-Meta-Quota-Bytes": quota} resp, _ = self.os_roles_operator.account_client.request( "POST", url="", headers=headers, body="") self.assertEqual(resp["status"], "204") self.assertHeaders(resp, 'Account', 'POST') tempest-17.2.0/tempest/api/object_storage/test_crossdomain.py0000666000175100017510000000426713207044712024477 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest.common import custom_matchers from tempest.common import utils from tempest.lib import decorators class CrossdomainTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(CrossdomainTest, cls).resource_setup() cls.xml_start = '\n' \ '\n\n' cls.xml_end = "" def setUp(self): super(CrossdomainTest, self).setUp() # Turning http://.../v1/foobar into http://.../ self.account_client.skip_path() @decorators.idempotent_id('d1b8b031-b622-4010-82f9-ff78a9e915c7') @utils.requires_ext(extension='crossdomain', service='object') def test_get_crossdomain_policy(self): resp, body = self.account_client.get("crossdomain.xml", {}) body = body.decode() self.assertTrue(body.startswith(self.xml_start) and body.endswith(self.xml_end)) # The target of the request is not any Swift resource. Therefore, the # existence of response header is checked without a custom matcher. self.assertIn('content-length', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) tempest-17.2.0/tempest/api/object_storage/test_account_quotas_negative.py0000666000175100017510000000671213207044712027065 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class AccountQuotasNegativeTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['reseller', CONF.object_storage.reseller_admin_role]] @classmethod def setup_credentials(cls): super(AccountQuotasNegativeTest, cls).setup_credentials() cls.os_reselleradmin = cls.os_roles_reseller @classmethod def resource_setup(cls): super(AccountQuotasNegativeTest, cls).resource_setup() cls.create_container() # Retrieve a ResellerAdmin auth data and use it to set a quota # on the client's account cls.reselleradmin_auth_data = \ cls.os_reselleradmin.auth_provider.auth_data def setUp(self): super(AccountQuotasNegativeTest, self).setUp() # Set the reselleradmin auth in headers for next account_client # request self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # Set a quota of 20 bytes on the user's account before each test headers = {"X-Account-Meta-Quota-Bytes": "20"} self.os_roles_operator.account_client.request( "POST", url="", headers=headers, body="") def tearDown(self): # Set the reselleradmin auth in headers for next account_client # request self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # remove the quota from the container headers = {"X-Remove-Account-Meta-Quota-Bytes": "x"} self.os_roles_operator.account_client.request( "POST", url="", headers=headers, body="") super(AccountQuotasNegativeTest, self).tearDown() @classmethod def resource_cleanup(cls): cls.delete_containers() super(AccountQuotasNegativeTest, cls).resource_cleanup() @decorators.attr(type=["negative"]) @decorators.idempotent_id('d1dc5076-555e-4e6d-9697-28f1fe976324') @utils.requires_ext(extension='account_quotas', service='object') def test_user_modify_quota(self): """Test that a user cannot modify or remove a quota on its account.""" # Not able to remove quota self.assertRaises( lib_exc.Forbidden, self.account_client.create_update_or_delete_account_metadata, create_update_metadata={"Quota-Bytes": ""}) # Not able to modify quota self.assertRaises( lib_exc.Forbidden, self.account_client.create_update_or_delete_account_metadata, create_update_metadata={"Quota-Bytes": "100"}) tempest-17.2.0/tempest/api/object_storage/test_object_version.py0000666000175100017510000001031313207044712025156 0ustar zuulzuul00000000000000# 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 from tempest.api.object_storage import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ContainerTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(ContainerTest, cls).resource_setup() cls.containers = [] @classmethod def resource_cleanup(cls): cls.delete_containers() super(ContainerTest, cls).resource_cleanup() def assertContainer(self, container, count, byte, versioned): resp, _ = self.container_client.list_container_metadata(container) self.assertHeaders(resp, 'Container', 'HEAD') header_value = resp.get('x-container-object-count', 'Missing Header') self.assertEqual(header_value, count) header_value = resp.get('x-container-bytes-used', 'Missing Header') self.assertEqual(header_value, byte) header_value = resp.get('x-versions-location', 'Missing Header') self.assertEqual(header_value, versioned) @decorators.idempotent_id('a151e158-dcbf-4a1f-a1e7-46cd65895a6f') @testtools.skipIf( not CONF.object_storage_feature_enabled.object_versioning, 'Object-versioning is disabled') def test_versioned_container(self): # create container vers_container_name = data_utils.rand_name(name='TestVersionContainer') resp, _ = self.container_client.update_container(vers_container_name) self.containers.append(vers_container_name) self.assertHeaders(resp, 'Container', 'PUT') self.assertContainer(vers_container_name, '0', '0', 'Missing Header') base_container_name = data_utils.rand_name(name='TestBaseContainer') headers = {'X-versions-Location': vers_container_name} resp, _ = self.container_client.update_container( base_container_name, **headers) self.containers.append(base_container_name) self.assertHeaders(resp, 'Container', 'PUT') self.assertContainer(base_container_name, '0', '0', vers_container_name) object_name = data_utils.rand_name(name='TestObject') # create object data_1 = data_utils.random_bytes() resp, _ = self.object_client.create_object(base_container_name, object_name, data_1) # create 2nd version of object data_2 = data_utils.random_bytes() resp, _ = self.object_client.create_object(base_container_name, object_name, data_2) _, body = self.object_client.get_object(base_container_name, object_name) self.assertEqual(body, data_2) # delete object version 2 resp, _ = self.object_client.delete_object(base_container_name, object_name) self.assertContainer(base_container_name, '1', '1024', vers_container_name) _, body = self.object_client.get_object(base_container_name, object_name) self.assertEqual(body, data_1) # delete object version 1 self.object_client.delete_object(base_container_name, object_name) # containers should be empty self.assertContainer(base_container_name, '0', '0', vers_container_name) self.assertContainer(vers_container_name, '0', '0', 'Missing Header') tempest-17.2.0/tempest/api/object_storage/test_account_services_negative.py0000666000175100017510000000411513207044712027367 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class AccountNegativeTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] @classmethod def setup_credentials(cls): super(AccountNegativeTest, cls).setup_credentials() cls.os_operator = cls.os_roles_operator_alt @decorators.attr(type=['negative']) @decorators.idempotent_id('070e6aca-6152-4867-868d-1118d68fb38c') def test_list_containers_with_non_authorized_user(self): # list containers using non-authorized user test_auth_provider = self.os_operator.auth_provider # Get auth for the test user test_auth_provider.auth_data # Get fresh auth for test user and set it to next auth request for # account_client delattr(test_auth_provider, 'auth_data') test_auth_new_data = test_auth_provider.auth_data self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=test_auth_new_data ) params = {'format': 'json'} # list containers with non-authorized user token self.assertRaises(lib_exc.Forbidden, self.account_client.list_account_containers, params=params) tempest-17.2.0/tempest/api/object_storage/test_object_services.py0000666000175100017510000013532713207044712025331 0ustar zuulzuul00000000000000# 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 hashlib import random import re import time import zlib from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ObjectTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(ObjectTest, cls).resource_setup() cls.container_name = cls.create_container() @classmethod def resource_cleanup(cls): cls.delete_containers() super(ObjectTest, cls).resource_cleanup() def _upload_segments(self): # create object object_name = data_utils.rand_name(name='LObject') data = data_utils.arbitrary_string() segments = 10 data_segments = [data + str(i) for i in range(segments)] # uploading segments for i in range(segments): obj_name = "%s/%s" % (object_name, i) self.object_client.create_object( self.container_name, obj_name, data_segments[i]) return object_name, data_segments def _copy_object_2d(self, src_object_name, metadata=None): dst_object_name = data_utils.rand_name(name='TestObject') resp, _ = self.object_client.copy_object_2d_way(self.container_name, src_object_name, dst_object_name, metadata=metadata) return dst_object_name, resp def _check_copied_obj(self, dst_object_name, src_body, in_meta=None, not_in_meta=None): resp, dest_body = self.object_client.get_object(self.container_name, dst_object_name) self.assertEqual(src_body, dest_body) if in_meta: for meta_key in in_meta: self.assertIn('x-object-meta-' + meta_key, resp) if not_in_meta: for meta_key in not_in_meta: self.assertNotIn('x-object-meta-' + meta_key, resp) @decorators.attr(type='smoke') @decorators.idempotent_id('5b4ce26f-3545-46c9-a2ba-5754358a4c62') def test_create_object(self): # create object object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # create another object object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertHeaders(resp, 'Object', 'PUT') # check uploaded content _, body = self.object_client.get_object(self.container_name, object_name) self.assertEqual(data, body) @decorators.idempotent_id('5daebb1d-f0d5-4dc9-b541-69672eff00b0') def test_create_object_with_content_disposition(self): # create object with content_disposition object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata = {} metadata['content-disposition'] = 'inline' resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata) self.assertHeaders(resp, 'Object', 'PUT') resp, body = self.object_client.get_object( self.container_name, object_name, metadata=None) self.assertIn('content-disposition', resp) self.assertEqual(resp['content-disposition'], 'inline') self.assertEqual(body, data) @decorators.idempotent_id('605f8317-f945-4bee-ae91-013f1da8f0a0') def test_create_object_with_content_encoding(self): # create object with content_encoding object_name = data_utils.rand_name(name='TestObject') # put compressed string data_before = b'x' * 2000 data = zlib.compress(data_before) metadata = {} metadata['content-encoding'] = 'deflate' resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata) self.assertHeaders(resp, 'Object', 'PUT') # download compressed object metadata = {} metadata['accept-encoding'] = 'deflate' resp, body = self.object_client.get_object( self.container_name, object_name, metadata=metadata) self.assertEqual(body, data_before) @decorators.idempotent_id('73820093-0503-40b1-a478-edf0e69c7d1f') def test_create_object_with_etag(self): # create object with etag object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() md5 = hashlib.md5(data).hexdigest() metadata = {'Etag': md5} resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata) self.assertHeaders(resp, 'Object', 'PUT') # check uploaded content _, body = self.object_client.get_object(self.container_name, object_name) self.assertEqual(data, body) @decorators.idempotent_id('84dafe57-9666-4f6d-84c8-0814d37923b8') def test_create_object_with_expect_continue(self): # create object with expect_continue object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() status, _ = self.object_client.create_object_continue( self.container_name, object_name, data) self.assertEqual(status, 201) # check uploaded content _, body = self.object_client.get_object(self.container_name, object_name) self.assertEqual(data, body) @decorators.idempotent_id('4f84422a-e2f2-4403-b601-726a4220b54e') def test_create_object_with_transfer_encoding(self): # create object with transfer_encoding object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes(1024) headers = {'Transfer-Encoding': 'chunked'} resp, _ = self.object_client.create_object( self.container_name, object_name, data=data_utils.chunkify(data, 512), headers=headers, chunked=True) self.assertHeaders(resp, 'Object', 'PUT') # check uploaded content _, body = self.object_client.get_object(self.container_name, object_name) self.assertEqual(data, body) @decorators.idempotent_id('0f3d62a6-47e3-4554-b0e5-1a5dc372d501') def test_create_object_with_x_fresh_metadata(self): # create object with x_fresh_metadata object_name_base = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata_1 = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name_base, data, metadata=metadata_1) object_name = data_utils.rand_name(name='TestObject') metadata_2 = {'X-Copy-From': '%s/%s' % (self.container_name, object_name_base), 'X-Fresh-Metadata': 'true'} resp, _ = self.object_client.create_object( self.container_name, object_name, '', metadata=metadata_2) self.assertHeaders(resp, 'Object', 'PUT') resp, body = self.object_client.get_object(self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta', resp) self.assertEqual(data, body) @decorators.idempotent_id('1c7ed3e4-2099-406b-b843-5301d4811baf') def test_create_object_with_x_object_meta(self): # create object with object_meta object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata = {'X-Object-Meta-test-meta': 'Meta'} resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata) self.assertHeaders(resp, 'Object', 'PUT') resp, body = self.object_client.get_object(self.container_name, object_name) self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') self.assertEqual(data, body) @decorators.idempotent_id('e4183917-33db-4153-85cc-4dacbb938865') def test_create_object_with_x_object_metakey(self): # create object with the blank value of metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata = {'X-Object-Meta-test-meta': ''} resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata) self.assertHeaders(resp, 'Object', 'PUT') resp, body = self.object_client.get_object(self.container_name, object_name) self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], '') self.assertEqual(data, body) @decorators.idempotent_id('ce798afc-b278-45de-a5ce-2ea124b98b99') def test_create_object_with_x_remove_object_meta(self): # create object with x_remove_object_meta object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata_add = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=metadata_add) metadata_remove = {'X-Remove-Object-Meta-test-meta': 'Meta'} resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata_remove) self.assertHeaders(resp, 'Object', 'PUT') resp, body = self.object_client.get_object(self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta', resp) self.assertEqual(data, body) @decorators.idempotent_id('ad21e342-7916-4f9e-ab62-a1f885f2aaf9') def test_create_object_with_x_remove_object_metakey(self): # create object with the blank value of remove metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata_add = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=metadata_add) metadata_remove = {'X-Remove-Object-Meta-test-meta': ''} resp, _ = self.object_client.create_object( self.container_name, object_name, data, metadata=metadata_remove) self.assertHeaders(resp, 'Object', 'PUT') resp, body = self.object_client.get_object(self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta', resp) self.assertEqual(data, body) @decorators.idempotent_id('17738d45-03bd-4d45-9e0b-7b2f58f98687') def test_delete_object(self): # create object object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # delete object resp, _ = self.object_client.delete_object(self.container_name, object_name) self.assertHeaders(resp, 'Object', 'DELETE') @decorators.attr(type='smoke') @decorators.idempotent_id('7a94c25d-66e6-434c-9c38-97d4e2c29945') def test_update_object_metadata(self): # update object metadata object_name, _ = self.create_object(self.container_name) metadata = {'X-Object-Meta-test-meta': 'Meta'} resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') @decorators.idempotent_id('48650ed0-c189-4e1e-ad6b-1d4770c6e134') def test_update_object_metadata_with_remove_metadata(self): # update object metadata with remove metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) update_metadata = {'X-Remove-Object-Meta-test-meta1': 'Meta1'} resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=update_metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta1', resp) @decorators.idempotent_id('f726174b-2ded-4708-bff7-729d12ce1f84') def test_update_object_metadata_with_create_and_remove_metadata(self): # creation and deletion of metadata with one request object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) update_metadata = {'X-Object-Meta-test-meta2': 'Meta2', 'X-Remove-Object-Meta-test-meta1': 'Meta1'} resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=update_metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta1', resp) self.assertIn('x-object-meta-test-meta2', resp) self.assertEqual(resp['x-object-meta-test-meta2'], 'Meta2') @decorators.idempotent_id('08854588-6449-4bb7-8cca-f2e1040f5e6f') def test_update_object_metadata_with_x_object_manifest(self): # update object metadata with x_object_manifest # uploading segments object_name, _ = self._upload_segments() # creating a manifest file data_empty = '' self.object_client.create_object(self.container_name, object_name, data_empty, metadata=None) object_prefix = '%s/%s' % (self.container_name, object_name) update_metadata = {'X-Object-Manifest': object_prefix} resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=update_metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-manifest', resp) self.assertNotEmpty(resp['x-object-manifest']) @decorators.idempotent_id('0dbbe89c-6811-4d84-a2df-eca2bdd40c0e') def test_update_object_metadata_with_x_object_metakey(self): # update object metadata with a blank value of metadata object_name, _ = self.create_object(self.container_name) update_metadata = {'X-Object-Meta-test-meta': ''} resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=update_metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], '') @decorators.idempotent_id('9a88dca4-b684-425b-806f-306cd0e57e42') def test_update_object_metadata_with_x_remove_object_metakey(self): # update object metadata with a blank value of remove metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() create_metadata = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) update_metadata = {'X-Remove-Object-Meta-test-meta': ''} resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=update_metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta', resp) @decorators.attr(type='smoke') @decorators.idempotent_id('9a447cf6-de06-48de-8226-a8c6ed31caf2') def test_list_object_metadata(self): # get object metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=metadata) resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'HEAD') self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') @decorators.idempotent_id('170fb90e-f5c3-4b1f-ae1b-a18810821172') def test_list_no_object_metadata(self): # get empty list of object metadata object_name, _ = self.create_object(self.container_name) resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'HEAD') self.assertNotIn('x-object-meta-', str(resp)) @decorators.idempotent_id('23a3674c-d6de-46c3-86af-ff92bfc8a3da') def test_list_object_metadata_with_x_object_manifest(self): # get object metadata with x_object_manifest # uploading segments object_name, _ = self._upload_segments() # creating a manifest file object_prefix = '%s/%s' % (self.container_name, object_name) metadata = {'X-Object-Manifest': object_prefix} data_empty = '' resp, _ = self.object_client.create_object( self.container_name, object_name, data_empty, metadata=metadata) resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) # Check only the existence of common headers with custom matcher self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders( 'Object', 'HEAD')) self.assertIn('x-object-manifest', resp) # Etag value of a large object is enclosed in double-quotations. # This is a special case, therefore the formats of response headers # are checked without a custom matcher. self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) self.assertTrue(resp['etag'].strip('\"').isalnum()) self.assertTrue(re.match(r"^\d+\.?\d*\Z", resp['x-timestamp'])) self.assertNotEmpty(resp['content-type']) self.assertTrue(re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", resp['x-trans-id'])) self.assertNotEmpty(resp['date']) self.assertEqual(resp['accept-ranges'], 'bytes') self.assertEqual(resp['x-object-manifest'], '%s/%s' % (self.container_name, object_name)) @decorators.attr(type='smoke') @decorators.idempotent_id('02610ba7-86b7-4272-9ed8-aa8d417cb3cd') def test_get_object(self): # retrieve object's data (in response body) # create object object_name, data = self.create_object(self.container_name) # get object resp, body = self.object_client.get_object(self.container_name, object_name) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('005f9bf6-e06d-41ec-968e-96c78e0b1d82') def test_get_object_with_metadata(self): # get object with metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() metadata = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=metadata) resp, body = self.object_client.get_object( self.container_name, object_name, metadata=None) self.assertHeaders(resp, 'Object', 'GET') self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') self.assertEqual(body, data) @decorators.idempotent_id('05a1890e-7db9-4a6c-90a8-ce998a2bddfa') def test_get_object_with_range(self): # get object with range object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes(100) self.object_client.create_object(self.container_name, object_name, data, metadata=None) rand_num = random.randint(3, len(data) - 1) metadata = {'Range': 'bytes=%s-%s' % (rand_num - 3, rand_num - 1)} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=metadata) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data[rand_num - 3: rand_num]) @decorators.idempotent_id('11b4515b-7ba7-4ca8-8838-357ded86fc10') def test_get_object_with_x_object_manifest(self): # get object with x_object_manifest # uploading segments object_name, data_segments = self._upload_segments() # creating a manifest file object_prefix = '%s/%s' % (self.container_name, object_name) metadata = {'X-Object-Manifest': object_prefix} data_empty = '' resp, body = self.object_client.create_object( self.container_name, object_name, data_empty, metadata=metadata) resp, body = self.object_client.get_object( self.container_name, object_name, metadata=None) # Check only the existence of common headers with custom matcher self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders( 'Object', 'GET')) self.assertIn('x-object-manifest', resp) # Etag value of a large object is enclosed in double-quotations. # This is a special case, therefore the formats of response headers # are checked without a custom matcher. self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) self.assertTrue(resp['etag'].strip('\"').isalnum()) self.assertTrue(re.match(r"^\d+\.?\d*\Z", resp['x-timestamp'])) self.assertNotEmpty(resp['content-type']) self.assertTrue(re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", resp['x-trans-id'])) self.assertNotEmpty(resp['date']) self.assertEqual(resp['accept-ranges'], 'bytes') self.assertEqual(resp['x-object-manifest'], '%s/%s' % (self.container_name, object_name)) self.assertEqual(''.join(data_segments), body.decode()) @decorators.idempotent_id('c05b4013-e4de-47af-be84-e598062b16fc') def test_get_object_with_if_match(self): # get object with if_match object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes(10) create_md5 = hashlib.md5(data).hexdigest() create_metadata = {'Etag': create_md5} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) list_metadata = {'If-Match': create_md5} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('be133639-e5d2-4313-9b1f-2d59fc054a16') def test_get_object_with_if_modified_since(self): # get object with if_modified_since object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() time_now = time.time() self.object_client.create_object(self.container_name, object_name, data, metadata=None) http_date = time.ctime(time_now - 86400) list_metadata = {'If-Modified-Since': http_date} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('641500d5-1612-4042-a04d-01fc4528bc30') def test_get_object_with_if_none_match(self): # get object with if_none_match object_name = data_utils.rand_name(name='TestObject') data = data_utils.random_bytes() create_md5 = hashlib.md5(data).hexdigest() create_metadata = {'Etag': create_md5} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) list_data = data_utils.random_bytes() list_md5 = hashlib.md5(list_data).hexdigest() list_metadata = {'If-None-Match': list_md5} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('0aa1201c-10aa-467a-bee7-63cbdd463152') def test_get_object_with_if_unmodified_since(self): # get object with if_unmodified_since object_name, data = self.create_object(self.container_name) time_now = time.time() http_date = time.ctime(time_now + 86400) list_metadata = {'If-Unmodified-Since': http_date} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('94587078-475f-48f9-a40f-389c246e31cd') def test_get_object_with_x_newest(self): # get object with x_newest object_name, data = self.create_object(self.container_name) list_metadata = {'X-Newest': 'true'} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('1a9ab572-1b66-4981-8c21-416e2a5e6011') def test_copy_object_in_same_container(self): # create source object src_object_name = data_utils.rand_name(name='SrcObject') src_data = data_utils.random_bytes(size=len(src_object_name) * 2) resp, _ = self.object_client.create_object(self.container_name, src_object_name, src_data) # create destination object dst_object_name = data_utils.rand_name(name='DstObject') dst_data = data_utils.random_bytes(size=len(dst_object_name) * 3) resp, _ = self.object_client.create_object(self.container_name, dst_object_name, dst_data) # copy source object to destination headers = {} headers['X-Copy-From'] = "%s/%s" % (str(self.container_name), str(src_object_name)) resp, body = self.object_client.create_object(self.container_name, dst_object_name, data=None, headers=headers) self.assertHeaders(resp, 'Object', 'PUT') # check data resp, body = self.object_client.get_object(self.container_name, dst_object_name) self.assertEqual(body, src_data) @decorators.idempotent_id('2248abba-415d-410b-9c30-22dff9cd6e67') def test_copy_object_to_itself(self): # change the content type of an existing object # create object object_name, _ = self.create_object(self.container_name) # get the old content type resp_tmp, _ = self.object_client.list_object_metadata( self.container_name, object_name) # change the content type of the object metadata = {'content-type': 'text/plain; charset=UTF-8'} self.assertNotEqual(resp_tmp['content-type'], metadata['content-type']) headers = {} headers['X-Copy-From'] = "%s/%s" % (str(self.container_name), str(object_name)) resp, body = self.object_client.create_object(self.container_name, object_name, data=None, metadata=metadata, headers=headers) self.assertHeaders(resp, 'Object', 'PUT') # check the content type resp, _ = self.object_client.list_object_metadata(self.container_name, object_name) self.assertEqual(resp['content-type'], metadata['content-type']) @decorators.idempotent_id('06f90388-2d0e-40aa-934c-e9a8833e958a') def test_copy_object_2d_way(self): # create source object src_object_name = data_utils.rand_name(name='SrcObject') src_data = data_utils.random_bytes(size=len(src_object_name) * 2) resp, _ = self.object_client.create_object(self.container_name, src_object_name, src_data) # create destination object dst_object_name = data_utils.rand_name(name='DstObject') dst_data = data_utils.random_bytes(size=len(dst_object_name) * 3) resp, _ = self.object_client.create_object(self.container_name, dst_object_name, dst_data) # copy source object to destination resp, _ = self.object_client.copy_object_2d_way(self.container_name, src_object_name, dst_object_name) self.assertHeaders(resp, 'Object', 'COPY') self.assertEqual( resp['x-copied-from'], self.container_name + "/" + src_object_name) # check data self._check_copied_obj(dst_object_name, src_data) @decorators.idempotent_id('aa467252-44f3-472a-b5ae-5b57c3c9c147') def test_copy_object_across_containers(self): # create a container to use as a source container src_container_name = data_utils.rand_name(name='TestSourceContainer') self.container_client.update_container(src_container_name) self.containers.append(src_container_name) # create a container to use as a destination container dst_container_name = data_utils.rand_name( name='TestDestinationContainer') self.container_client.update_container(dst_container_name) self.containers.append(dst_container_name) # create object in source container object_name = data_utils.rand_name(name='Object') data = data_utils.random_bytes(size=len(object_name) * 2) resp, _ = self.object_client.create_object(src_container_name, object_name, data) # set object metadata meta_key = data_utils.rand_name(name='test') meta_value = data_utils.rand_name(name='MetaValue') orig_metadata = {'X-Object-Meta-' + meta_key: meta_value} resp, _ = self.object_client.create_or_update_object_metadata( src_container_name, object_name, headers=orig_metadata) self.assertHeaders(resp, 'Object', 'POST') # copy object from source container to destination container headers = {} headers['X-Copy-From'] = "%s/%s" % (str(src_container_name), str(object_name)) resp, body = self.object_client.create_object(dst_container_name, object_name, data=None, headers=headers) self.assertHeaders(resp, 'Object', 'PUT') # check if object is present in destination container resp, body = self.object_client.get_object(dst_container_name, object_name) self.assertEqual(body, data) actual_meta_key = 'x-object-meta-' + meta_key self.assertIn(actual_meta_key, resp) self.assertEqual(resp[actual_meta_key], meta_value) @decorators.idempotent_id('5a9e2cc6-85b6-46fc-916d-0cbb7a88e5fd') def test_copy_object_with_x_fresh_metadata(self): # create source object metadata = {'x-object-meta-src': 'src_value'} src_object_name, data = self.create_object(self.container_name, metadata=metadata) # copy source object with x_fresh_metadata header metadata = {'X-Fresh-Metadata': 'true'} dst_object_name, resp = self._copy_object_2d(src_object_name, metadata) self.assertHeaders(resp, 'Object', 'COPY') self.assertNotIn('x-object-meta-src', resp) self.assertEqual(resp['x-copied-from'], self.container_name + "/" + src_object_name) # check that destination object does NOT have any object-meta self._check_copied_obj(dst_object_name, data, not_in_meta=["src"]) @decorators.idempotent_id('a28a8b99-e701-4d7e-9d84-3b66f121460b') def test_copy_object_with_x_object_metakey(self): # create source object metadata = {'x-object-meta-src': 'src_value'} src_obj_name, data = self.create_object(self.container_name, metadata=metadata) # copy source object to destination with x-object-meta-key metadata = {'x-object-meta-test': ''} dst_obj_name, resp = self._copy_object_2d(src_obj_name, metadata) self.assertHeaders(resp, 'Object', 'COPY') expected = {'x-object-meta-test': '', 'x-object-meta-src': 'src_value', 'x-copied-from': self.container_name + "/" + src_obj_name} for key, value in expected.items(): self.assertIn(key, resp) self.assertEqual(value, resp[key]) # check destination object self._check_copied_obj(dst_obj_name, data, in_meta=["test", "src"]) @decorators.idempotent_id('edabedca-24c3-4322-9b70-d6d9f942a074') def test_copy_object_with_x_object_meta(self): # create source object metadata = {'x-object-meta-src': 'src_value'} src_obj_name, data = self.create_object(self.container_name, metadata=metadata) # copy source object to destination with object metadata metadata = {'x-object-meta-test': 'value'} dst_obj_name, resp = self._copy_object_2d(src_obj_name, metadata) self.assertHeaders(resp, 'Object', 'COPY') expected = {'x-object-meta-test': 'value', 'x-object-meta-src': 'src_value', 'x-copied-from': self.container_name + "/" + src_obj_name} for key, value in expected.items(): self.assertIn(key, resp) self.assertEqual(value, resp[key]) # check destination object self._check_copied_obj(dst_obj_name, data, in_meta=["test", "src"]) @decorators.idempotent_id('e3e6a64a-9f50-4955-b987-6ce6767c97fb') def test_object_upload_in_segments(self): # create object object_name = data_utils.rand_name(name='LObject') data = data_utils.arbitrary_string() segments = 10 data_segments = [data + str(i) for i in range(segments)] # uploading segments for i in range(segments): obj_name = "%s/%s" % (object_name, i) resp, _ = self.object_client.create_object( self.container_name, obj_name, data_segments[i]) # creating a manifest file metadata = {'X-Object-Manifest': '%s/%s/' % (self.container_name, object_name)} resp, _ = self.object_client.create_object(self.container_name, object_name, data='') self.assertHeaders(resp, 'Object', 'PUT') resp, _ = self.object_client.create_or_update_object_metadata( self.container_name, object_name, headers=metadata) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) # Etag value of a large object is enclosed in double-quotations. # After etag quotes are checked they are removed and the response is # checked if all common headers are present and well formatted self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) resp['etag'] = resp['etag'].strip('"') self.assertHeaders(resp, 'Object', 'HEAD') self.assertIn('x-object-manifest', resp) self.assertEqual(resp['x-object-manifest'], '%s/%s/' % (self.container_name, object_name)) # downloading the object resp, body = self.object_client.get_object( self.container_name, object_name) self.assertEqual(''.join(data_segments), body.decode()) @decorators.idempotent_id('50d01f12-526f-4360-9ac2-75dd508d7b68') def test_get_object_if_different(self): # http://en.wikipedia.org/wiki/HTTP_ETag # Make a conditional request for an object using the If-None-Match # header, it should get downloaded only if the local file is different, # otherwise the response code should be 304 Not Modified object_name, data = self.create_object(self.container_name) # local copy is identical, no download md5 = hashlib.md5(data).hexdigest() headers = {'If-None-Match': md5} url = "%s/%s" % (self.container_name, object_name) resp, _ = self.object_client.get(url, headers=headers) self.assertEqual(resp['status'], '304') # When the file is not downloaded from Swift server, response does # not contain 'X-Timestamp' header. This is the special case, therefore # the existence of response headers is checked without custom matcher. self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) # local copy is different, download local_data = "something different" md5 = hashlib.md5(local_data.encode()).hexdigest() headers = {'If-None-Match': md5} resp, _ = self.object_client.get(url, headers=headers) self.assertHeaders(resp, 'Object', 'GET') class PublicObjectTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] @classmethod def setup_credentials(cls): super(PublicObjectTest, cls).setup_credentials() cls.os_alt = cls.os_roles_operator_alt @classmethod def setup_clients(cls): super(PublicObjectTest, cls).setup_clients() cls.object_client_alt = cls.os_alt.object_client def setUp(self): super(PublicObjectTest, self).setUp() self.container_name = data_utils.rand_name(name='TestContainer') self.container_client.update_container(self.container_name) def tearDown(self): self.delete_containers([self.container_name]) super(PublicObjectTest, self).tearDown() @decorators.idempotent_id('07c9cf95-c0d4-4b49-b9c8-0ef2c9b27193') def test_access_public_container_object_without_using_creds(self): # make container public-readable and access an object in it object # anonymously, without using credentials # update container metadata to make it publicly readable cont_headers = {'X-Container-Read': '.r:*,.rlistings'} resp_meta, body = ( self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') data = data_utils.random_bytes(size=len(object_name)) resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertHeaders(resp, 'Object', 'PUT') # list container metadata resp_meta, _ = self.container_client.list_container_metadata( self.container_name) self.assertHeaders(resp_meta, 'Container', 'HEAD') self.assertIn('x-container-read', resp_meta) self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings') # trying to get object with empty headers as it is public readable self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) resp, body = self.object_client.get_object( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @decorators.idempotent_id('54e2a2fe-42dc-491b-8270-8e4217dd4cdc') def test_access_public_object_with_another_user_creds(self): # make container public-readable and access an object in it using # another user's credentials cont_headers = {'X-Container-Read': '.r:*,.rlistings'} resp_meta, body = ( self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=cont_headers, create_update_metadata_prefix='')) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') data = data_utils.random_bytes(size=len(object_name)) resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertHeaders(resp, 'Object', 'PUT') # list container metadata resp, _ = self.container_client.list_container_metadata( self.container_name) self.assertHeaders(resp, 'Container', 'HEAD') self.assertIn('x-container-read', resp) self.assertEqual(resp['x-container-read'], '.r:*,.rlistings') # get auth token of alternative user alt_auth_data = self.object_client_alt.auth_provider.auth_data self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=alt_auth_data ) # access object using alternate user creds resp, body = self.object_client.get_object( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) tempest-17.2.0/tempest/api/object_storage/__init__.py0000666000175100017510000000000013207044712022633 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/object_storage/test_container_sync.py0000666000175100017510000001375213207044712025173 0ustar zuulzuul00000000000000# 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 time from six.moves.urllib import parse as urlparse import testtools from tempest.api.object_storage import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF # This test can be quite long to run due to its # dependency on container-sync process running interval. # You can obviously reduce the container-sync interval in the # container-server configuration. class ContainerSyncTest(base.BaseObjectTest): clients = {} credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] @classmethod def setup_credentials(cls): super(ContainerSyncTest, cls).setup_credentials() cls.os_alt = cls.os_roles_operator_alt @classmethod def setup_clients(cls): super(ContainerSyncTest, cls).setup_clients() cls.object_client_alt = cls.os_alt.object_client cls.container_client_alt = cls.os_alt.container_client @classmethod def resource_setup(cls): super(ContainerSyncTest, cls).resource_setup() cls.containers = [] cls.objects = [] # Default container-server config only allows localhost cls.local_ip = '127.0.0.1' # Must be configure according to container-sync interval container_sync_timeout = CONF.object_storage.container_sync_timeout cls.container_sync_interval = \ CONF.object_storage.container_sync_interval cls.attempts = \ int(container_sync_timeout / cls.container_sync_interval) # define container and object clients cls.clients[data_utils.rand_name(name='TestContainerSync')] = \ (cls.container_client, cls.object_client) cls.clients[data_utils.rand_name(name='TestContainerSync')] = \ (cls.container_client_alt, cls.object_client_alt) for cont_name, client in cls.clients.items(): client[0].create_container(cont_name) cls.containers.append(cont_name) @classmethod def resource_cleanup(cls): for client in cls.clients.values(): cls.delete_containers(client[0], client[1]) super(ContainerSyncTest, cls).resource_cleanup() def _test_container_synchronization(self, make_headers): # container to container synchronization # to allow/accept sync requests to/from other accounts # turn container synchronization on and create object in container for cont in (self.containers, self.containers[::-1]): cont_client = [self.clients[c][0] for c in cont] obj_client = [self.clients[c][1] for c in cont] headers = make_headers(cont[1], cont_client[1]) cont_client[0].put(str(cont[0]), body=None, headers=headers) # create object in container object_name = data_utils.rand_name(name='TestSyncObject') data = object_name[::-1].encode() # Raw data, we need bytes resp, _ = obj_client[0].create_object(cont[0], object_name, data) self.objects.append(object_name) # wait until container contents list is not empty cont_client = [self.clients[c][0] for c in self.containers] params = {'format': 'json'} while self.attempts > 0: object_lists = [] for c_client, cont in zip(cont_client, self.containers): resp, object_list = c_client.list_container_objects( cont, params=params) object_lists.append(dict( (obj['name'], obj) for obj in object_list)) # check that containers are not empty and have equal keys() # or wait for next attempt if object_lists[0] and object_lists[1] and \ set(object_lists[0].keys()) == set(object_lists[1].keys()): break else: time.sleep(self.container_sync_interval) self.attempts -= 1 self.assertEqual(object_lists[0], object_lists[1], 'Different object lists in containers.') # Verify object content obj_clients = [(self.clients[c][1], c) for c in self.containers] for obj_client, cont in obj_clients: for obj_name in object_lists[0]: resp, object_content = obj_client.get_object(cont, obj_name) self.assertEqual(object_content, obj_name[::-1].encode()) @decorators.attr(type='slow') @decorators.skip_because(bug='1317133') @decorators.idempotent_id('be008325-1bba-4925-b7dd-93b58f22ce9b') @testtools.skipIf( not CONF.object_storage_feature_enabled.container_sync, 'Old-style container sync function is disabled') def test_container_synchronization(self): def make_headers(cont, cont_client): # tell first container to synchronize to a second client_proxy_ip = \ urlparse.urlparse(cont_client.base_url).netloc.split(':')[0] client_base_url = \ cont_client.base_url.replace(client_proxy_ip, self.local_ip) headers = {'X-Container-Sync-Key': 'sync_key', 'X-Container-Sync-To': "%s/%s" % (client_base_url, str(cont))} return headers self._test_container_synchronization(make_headers) tempest-17.2.0/tempest/api/object_storage/base.py0000666000175100017510000001265313207044712022027 0ustar zuulzuul00000000000000# 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 time from tempest.common import custom_matchers from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import exceptions as lib_exc import tempest.test CONF = config.CONF def delete_containers(containers, container_client, object_client): """Remove containers and all objects in them. The containers should be visible from the container_client given. Will not throw any error if the containers don't exist. Will not check that object and container deletions succeed. After delete all the objects from a container, it will wait 2 seconds before delete the container itself, in order to deployments using HA proxy sync the deletion properly, otherwise, the container might fail to be deleted because it's not empty. :param containers: List of containers to be deleted :param container_client: Client to be used to delete containers :param object_client: Client to be used to delete objects """ for cont in containers: try: params = {'limit': 9999, 'format': 'json'} _, objlist = container_client.list_container_objects(cont, params) # delete every object in the container for obj in objlist: test_utils.call_and_ignore_notfound_exc( object_client.delete_object, cont, obj['name']) # sleep 2 seconds to sync the deletion of the objects # in HA deployment time.sleep(2) container_client.delete_container(cont) except lib_exc.NotFound: pass class BaseObjectTest(tempest.test.BaseTestCase): credentials = [['operator', CONF.object_storage.operator_role]] @classmethod def skip_checks(cls): super(BaseObjectTest, cls).skip_checks() if not CONF.service_available.swift: skip_msg = ("%s skipped as swift is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_credentials(cls): cls.set_network_resources() super(BaseObjectTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(BaseObjectTest, cls).setup_clients() cls.object_client = cls.os_roles_operator.object_client cls.bulk_client = cls.os_roles_operator.bulk_client cls.container_client = cls.os_roles_operator.container_client cls.account_client = cls.os_roles_operator.account_client cls.capabilities_client = cls.os_roles_operator.capabilities_client @classmethod def resource_setup(cls): super(BaseObjectTest, cls).resource_setup() # Make sure we get fresh auth data after assigning swift role cls.object_client.auth_provider.clear_auth() cls.container_client.auth_provider.clear_auth() cls.account_client.auth_provider.clear_auth() # make sure that discoverability is enabled and that the sections # have not been disallowed by Swift cls.policies = None if CONF.object_storage_feature_enabled.discoverability: body = cls.capabilities_client.list_capabilities() if 'swift' in body and 'policies' in body['swift']: cls.policies = body['swift']['policies'] cls.containers = [] @classmethod def create_container(cls): # wrapper that returns a test container container_name = data_utils.rand_name(name='TestContainer') cls.container_client.update_container(container_name) cls.containers.append(container_name) return container_name @classmethod def create_object(cls, container_name, object_name=None, data=None, metadata=None): # wrapper that returns a test object if object_name is None: object_name = data_utils.rand_name(name='TestObject') if data is None: data = data_utils.random_bytes() cls.object_client.create_object(container_name, object_name, data, metadata=metadata) return object_name, data @classmethod def delete_containers(cls, container_client=None, object_client=None): if container_client is None: container_client = cls.container_client if object_client is None: object_client = cls.object_client delete_containers(cls.containers, container_client, object_client) def assertHeaders(self, resp, target, method): """Check the existence and the format of response headers""" self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders( target, method, self.policies)) self.assertThat(resp, custom_matchers.AreAllWellFormatted()) tempest-17.2.0/tempest/api/object_storage/test_object_temp_url_negative.py0000666000175100017510000000750713207044712027215 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 hashlib import hmac import time from six.moves.urllib import parse as urlparse from tempest.api.object_storage import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ObjectTempUrlNegativeTest(base.BaseObjectTest): metadata = {} containers = [] @classmethod def resource_setup(cls): super(ObjectTempUrlNegativeTest, cls).resource_setup() cls.container_name = cls.create_container() # update account metadata cls.key = 'Meta' cls.metadata = {'Temp-URL-Key': cls.key} cls.account_client.create_update_or_delete_account_metadata( create_update_metadata=cls.metadata) cls.account_client_metadata, _ = \ cls.account_client.list_account_metadata() @classmethod def resource_cleanup(cls): cls.account_client.create_update_or_delete_account_metadata( delete_metadata=cls.metadata) cls.delete_containers() super(ObjectTempUrlNegativeTest, cls).resource_cleanup() def setUp(self): super(ObjectTempUrlNegativeTest, self).setUp() # make sure the metadata has been set self.assertIn('x-account-meta-temp-url-key', self.account_client_metadata) self.assertEqual( self.account_client_metadata['x-account-meta-temp-url-key'], self.key) # create object self.object_name = data_utils.rand_name(name='ObjectTemp') content = data_utils.arbitrary_string(size=len(self.object_name), base_text=self.object_name) self.object_client.create_object(self.container_name, self.object_name, content) def _get_expiry_date(self, expiration_time=1000): return int(time.time() + expiration_time) def _get_temp_url(self, container, object_name, method, expires, key): """Create the temporary URL.""" path = "%s/%s/%s" % ( urlparse.urlparse(self.object_client.base_url).path, container, object_name) hmac_body = '%s\n%s\n%s' % (method, expires, path) sig = hmac.new( key.encode(), hmac_body.encode(), hashlib.sha1 ).hexdigest() url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container, object_name, sig, expires) return url @decorators.attr(type=['negative']) @decorators.idempotent_id('5a583aca-c804-41ba-9d9a-e7be132bdf0b') @utils.requires_ext(extension='tempurl', service='object') def test_get_object_after_expiration_time(self): expires = self._get_expiry_date(1) # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) # temp URL is valid for 1 seconds, let's wait 2 time.sleep(2) self.assertRaises(lib_exc.Unauthorized, self.object_client.get, url) tempest-17.2.0/tempest/api/object_storage/test_object_temp_url.py0000666000175100017510000001610413207044712025324 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 hashlib import hmac import time from six.moves.urllib import parse as urlparse from tempest.api.object_storage import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ObjectTempUrlTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(ObjectTempUrlTest, cls).resource_setup() # create a container cls.container_name = cls.create_container() # update account metadata cls.key = 'Meta' cls.metadatas = [] metadata = {'Temp-URL-Key': cls.key} cls.metadatas.append(metadata) cls.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata) # create an object cls.object_name, cls.content = cls.create_object(cls.container_name) @classmethod def resource_cleanup(cls): for metadata in cls.metadatas: cls.account_client.create_update_or_delete_account_metadata( delete_metadata=metadata) cls.delete_containers() super(ObjectTempUrlTest, cls).resource_cleanup() def setUp(self): super(ObjectTempUrlTest, self).setUp() # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key'], self.key) def _get_expiry_date(self, expiration_time=1000): return int(time.time() + expiration_time) def _get_temp_url(self, container, object_name, method, expires, key): """Create the temporary URL.""" path = "%s/%s/%s" % ( urlparse.urlparse(self.object_client.base_url).path, container, object_name) hmac_body = '%s\n%s\n%s' % (method, expires, path) sig = hmac.new( key.encode(), hmac_body.encode(), hashlib.sha1 ).hexdigest() url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container, object_name, sig, expires) return url @decorators.idempotent_id('f91c96d4-1230-4bba-8eb9-84476d18d991') @utils.requires_ext(extension='tempurl', service='object') def test_get_object_using_temp_url(self): expires = self._get_expiry_date() # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) # trying to get object using temp url within expiry time resp, body = self.object_client.get(url) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, self.content) # Testing a HEAD on this Temp URL resp, _ = self.object_client.head(url) self.assertHeaders(resp, 'Object', 'HEAD') @decorators.idempotent_id('671f9583-86bd-4128-a034-be282a68c5d8') @utils.requires_ext(extension='tempurl', service='object') def test_get_object_using_temp_url_key_2(self): key2 = 'Meta2-' metadata = {'Temp-URL-Key-2': key2} self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata) self.metadatas.append(metadata) # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key-2', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key-2'], key2) expires = self._get_expiry_date() url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, key2) _, body = self.object_client.get(url) self.assertEqual(body, self.content) @decorators.idempotent_id('9b08dade-3571-4152-8a4f-a4f2a873a735') @utils.requires_ext(extension='tempurl', service='object') def test_put_object_using_temp_url(self): new_data = data_utils.random_bytes(size=len(self.object_name)) expires = self._get_expiry_date() url = self._get_temp_url(self.container_name, self.object_name, "PUT", expires, self.key) # trying to put random data in the object using temp url resp, _ = self.object_client.put(url, new_data, None) self.assertHeaders(resp, 'Object', 'PUT') # Testing a HEAD on this Temp URL resp, _ = self.object_client.head(url) self.assertHeaders(resp, 'Object', 'HEAD') # Validate that the content of the object has been modified url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) _, body = self.object_client.get(url) self.assertEqual(body, new_data) @decorators.idempotent_id('249a0111-5ad3-4534-86a7-1993d55f9185') @utils.requires_ext(extension='tempurl', service='object') def test_head_object_using_temp_url(self): expires = self._get_expiry_date() # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "HEAD", expires, self.key) # Testing a HEAD on this Temp URL resp, _ = self.object_client.head(url) self.assertHeaders(resp, 'Object', 'HEAD') @decorators.idempotent_id('9d9cfd90-708b-465d-802c-e4a8090b823d') @utils.requires_ext(extension='tempurl', service='object') def test_get_object_using_temp_url_with_inline_query_parameter(self): expires = self._get_expiry_date() # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) url = url + '&inline' # trying to get object using temp url within expiry time resp, body = self.object_client.get(url) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, self.content) self.assertEqual(resp['content-disposition'], 'inline') tempest-17.2.0/tempest/api/object_storage/test_account_services.py0000666000175100017510000004102213207044712025503 0ustar zuulzuul00000000000000# 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 random import six import testtools from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class AccountTest(base.BaseObjectTest): credentials = [['operator', CONF.object_storage.operator_role], ['operator_alt', CONF.object_storage.operator_role]] containers = [] @classmethod def setup_credentials(cls): super(AccountTest, cls).setup_credentials() cls.os_operator = cls.os_roles_operator_alt @classmethod def resource_setup(cls): super(AccountTest, cls).resource_setup() for i in range(ord('a'), ord('f') + 1): name = data_utils.rand_name(name='%s-' % six.int2byte(i)) cls.container_client.update_container(name) cls.containers.append(name) cls.containers_count = len(cls.containers) @classmethod def resource_cleanup(cls): cls.delete_containers() super(AccountTest, cls).resource_cleanup() @decorators.attr(type='smoke') @decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f') def test_list_containers(self): # list of all containers should not be empty resp, container_list = self.account_client.list_account_containers() self.assertHeaders(resp, 'Account', 'GET') self.assertIsNotNone(container_list) for container_name in self.containers: self.assertIn(six.text_type(container_name).encode('utf-8'), container_list) @decorators.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565') def test_list_no_containers(self): # List request to empty account # To test listing no containers, create new user other than # the base user of this instance. resp, container_list = \ self.os_operator.account_client.list_account_containers() # When sending a request to an account which has not received a PUT # container request, the response does not contain 'accept-ranges' # header. This is a special case, therefore the existence of response # headers is checked without custom matcher. # # As the expected response is 204 No Content, Content-Length presence # is not checked here intentionally. According to RFC 7230 a server # MUST NOT send the header in such responses. Thus, clients should not # depend on this header. However, the standard does not require them # to validate the server's behavior. We leverage that to not refuse # any implementation violating it like Swift [1] or some versions of # Ceph RadosGW [2]. # [1] https://bugs.launchpad.net/swift/+bug/1537811 # [2] http://tracker.ceph.com/issues/13582 self.assertIn('x-timestamp', resp) self.assertIn('x-account-bytes-used', resp) self.assertIn('x-account-container-count', resp) self.assertIn('x-account-object-count', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) self.assertEmpty(container_list) @decorators.idempotent_id('1c7efa35-e8a2-4b0b-b5ff-862c7fd83704') def test_list_containers_with_format_json(self): # list containers setting format parameter to 'json' params = {'format': 'json'} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertIsNotNone(container_list) self.assertTrue([c['name'] for c in container_list]) self.assertTrue([c['count'] for c in container_list]) self.assertTrue([c['bytes'] for c in container_list]) @decorators.idempotent_id('4477b609-1ca6-4d4b-b25d-ad3f01086089') def test_list_containers_with_format_xml(self): # list containers setting format parameter to 'xml' params = {'format': 'xml'} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertIsNotNone(container_list) self.assertEqual(container_list.tag, 'account') self.assertIn('name', container_list.keys()) self.assertEqual(container_list.find(".//container").tag, 'container') self.assertEqual(container_list.find(".//name").tag, 'name') self.assertEqual(container_list.find(".//count").tag, 'count') self.assertEqual(container_list.find(".//bytes").tag, 'bytes') @decorators.idempotent_id('6eb04a6a-4860-4e31-ba91-ea3347d76b58') @testtools.skipIf( not CONF.object_storage_feature_enabled.discoverability, 'Discoverability function is disabled') def test_list_extensions(self): resp = self.capabilities_client.list_capabilities() self.assertThat(resp, custom_matchers.AreAllWellFormatted()) @decorators.idempotent_id('5cfa4ab2-4373-48dd-a41f-a532b12b08b2') def test_list_containers_with_limit(self): # list containers one of them, half of them then all of them for limit in (1, self.containers_count // 2, self.containers_count): params = {'limit': limit} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), limit) @decorators.idempotent_id('638f876d-6a43-482a-bbb3-0840bca101c6') def test_list_containers_with_marker(self): # list containers using marker param # first expect to get 0 container as we specified last # the container as marker # second expect to get the bottom half of the containers params = {'marker': self.containers[-1]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEmpty(container_list) params = {'marker': self.containers[self.containers_count // 2]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), self.containers_count // 2 - 1) @decorators.idempotent_id('5ca164e4-7bde-43fa-bafb-913b53b9e786') def test_list_containers_with_end_marker(self): # list containers using end_marker param # first expect to get 0 container as we specified first container as # end_marker # second expect to get the top half of the containers params = {'end_marker': self.containers[0]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEmpty(container_list) params = {'end_marker': self.containers[self.containers_count // 2]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), self.containers_count // 2) @decorators.idempotent_id('ac8502c2-d4e4-4f68-85a6-40befea2ef5e') def test_list_containers_with_marker_and_end_marker(self): # list containers combining marker and end_marker param params = {'marker': self.containers[0], 'end_marker': self.containers[self.containers_count - 1]} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), self.containers_count - 2) @decorators.idempotent_id('f7064ae8-dbcc-48da-b594-82feef6ea5af') def test_list_containers_with_limit_and_marker(self): # list containers combining marker and limit param # result are always limitated by the limit whatever the marker for marker in random.choice(self.containers): limit = random.randint(0, self.containers_count - 1) params = {'marker': marker, 'limit': limit} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertLessEqual(len(container_list), limit, str(container_list)) @decorators.idempotent_id('888a3f0e-7214-4806-8e50-5e0c9a69bb5e') def test_list_containers_with_limit_and_end_marker(self): # list containers combining limit and end_marker param limit = random.randint(1, self.containers_count) params = {'limit': limit, 'end_marker': self.containers[self.containers_count // 2]} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), min(limit, self.containers_count // 2)) @decorators.idempotent_id('8cf98d9c-e3a0-4e44-971b-c87656fdddbd') def test_list_containers_with_limit_and_marker_and_end_marker(self): # list containers combining limit, marker and end_marker param limit = random.randint(1, self.containers_count) params = {'limit': limit, 'marker': self.containers[0], 'end_marker': self.containers[self.containers_count - 1]} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), min(limit, self.containers_count - 2)) @decorators.idempotent_id('365e6fc7-1cfe-463b-a37c-8bd08d47b6aa') def test_list_containers_with_prefix(self): # list containers that have a name that starts with a prefix prefix = '{0}-a'.format(CONF.resources_prefix) params = {'prefix': prefix} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') for container in container_list: self.assertEqual(True, container.decode( 'utf-8').startswith(prefix)) @decorators.idempotent_id('b1811cff-d1ed-4c15-a52e-efd8de41cf34') def test_list_containers_reverse_order(self): # list containers in reverse order _, orig_container_list = self.account_client.list_account_containers() params = {'reverse': True} resp, container_list = self.account_client.list_account_containers( params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(sorted(orig_container_list, reverse=True), container_list) @decorators.attr(type='smoke') @decorators.idempotent_id('4894c312-6056-4587-8d6f-86ffbf861f80') def test_list_account_metadata(self): # list all account metadata # set metadata to account metadata = {'test-account-meta1': 'Meta1', 'test-account-meta2': 'Meta2'} resp, _ = self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata) resp, _ = self.account_client.list_account_metadata() self.assertHeaders(resp, 'Account', 'HEAD') self.assertIn('x-account-meta-test-account-meta1', resp) self.assertIn('x-account-meta-test-account-meta2', resp) self.account_client.create_update_or_delete_account_metadata( delete_metadata=metadata) @decorators.idempotent_id('b904c2e3-24c2-4dba-ad7d-04e90a761be5') def test_list_no_account_metadata(self): # list no account metadata resp, _ = self.account_client.list_account_metadata() self.assertHeaders(resp, 'Account', 'HEAD') self.assertNotIn('x-account-meta-', str(resp)) @decorators.idempotent_id('e2a08b5f-3115-4768-a3ee-d4287acd6c08') def test_update_account_metadata_with_create_metadata(self): # add metadata to account metadata = {'test-account-meta1': 'Meta1'} resp, _ = self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertIn('x-account-meta-test-account-meta1', resp) self.assertEqual(resp['x-account-meta-test-account-meta1'], metadata['test-account-meta1']) self.account_client.create_update_or_delete_account_metadata( delete_metadata=metadata) @decorators.idempotent_id('9f60348d-c46f-4465-ae06-d51dbd470953') def test_update_account_metadata_with_delete_metadata(self): # delete metadata from account metadata = {'test-account-meta1': 'Meta1'} self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata) resp, _ = self.account_client.create_update_or_delete_account_metadata( delete_metadata=metadata) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) @decorators.idempotent_id('64fd53f3-adbd-4639-af54-436e4982dbfb') def test_update_account_metadata_with_create_metadata_key(self): # if the value of metadata is not set, the metadata is not # registered at a server metadata = {'test-account-meta1': ''} resp, _ = self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) @decorators.idempotent_id('d4d884d3-4696-4b85-bc98-4f57c4dd2bf1') def test_update_account_metadata_with_delete_metadata_key(self): # Although the value of metadata is not set, the feature of # deleting metadata is valid metadata_1 = {'test-account-meta1': 'Meta1'} self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata_1) metadata_2 = {'test-account-meta1': ''} resp, _ = self.account_client.create_update_or_delete_account_metadata( delete_metadata=metadata_2) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) @decorators.idempotent_id('8e5fc073-59b9-42ee-984a-29ed11b2c749') def test_update_account_metadata_with_create_and_delete_metadata(self): # Send a request adding and deleting metadata requests simultaneously metadata_1 = {'test-account-meta1': 'Meta1'} self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata_1) metadata_2 = {'test-account-meta2': 'Meta2'} resp, _ = ( self.account_client.create_update_or_delete_account_metadata( create_update_metadata=metadata_2, delete_metadata=metadata_1)) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) self.assertIn('x-account-meta-test-account-meta2', resp) self.assertEqual(resp['x-account-meta-test-account-meta2'], metadata_2['test-account-meta2']) self.account_client.create_update_or_delete_account_metadata( delete_metadata=metadata_2) tempest-17.2.0/tempest/api/object_storage/test_container_sync_middleware.py0000666000175100017510000000407113207044712027362 0ustar zuulzuul00000000000000# Copyright(c)2015 NTT 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 tempest.api.object_storage import test_container_sync from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF # This test can be quite long to run due to its # dependency on container-sync process running interval. # You can obviously reduce the container-sync interval in the # container-server configuration. class ContainerSyncMiddlewareTest(test_container_sync.ContainerSyncTest): @classmethod def resource_setup(cls): super(ContainerSyncMiddlewareTest, cls).resource_setup() # Set container-sync-realms.conf info cls.realm_name = CONF.object_storage.realm_name cls.key = 'sync_key' cls.cluster_name = CONF.object_storage.cluster_name @decorators.attr(type='slow') @decorators.idempotent_id('ea4645a1-d147-4976-82f7-e5a7a3065f80') @utils.requires_ext(extension='container_sync', service='object') def test_container_synchronization(self): def make_headers(cont, cont_client): # tell first container to synchronize to a second account_name = cont_client.base_url.split('/')[-1] headers = {'X-Container-Sync-Key': "%s" % (self.key), 'X-Container-Sync-To': "//%s/%s/%s/%s" % (self.realm_name, self.cluster_name, str(account_name), str(cont))} return headers self._test_container_synchronization(make_headers) tempest-17.2.0/tempest/api/object_storage/test_object_slo.py0000666000175100017510000001605013207044712024272 0ustar zuulzuul00000000000000# Copyright 2013 NTT 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. import hashlib from oslo_serialization import jsonutils as json from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators # Each segment, except for the final one, must be at least 1 megabyte MIN_SEGMENT_SIZE = 1024 * 1024 class ObjectSloTest(base.BaseObjectTest): def setUp(self): super(ObjectSloTest, self).setUp() self.container_name = self.create_container() self.objects = [] def tearDown(self): for obj in self.objects: test_utils.call_and_ignore_notfound_exc( self.object_client.delete_object, self.container_name, obj) self.container_client.delete_container(self.container_name) super(ObjectSloTest, self).tearDown() def _create_object(self, container_name, object_name, data, params=None): resp, _ = self.object_client.create_object(container_name, object_name, data, params) self.objects.append(object_name) return resp def _create_manifest(self): # Create a manifest file for SLO uploading object_name = data_utils.rand_name(name='TestObject') object_name_base_1 = object_name + '_01' object_name_base_2 = object_name + '_02' data_size = MIN_SEGMENT_SIZE self.content = data_utils.random_bytes(data_size) self._create_object(self.container_name, object_name_base_1, self.content) self._create_object(self.container_name, object_name_base_2, self.content) path_object_1 = '/%s/%s' % (self.container_name, object_name_base_1) path_object_2 = '/%s/%s' % (self.container_name, object_name_base_2) data_manifest = [{'path': path_object_1, 'etag': hashlib.md5(self.content).hexdigest(), 'size_bytes': data_size}, {'path': path_object_2, 'etag': hashlib.md5(self.content).hexdigest(), 'size_bytes': data_size}] return json.dumps(data_manifest) def _create_large_object(self): # Create a large object for preparation of testing various SLO # features manifest = self._create_manifest() params = {'multipart-manifest': 'put'} object_name = data_utils.rand_name(name='TestObject') self._create_object(self.container_name, object_name, manifest, params) return object_name def _assertHeadersSLO(self, resp, method): # When sending GET or HEAD requests to SLO the response contains # 'X-Static-Large-Object' header if method in ('GET', 'HEAD'): self.assertIn('x-static-large-object', resp) self.assertEqual(resp['x-static-large-object'], 'True') # Etag value of a large object is enclosed in double-quotations. # After etag quotes are checked they are removed and the response is # checked if all common headers are present and well formatted self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) resp['etag'] = resp['etag'].strip('"') self.assertHeaders(resp, 'Object', method) @decorators.idempotent_id('2c3f24a6-36e8-4711-9aa2-800ee1fc7b5b') @utils.requires_ext(extension='slo', service='object') def test_upload_manifest(self): # create static large object from multipart manifest manifest = self._create_manifest() params = {'multipart-manifest': 'put'} object_name = data_utils.rand_name(name='TestObject') resp = self._create_object(self.container_name, object_name, manifest, params) self._assertHeadersSLO(resp, 'PUT') @decorators.idempotent_id('e69ad766-e1aa-44a2-bdd2-bf62c09c1456') @utils.requires_ext(extension='slo', service='object') def test_list_large_object_metadata(self): # list static large object metadata using multipart manifest object_name = self._create_large_object() resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self._assertHeadersSLO(resp, 'HEAD') @decorators.idempotent_id('49bc49bc-dd1b-4c0f-904e-d9f10b830ee8') @utils.requires_ext(extension='slo', service='object') def test_retrieve_large_object(self): # list static large object using multipart manifest object_name = self._create_large_object() resp, body = self.object_client.get_object( self.container_name, object_name) self._assertHeadersSLO(resp, 'GET') sum_data = self.content + self.content self.assertEqual(body, sum_data) @decorators.idempotent_id('87b6dfa1-abe9-404d-8bf0-6c3751e6aa77') @utils.requires_ext(extension='slo', service='object') def test_delete_large_object(self): # delete static large object using multipart manifest object_name = self._create_large_object() params_del = {'multipart-manifest': 'delete'} resp, _ = self.object_client.delete_object( self.container_name, object_name, params=params_del) # When deleting SLO using multipart manifest, the response contains # not 'content-length' but 'transfer-encoding' header. This is the # special case, therefore the existence of response headers is checked # outside of custom matcher. self.assertIn('transfer-encoding', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) resp, body = self.container_client.list_container_objects( self.container_name) self.assertEqual(int(resp['x-container-object-count']), 0) tempest-17.2.0/tempest/api/object_storage/test_container_services.py0000666000175100017510000004247413207044712026045 0ustar zuulzuul00000000000000# 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 tempest.api.object_storage import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ContainerTest(base.BaseObjectTest): def tearDown(self): self.delete_containers() super(ContainerTest, self).tearDown() @decorators.attr(type='smoke') @decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f') def test_create_container(self): container_name = data_utils.rand_name(name='TestContainer') resp, _ = self.container_client.update_container(container_name) self.containers.append(container_name) self.assertHeaders(resp, 'Container', 'PUT') @decorators.idempotent_id('49f866ed-d6af-4395-93e7-4187eb56d322') def test_create_container_overwrite(self): # overwrite container with the same name container_name = data_utils.rand_name(name='TestContainer') self.container_client.update_container(container_name) self.containers.append(container_name) resp, _ = self.container_client.update_container(container_name) self.assertHeaders(resp, 'Container', 'PUT') @decorators.idempotent_id('c2ac4d59-d0f5-40d5-ba19-0635056d48cd') def test_create_container_with_metadata_key(self): # create container with the blank value of metadata container_name = data_utils.rand_name(name='TestContainer') headers = {'X-Container-Meta-test-container-meta': ''} resp, _ = self.container_client.update_container( container_name, **headers) self.containers.append(container_name) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) # if the value of metadata is blank, metadata is not registered # in the server self.assertNotIn('x-container-meta-test-container-meta', resp) @decorators.idempotent_id('e1e8df32-7b22-44e1-aa08-ccfd8d446b58') def test_create_container_with_metadata_value(self): # create container with metadata value container_name = data_utils.rand_name(name='TestContainer') # metadata name using underscores should be converted to hyphens headers = {'X-Container-Meta-test_container_meta': 'Meta1'} resp, _ = self.container_client.update_container( container_name, **headers) self.containers.append(container_name) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) self.assertIn('x-container-meta-test-container-meta', resp) self.assertEqual(resp['x-container-meta-test-container-meta'], headers['X-Container-Meta-test_container_meta']) @decorators.idempotent_id('24d16451-1c0c-4e4f-b59c-9840a3aba40e') def test_create_container_with_remove_metadata_key(self): # create container with the blank value of remove metadata container_name = data_utils.rand_name(name='TestContainer') headers = {'X-Container-Meta-test-container-meta': 'Meta1'} self.container_client.update_container(container_name, **headers) self.containers.append(container_name) headers = {'X-Remove-Container-Meta-test-container-meta': ''} resp, _ = self.container_client.update_container( container_name, **headers) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta', resp) @decorators.idempotent_id('8a21ebad-a5c7-4e29-b428-384edc8cd156') def test_create_container_with_remove_metadata_value(self): # create container with remove metadata container_name = data_utils.rand_name(name='TestContainer') headers = {'X-Container-Meta-test-container-meta': 'Meta1'} self.container_client.update_container(container_name, **headers) self.containers.append(container_name) headers = {'X-Remove-Container-Meta-test-container-meta': 'Meta1'} resp, _ = self.container_client.update_container( container_name, **headers) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta', resp) @decorators.idempotent_id('95d3a249-b702-4082-a2c4-14bb860cf06a') def test_delete_container(self): # create a container container_name = self.create_container() # delete container, success asserted within resp, _ = self.container_client.delete_container(container_name) self.assertHeaders(resp, 'Container', 'DELETE') @decorators.attr(type='smoke') @decorators.idempotent_id('312ff6bd-5290-497f-bda1-7c5fec6697ab') def test_list_container_contents(self): # get container contents list container_name = self.create_container() object_name, _ = self.create_object(container_name) resp, object_list = self.container_client.list_container_objects( container_name) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name], object_list) @decorators.idempotent_id('4646ac2d-9bfb-4c7d-a3c5-0f527402b3df') def test_list_container_contents_with_no_object(self): # get empty container contents list container_name = self.create_container() resp, object_list = self.container_client.list_container_objects( container_name) self.assertHeaders(resp, 'Container', 'GET') self.assertEmpty(object_list) @decorators.idempotent_id('fe323a32-57b9-4704-a996-2e68f83b09bc') def test_list_container_contents_with_delimiter(self): # get container contents list using delimiter param container_name = self.create_container() object_name = data_utils.rand_name(name='TestObject/') self.create_object(container_name, object_name) params = {'delimiter': '/'} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name.split('/')[0] + '/'], object_list) @decorators.idempotent_id('55b4fa5c-e12e-4ca9-8fcf-a79afe118522') def test_list_container_contents_with_end_marker(self): # get container contents list using end_marker param container_name = self.create_container() object_name, _ = self.create_object(container_name) params = {'end_marker': object_name + 'zzzz'} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name], object_list) @decorators.idempotent_id('196f5034-6ab0-4032-9da9-a937bbb9fba9') def test_list_container_contents_with_format_json(self): # get container contents list using format_json param container_name = self.create_container() self.create_object(container_name) params = {'format': 'json'} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertIsNotNone(object_list) self.assertTrue([c['name'] for c in object_list]) self.assertTrue([c['hash'] for c in object_list]) self.assertTrue([c['bytes'] for c in object_list]) self.assertTrue([c['content_type'] for c in object_list]) self.assertTrue([c['last_modified'] for c in object_list]) @decorators.idempotent_id('655a53ca-4d15-408c-a377-f4c6dbd0a1fa') def test_list_container_contents_with_format_xml(self): # get container contents list using format_xml param container_name = self.create_container() self.create_object(container_name) params = {'format': 'xml'} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertIsNotNone(object_list) self.assertEqual(object_list.tag, 'container') self.assertIn('name', object_list.keys()) self.assertEqual(object_list.find(".//object").tag, 'object') self.assertEqual(object_list.find(".//name").tag, 'name') self.assertEqual(object_list.find(".//hash").tag, 'hash') self.assertEqual(object_list.find(".//bytes").tag, 'bytes') self.assertEqual(object_list.find(".//content_type").tag, 'content_type') self.assertEqual(object_list.find(".//last_modified").tag, 'last_modified') @decorators.idempotent_id('297ec38b-2b61-4ff4-bcd1-7fa055e97b61') def test_list_container_contents_with_limit(self): # get container contents list using limit param container_name = self.create_container() object_name, _ = self.create_object(container_name) params = {'limit': data_utils.rand_int_id(1, 10000)} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name], object_list) @decorators.idempotent_id('c31ddc63-2a58-4f6b-b25c-94d2937e6867') def test_list_container_contents_with_marker(self): # get container contents list using marker param container_name = self.create_container() object_name, _ = self.create_object(container_name) params = {'marker': 'AaaaObject1234567890'} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name], object_list) @decorators.idempotent_id('58ca6cc9-6af0-408d-aaec-2a6a7b2f0df9') def test_list_container_contents_with_path(self): # get container contents list using path param container_name = self.create_container() object_name = data_utils.rand_name(name='TestObject') object_name = 'Swift/' + object_name self.create_object(container_name, object_name) params = {'path': 'Swift'} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name], object_list) @decorators.idempotent_id('77e742c7-caf2-4ec9-8aa4-f7d509a3344c') def test_list_container_contents_with_prefix(self): # get container contents list using prefix param container_name = self.create_container() object_name, _ = self.create_object(container_name) prefix_key = object_name[0:8] params = {'prefix': prefix_key} resp, object_list = self.container_client.list_container_objects( container_name, params=params) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual([object_name], object_list) @decorators.attr(type='smoke') @decorators.idempotent_id('96e68f0e-19ec-4aa2-86f3-adc6a45e14dd') def test_list_container_metadata(self): # List container metadata container_name = self.create_container() metadata = {'name': 'Pictures'} self.container_client.create_update_or_delete_container_metadata( container_name, create_update_metadata=metadata) resp, _ = self.container_client.list_container_metadata( container_name) self.assertHeaders(resp, 'Container', 'HEAD') self.assertIn('x-container-meta-name', resp) self.assertEqual(resp['x-container-meta-name'], metadata['name']) @decorators.idempotent_id('a2faf936-6b13-4f8d-92a2-c2278355821e') def test_list_no_container_metadata(self): # HEAD container without metadata container_name = self.create_container() resp, _ = self.container_client.list_container_metadata( container_name) self.assertHeaders(resp, 'Container', 'HEAD') self.assertNotIn('x-container-meta-', str(resp)) @decorators.idempotent_id('cf19bc0b-7e16-4a5a-aaed-cb0c2fe8deef') def test_update_container_metadata_with_create_and_delete_metadata(self): # Send one request of adding and deleting metadata container_name = data_utils.rand_name(name='TestContainer') metadata_1 = {'X-Container-Meta-test-container-meta1': 'Meta1'} self.container_client.update_container(container_name, **metadata_1) self.containers.append(container_name) metadata_2 = {'test-container-meta2': 'Meta2'} resp, _ = ( self.container_client.create_update_or_delete_container_metadata( container_name, create_update_metadata=metadata_2, delete_metadata={'test-container-meta1': 'Meta1'})) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) self.assertIn('x-container-meta-test-container-meta2', resp) self.assertEqual(resp['x-container-meta-test-container-meta2'], metadata_2['test-container-meta2']) @decorators.idempotent_id('2ae5f295-4bf1-4e04-bfad-21e54b62cec5') def test_update_container_metadata_with_create_metadata(self): # update container metadata using add metadata container_name = self.create_container() metadata = {'test-container-meta1': 'Meta1'} resp, _ = ( self.container_client.create_update_or_delete_container_metadata( container_name, create_update_metadata=metadata)) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertIn('x-container-meta-test-container-meta1', resp) self.assertEqual(resp['x-container-meta-test-container-meta1'], metadata['test-container-meta1']) @decorators.idempotent_id('3a5ce7d4-6e4b-47d0-9d87-7cd42c325094') def test_update_container_metadata_with_delete_metadata(self): # update container metadata using delete metadata container_name = data_utils.rand_name(name='TestContainer') metadata = {'X-Container-Meta-test-container-meta1': 'Meta1'} self.container_client.update_container(container_name, **metadata) self.containers.append(container_name) resp, _ = ( self.container_client.create_update_or_delete_container_metadata( container_name, delete_metadata={'test-container-meta1': 'Meta1'})) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) @decorators.idempotent_id('31f40a5f-6a52-4314-8794-cd89baed3040') def test_update_container_metadata_with_create_metadata_key(self): # update container metadata with a blank value of metadata container_name = self.create_container() metadata = {'test-container-meta1': ''} resp, _ = ( self.container_client.create_update_or_delete_container_metadata( container_name, create_update_metadata=metadata)) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) @decorators.idempotent_id('a2e36378-6f1f-43f4-840a-ffd9cfd61914') def test_update_container_metadata_with_delete_metadata_key(self): # update container metadata with a blank value of metadata container_name = data_utils.rand_name(name='TestContainer') headers = {'X-Container-Meta-test-container-meta1': 'Meta1'} self.container_client.update_container(container_name, **headers) self.containers.append(container_name) metadata = {'test-container-meta1': ''} resp, _ = ( self.container_client.create_update_or_delete_container_metadata( container_name, delete_metadata=metadata)) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata(container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) tempest-17.2.0/tempest/api/object_storage/test_container_staticweb.py0000666000175100017510000001505313207044712026200 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest.common import custom_matchers from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class StaticWebTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(StaticWebTest, cls).resource_setup() # This header should be posted on the container before every test headers_public_read_acl = {'Read': '.r:*,.rlistings'} # Create test container and create one object in it cls.container_name = cls.create_container() cls.object_name, cls.object_data = cls.create_object( cls.container_name) cls.container_client.create_update_or_delete_container_metadata( cls.container_name, create_update_metadata=headers_public_read_acl, create_update_metadata_prefix="X-Container-") @classmethod def resource_cleanup(cls): cls.delete_containers() super(StaticWebTest, cls).resource_cleanup() @decorators.idempotent_id('c1f055ab-621d-4a6a-831f-846fcb578b8b') @utils.requires_ext(extension='staticweb', service='object') def test_web_index(self): headers = {'web-index': self.object_name} self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=headers) # Maintain original headers, no auth added self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) # test GET on http://account_url/container_name # we should retrieve the self.object_name file resp, body = self.account_client.request("GET", self.container_name, headers={}) # This request is equivalent to GET object self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, self.object_data) # clean up before exiting self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata={'web-index': ""}) _, body = self.container_client.list_container_metadata( self.container_name) self.assertNotIn('x-container-meta-web-index', body) @decorators.idempotent_id('941814cf-db9e-4b21-8112-2b6d0af10ee5') @utils.requires_ext(extension='staticweb', service='object') def test_web_listing(self): headers = {'web-listings': 'true'} self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=headers) # test GET on http://account_url/container_name # we should retrieve a listing of objects resp, body = self.account_client.request("GET", self.container_name, headers={}) # The target of the request is not any Swift resource. Therefore, the # existence of response header is checked without a custom matcher. self.assertIn('content-length', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) self.assertIn(self.object_name, body.decode()) # clean up before exiting self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata={'web-listings': ""}) _, body = self.container_client.list_container_metadata( self.container_name) self.assertNotIn('x-container-meta-web-listings', body) @decorators.idempotent_id('bc37ec94-43c8-4990-842e-0e5e02fc8926') @utils.requires_ext(extension='staticweb', service='object') def test_web_listing_css(self): headers = {'web-listings': 'true', 'web-listings-css': 'listings.css'} self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=headers) # Maintain original headers, no auth added self.account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) # test GET on http://account_url/container_name # we should retrieve a listing of objects _, body = self.account_client.request("GET", self.container_name, headers={}) self.assertIn(self.object_name, body.decode()) css = '' self.assertIn(css, body.decode()) @decorators.idempotent_id('f18b4bef-212e-45e7-b3ca-59af3a465f82') @utils.requires_ext(extension='staticweb', service='object') def test_web_error(self): headers = {'web-listings': 'true', 'web-error': self.object_name} self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=headers) # Create object to return when requested object not found object_name_404 = "404" + self.object_name object_data_404 = data_utils.arbitrary_string() self.object_client.create_object(self.container_name, object_name_404, object_data_404) # Do not set auth in HTTP headers for next request self.object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) # Request non-existing object self.assertRaises( lib_exc.NotFound, self.object_client.get_object, self.container_name, "notexisting") tempest-17.2.0/tempest/api/object_storage/test_healthcheck.py0000666000175100017510000000304213207044712024407 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.object_storage import base from tempest.common import custom_matchers from tempest.lib import decorators class HealthcheckTest(base.BaseObjectTest): def setUp(self): super(HealthcheckTest, self).setUp() # Turning http://.../v1/foobar into http://.../ self.account_client.skip_path() @decorators.idempotent_id('db5723b1-f25c-49a9-bfeb-7b5640caf337') def test_get_healthcheck(self): resp, _ = self.account_client.get("healthcheck", {}) # The target of the request is not any Swift resource. Therefore, the # existence of response header is checked without a custom matcher. self.assertIn('content-length', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) tempest-17.2.0/tempest/api/object_storage/test_container_services_negative.py0000666000175100017510000002031313207044712027713 0ustar zuulzuul00000000000000# Copyright 2016 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 from tempest.api.object_storage import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class ContainerNegativeTest(base.BaseObjectTest): @classmethod def resource_setup(cls): super(ContainerNegativeTest, cls).resource_setup() if CONF.object_storage_feature_enabled.discoverability: # use /info to get default constraints body = cls.capabilities_client.list_capabilities() cls.constraints = body['swift'] @decorators.attr(type=["negative"]) @decorators.idempotent_id('30686921-4bed-4764-a038-40d741ed4e78') @testtools.skipUnless( CONF.object_storage_feature_enabled.discoverability, 'Discoverability function is disabled') def test_create_container_name_exceeds_max_length(self): # Attempts to create a container name that is longer than max max_length = self.constraints['max_container_name_length'] # create a container with long name container_name = data_utils.arbitrary_string(size=max_length + 1) ex = self.assertRaises( exceptions.BadRequest, self.container_client.update_container, container_name) self.assertIn('Container name length of ' + str(max_length + 1) + ' longer than ' + str(max_length), str(ex)) @decorators.attr(type=["negative"]) @decorators.idempotent_id('41e645bf-2e68-4f84-bf7b-c71aa5cd76ce') @testtools.skipUnless( CONF.object_storage_feature_enabled.discoverability, 'Discoverability function is disabled') def test_create_container_metadata_name_exceeds_max_length(self): # Attempts to create container with metadata name # that is longer than max. max_length = self.constraints['max_meta_name_length'] container_name = data_utils.rand_name(name='TestContainer') metadata_name = 'X-Container-Meta-' + data_utils.arbitrary_string( size=max_length + 1) metadata = {metadata_name: 'penguin'} ex = self.assertRaises( exceptions.BadRequest, self.container_client.update_container, container_name, **metadata) self.assertIn('Metadata name too long', str(ex)) @decorators.attr(type=["negative"]) @decorators.idempotent_id('81e36922-326b-4b7c-8155-3bbceecd7a82') @testtools.skipUnless( CONF.object_storage_feature_enabled.discoverability, 'Discoverability function is disabled') def test_create_container_metadata_value_exceeds_max_length(self): # Attempts to create container with metadata value # that is longer than max. max_length = self.constraints['max_meta_value_length'] container_name = data_utils.rand_name(name='TestContainer') metadata_value = data_utils.arbitrary_string(size=max_length + 1) metadata = {'X-Container-Meta-animal': metadata_value} ex = self.assertRaises( exceptions.BadRequest, self.container_client.update_container, container_name, **metadata) self.assertIn('Metadata value longer than ' + str(max_length), str(ex)) @decorators.attr(type=["negative"]) @decorators.idempotent_id('ac666539-d566-4f02-8ceb-58e968dfb732') @testtools.skipUnless( CONF.object_storage_feature_enabled.discoverability, 'Discoverability function is disabled') def test_create_container_metadata_exceeds_overall_metadata_count(self): # Attempts to create container with metadata that exceeds the # default count max_count = self.constraints['max_meta_count'] container_name = data_utils.rand_name(name='TestContainer') metadata = {} for i in range(max_count + 1): metadata['X-Container-Meta-animal-' + str(i)] = 'penguin' ex = self.assertRaises( exceptions.BadRequest, self.container_client.update_container, container_name, **metadata) self.assertIn('Too many metadata items; max ' + str(max_count), str(ex)) @decorators.attr(type=["negative"]) @decorators.idempotent_id('1a95ab2e-b712-4a98-8a4d-8ce21b7557d6') def test_get_metadata_headers_with_invalid_container_name(self): # Attempts to retrieve metadata headers with an invalid # container name. self.assertRaises(exceptions.NotFound, self.container_client.list_container_metadata, 'invalid_container_name') @decorators.attr(type=["negative"]) @decorators.idempotent_id('125a24fa-90a7-4cfc-b604-44e49d788390') def test_update_metadata_with_nonexistent_container_name(self): # Attempts to update metadata using a nonexistent container name. metadata = {'animal': 'penguin'} self.assertRaises( exceptions.NotFound, self.container_client.create_update_or_delete_container_metadata, 'nonexistent_container_name', create_update_metadata=metadata) @decorators.attr(type=["negative"]) @decorators.idempotent_id('65387dbf-a0e2-4aac-9ddc-16eb3f1f69ba') def test_delete_with_nonexistent_container_name(self): # Attempts to delete metadata using a nonexistent container name. metadata = {'animal': 'penguin'} self.assertRaises( exceptions.NotFound, self.container_client.create_update_or_delete_container_metadata, 'nonexistent_container_name', delete_metadata=metadata) @decorators.attr(type=["negative"]) @decorators.idempotent_id('14331d21-1e81-420a-beea-19cb5e5207f5') def test_list_all_container_objects_with_nonexistent_container(self): # Attempts to get a listing of all objects on a container # that doesn't exist. params = {'limit': 9999, 'format': 'json'} self.assertRaises(exceptions.NotFound, self.container_client.list_container_objects, 'nonexistent_container_name', params) @decorators.attr(type=["negative"]) @decorators.idempotent_id('86b2ab08-92d5-493d-acd2-85f0c848819e') def test_list_all_container_objects_on_deleted_container(self): # Attempts to get a listing of all objects on a container # that was deleted. container_name = self.create_container() # delete container resp, _ = self.container_client.delete_container(container_name) self.assertHeaders(resp, 'Container', 'DELETE') params = {'limit': 9999, 'format': 'json'} self.assertRaises(exceptions.NotFound, self.container_client.list_container_objects, container_name, params) @decorators.attr(type=["negative"]) @decorators.idempotent_id('42da116e-1e8c-4c96-9e06-2f13884ed2b1') def test_delete_non_empty_container(self): # create a container and an object within it # attempt to delete a container that isn't empty. container_name = self.create_container() self.addCleanup(self.container_client.delete_container, container_name) object_name, _ = self.create_object(container_name) self.addCleanup(self.object_client.delete_object, container_name, object_name) ex = self.assertRaises(exceptions.Conflict, self.container_client.delete_container, container_name) self.assertIn('There was a conflict when trying to complete your ' 'request.', str(ex)) tempest-17.2.0/tempest/api/object_storage/test_account_bulk.py0000666000175100017510000001442713207044712024626 0ustar zuulzuul00000000000000# Copyright 2013 NTT 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. import tarfile import tempfile from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest.common import utils from tempest.lib import decorators class BulkTest(base.BaseObjectTest): def setUp(self): super(BulkTest, self).setUp() self.containers = [] def tearDown(self): # NOTE(andreaf) BulkTests needs to cleanup containers after each # test is executed. base.delete_containers(self.containers, self.container_client, self.object_client) super(BulkTest, self).tearDown() def _create_archive(self): # Create an archived file for bulk upload testing. # Directory and files contained in the directory correspond to # container and subsidiary objects. tmp_dir = tempfile.mkdtemp() tmp_file = tempfile.mkstemp(dir=tmp_dir) # Extract a container name and an object name container_name = tmp_dir.split("/")[-1] object_name = tmp_file[1].split("/")[-1] # Create tar file tarpath = tempfile.NamedTemporaryFile(suffix=".tar") tar = tarfile.open(None, 'w', tarpath) tar.add(tmp_dir, arcname=container_name) tar.close() tarpath.flush() return tarpath.name, container_name, object_name def _upload_archive(self, filepath): # upload an archived file with open(filepath) as fh: mydata = fh.read() resp = self.bulk_client.upload_archive( upload_path='', data=mydata, archive_file_format='tar') return resp def _check_contents_deleted(self, container_name): param = {'format': 'txt'} resp, body = self.account_client.list_account_containers(param) self.assertHeaders(resp, 'Account', 'GET') self.assertNotIn(container_name, body) @decorators.idempotent_id('a407de51-1983-47cc-9f14-47c2b059413c') @utils.requires_ext(extension='bulk_upload', service='object') def test_extract_archive(self): # Test bulk operation of file upload with an archived file filepath, container_name, object_name = self._create_archive() resp = self._upload_archive(filepath) self.containers.append(container_name) # When uploading an archived file with the bulk operation, the response # does not contain 'content-length' header. This is the special case, # therefore the existence of response headers is checked without # custom matcher. self.assertIn('transfer-encoding', resp.response) self.assertIn('content-type', resp.response) self.assertIn('x-trans-id', resp.response) self.assertIn('date', resp.response) # Check only the format of common headers with custom matcher self.assertThat(resp.response, custom_matchers.AreAllWellFormatted()) param = {'format': 'json'} resp, body = self.account_client.list_account_containers(param) self.assertHeaders(resp, 'Account', 'GET') self.assertIn(container_name, [b['name'] for b in body]) param = {'format': 'json'} resp, contents_list = self.container_client.list_container_objects( container_name, param) self.assertHeaders(resp, 'Container', 'GET') self.assertIn(object_name, [c['name'] for c in contents_list]) @decorators.idempotent_id('c075e682-0d2a-43b2-808d-4116200d736d') @utils.requires_ext(extension='bulk_delete', service='object') def test_bulk_delete(self): # Test bulk operation of deleting multiple files filepath, container_name, object_name = self._create_archive() self._upload_archive(filepath) data = '%s/%s\n%s' % (container_name, object_name, container_name) resp = self.bulk_client.delete_bulk_data(data=data) # When deleting multiple files using the bulk operation, the response # does not contain 'content-length' header. This is the special case, # therefore the existence of response headers is checked without # custom matcher. self.assertIn('transfer-encoding', resp.response) self.assertIn('content-type', resp.response) self.assertIn('x-trans-id', resp.response) self.assertIn('date', resp.response) # Check only the format of common headers with custom matcher self.assertThat(resp.response, custom_matchers.AreAllWellFormatted()) # Check if uploaded contents are completely deleted self._check_contents_deleted(container_name) @decorators.idempotent_id('dbea2bcb-efbb-4674-ac8a-a5a0e33d1d79') @utils.requires_ext(extension='bulk_delete', service='object') def test_bulk_delete_by_POST(self): # Test bulk operation of deleting multiple files filepath, container_name, object_name = self._create_archive() self._upload_archive(filepath) data = '%s/%s\n%s' % (container_name, object_name, container_name) resp = self.bulk_client.delete_bulk_data_with_post(data=data) # When deleting multiple files using the bulk operation, the response # does not contain 'content-length' header. This is the special case, # therefore the existence of response headers is checked without # custom matcher. self.assertIn('transfer-encoding', resp.response) self.assertIn('content-type', resp.response) self.assertIn('x-trans-id', resp.response) self.assertIn('date', resp.response) # Check only the format of common headers with custom matcher self.assertThat(resp.response, custom_matchers.AreAllWellFormatted()) # Check if uploaded contents are completely deleted self._check_contents_deleted(container_name) tempest-17.2.0/tempest/api/object_storage/test_object_formpost_negative.py0000666000175100017510000001211613207044712027227 0ustar zuulzuul00000000000000# Copyright (C) 2013 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 hashlib import hmac import time from six.moves.urllib import parse as urlparse from tempest.api.object_storage import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ObjectFormPostNegativeTest(base.BaseObjectTest): metadata = {} containers = [] @classmethod def resource_setup(cls): super(ObjectFormPostNegativeTest, cls).resource_setup() cls.container_name = cls.create_container() cls.object_name = data_utils.rand_name(name='ObjectTemp') cls.key = 'Meta' cls.metadata = {'Temp-URL-Key': cls.key} cls.account_client.create_update_or_delete_account_metadata( create_update_metadata=cls.metadata) def setUp(self): super(ObjectFormPostNegativeTest, self).setUp() # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key'], self.key) @classmethod def resource_cleanup(cls): cls.account_client.create_update_or_delete_account_metadata( delete_metadata=cls.metadata) cls.delete_containers() super(ObjectFormPostNegativeTest, cls).resource_cleanup() def get_multipart_form(self, expires=600): path = "%s/%s/%s" % ( urlparse.urlparse(self.container_client.base_url).path, self.container_name, self.object_name) redirect = '' max_file_size = 104857600 max_file_count = 10 expires += int(time.time()) hmac_body = '%s\n%s\n%s\n%s\n%s' % (path, redirect, max_file_size, max_file_count, expires) signature = hmac.new( self.key.encode(), hmac_body.encode(), hashlib.sha1 ).hexdigest() fields = {'redirect': redirect, 'max_file_size': str(max_file_size), 'max_file_count': str(max_file_count), 'expires': str(expires), 'signature': signature} boundary = '--boundary--' data = [] for (key, value) in fields.items(): data.append('--' + boundary) data.append('Content-Disposition: form-data; name="%s"' % key) data.append('') data.append(value) data.append('--' + boundary) data.append('Content-Disposition: form-data; ' 'name="file1"; filename="testfile"') data.append('Content-Type: application/octet-stream') data.append('') data.append('hello world') data.append('--' + boundary + '--') data.append('') body = '\r\n'.join(data) content_type = 'multipart/form-data; boundary=%s' % boundary return body, content_type @decorators.idempotent_id('d3fb3c4d-e627-48ce-9379-a1631f21336d') @utils.requires_ext(extension='formpost', service='object') @decorators.attr(type=['negative']) def test_post_object_using_form_expired(self): body, content_type = self.get_multipart_form(expires=1) time.sleep(2) headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} url = "%s/%s" % (self.container_name, self.object_name) exc = self.assertRaises( lib_exc.Unauthorized, self.object_client.post, url, body, headers=headers) self.assertIn('FormPost: Form Expired', str(exc)) @decorators.idempotent_id('b277257f-113c-4499-b8d1-5fead79f7360') @utils.requires_ext(extension='formpost', service='object') @decorators.attr(type=['negative']) def test_post_object_using_form_invalid_signature(self): self.key = "Wrong" body, content_type = self.get_multipart_form() headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} url = "%s/%s" % (self.container_name, self.object_name) exc = self.assertRaises( lib_exc.Unauthorized, self.object_client.post, url, body, headers=headers) self.assertIn('FormPost: Invalid Signature', str(exc)) tempest-17.2.0/tempest/api/object_storage/test_container_quotas.py0000666000175100017510000001072213207044712025525 0ustar zuulzuul00000000000000# Copyright 2013 Cloudwatt # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.object_storage import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc QUOTA_BYTES = 10 QUOTA_COUNT = 3 class ContainerQuotasTest(base.BaseObjectTest): """Attempts to test the perfect behavior of quotas in a container.""" def setUp(self): """Creates and sets a container with quotas. Quotas are set by adding meta values to the container, and are validated when set: - X-Container-Meta-Quota-Bytes: Maximum size of the container, in bytes. - X-Container-Meta-Quota-Count: Maximum object count of the container. """ super(ContainerQuotasTest, self).setUp() self.container_name = self.create_container() metadata = {"quota-bytes": str(QUOTA_BYTES), "quota-count": str(QUOTA_COUNT), } self.container_client.create_update_or_delete_container_metadata( self.container_name, create_update_metadata=metadata) def tearDown(self): """Cleans the container of any object after each test.""" self.delete_containers() super(ContainerQuotasTest, self).tearDown() @decorators.idempotent_id('9a0fb034-86af-4df0-86fa-f8bd7db21ae0') @utils.requires_ext(extension='container_quotas', service='object') @decorators.attr(type="smoke") def test_upload_valid_object(self): """Attempts to uploads an object smaller than the bytes quota.""" object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string(QUOTA_BYTES) nbefore = self._get_bytes_used() resp, _ = self.object_client.create_object( self.container_name, object_name, data) self.assertHeaders(resp, 'Object', 'PUT') nafter = self._get_bytes_used() self.assertEqual(nbefore + len(data), nafter) @decorators.idempotent_id('22eeeb2b-3668-4160-baef-44790f65a5a0') @utils.requires_ext(extension='container_quotas', service='object') @decorators.attr(type="smoke") def test_upload_large_object(self): """Attempts to upload an object larger than the bytes quota.""" object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string(QUOTA_BYTES + 1) nbefore = self._get_bytes_used() self.assertRaises(lib_exc.OverLimit, self.object_client.create_object, self.container_name, object_name, data) nafter = self._get_bytes_used() self.assertEqual(nbefore, nafter) @decorators.idempotent_id('3a387039-697a-44fc-a9c0-935de31f426b') @utils.requires_ext(extension='container_quotas', service='object') @decorators.attr(type="smoke") def test_upload_too_many_objects(self): """Attempts to upload many objects that exceeds the count limit.""" for _ in range(QUOTA_COUNT): name = data_utils.rand_name(name="TestObject") self.object_client.create_object(self.container_name, name, "") nbefore = self._get_object_count() self.assertEqual(nbefore, QUOTA_COUNT) self.assertRaises(lib_exc.OverLimit, self.object_client.create_object, self.container_name, "OverQuotaObject", "") nafter = self._get_object_count() self.assertEqual(nbefore, nafter) def _get_container_metadata(self): resp, _ = self.container_client.list_container_metadata( self.container_name) return resp def _get_object_count(self): resp = self._get_container_metadata() return int(resp["x-container-object-count"]) def _get_bytes_used(self): resp = self._get_container_metadata() return int(resp["x-container-bytes-used"]) tempest-17.2.0/tempest/api/compute/0000775000175100017510000000000013207045130017207 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/api_microversion_fixture.py0000666000175100017510000000217313207044712024711 0ustar zuulzuul00000000000000# 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.lib.services.compute 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 tempest-17.2.0/tempest/api/compute/flavors/0000775000175100017510000000000013207045130020663 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/flavors/test_flavors_negative.py0000666000175100017510000000656313207044712025653 0ustar zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import random import six from tempest.api.compute import base from tempest.common import image as common_image from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class FlavorsV2NegativeTest(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(FlavorsV2NegativeTest, cls).setup_clients() if CONF.image_feature_enabled.api_v1: cls.images_client = cls.os_primary.image_client elif CONF.image_feature_enabled.api_v2: cls.images_client = cls.os_primary.image_client_v2 else: raise lib_exc.InvalidConfiguration( 'Either api_v1 or api_v2 must be True in ' '[image-feature-enabled].') @decorators.attr(type=['negative']) @utils.services('image') @decorators.idempotent_id('90f0d93a-91c1-450c-91e6-07d18172cefe') def test_boot_with_low_ram(self): """Try boot a vm with lower than min ram Create an image with min_ram value Try to create server with flavor of insufficient ram size from that image """ flavor = self.flavors_client.show_flavor( CONF.compute.flavor_ref)['flavor'] min_img_ram = flavor['ram'] + 1 size = random.randint(1024, 4096) image_file = six.BytesIO(data_utils.random_bytes(size)) params = { 'name': data_utils.rand_name('image'), 'container_format': CONF.image.container_formats[0], 'disk_format': CONF.image.disk_formats[0], 'min_ram': min_img_ram } if CONF.image_feature_enabled.api_v1: params.update({'is_public': False}) params = {'headers': common_image.image_meta_to_headers(**params)} else: params.update({'visibility': 'private'}) image = self.images_client.create_image(**params) image = image['image'] if 'image' in image else image self.addCleanup(self.images_client.delete_image, image['id']) if CONF.image_feature_enabled.api_v1: self.images_client.update_image(image['id'], data=image_file) else: self.images_client.store_image_file(image['id'], data=image_file) self.assertEqual(min_img_ram, image['min_ram']) # Try to create server with flavor of insufficient ram size self.assertRaisesRegex(lib_exc.BadRequest, "Flavor's memory is too small for " "requested image", self.create_test_server, image_id=image['id'], flavor=flavor['id']) tempest-17.2.0/tempest/api/compute/flavors/test_flavors.py0000666000175100017510000001357013207044712023765 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators class FlavorsV2TestJSON(base.BaseV2ComputeTest): @decorators.attr(type='smoke') @decorators.idempotent_id('e36c0eaa-dff5-4082-ad1f-3f9a80aa3f59') def test_list_flavors(self): # List of all flavors should contain the expected flavor flavors = self.flavors_client.list_flavors()['flavors'] flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'], 'name': flavor['name']} self.assertIn(flavor_min_detail, flavors) @decorators.idempotent_id('6e85fde4-b3cd-4137-ab72-ed5f418e8c24') def test_list_flavors_with_detail(self): # Detailed list of all flavors should contain the expected flavor flavors = self.flavors_client.list_flavors(detail=True)['flavors'] flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] self.assertIn(flavor, flavors) @decorators.attr(type='smoke') @decorators.idempotent_id('1f12046b-753d-40d2-abb6-d8eb8b30cb2f') def test_get_flavor(self): # The expected flavor details should be returned flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] self.assertEqual(self.flavor_ref, flavor['id']) @decorators.idempotent_id('8d7691b3-6ed4-411a-abc9-2839a765adab') def test_list_flavors_limit_results(self): # Only the expected number of flavors should be returned params = {'limit': 1} flavors = self.flavors_client.list_flavors(**params)['flavors'] self.assertEqual(1, len(flavors)) @decorators.idempotent_id('b26f6327-2886-467a-82be-cef7a27709cb') def test_list_flavors_detailed_limit_results(self): # Only the expected number of flavors (detailed) should be returned params = {'limit': 1} flavors = self.flavors_client.list_flavors(detail=True, **params)['flavors'] self.assertEqual(1, len(flavors)) @decorators.idempotent_id('e800f879-9828-4bd0-8eae-4f17189951fb') def test_list_flavors_using_marker(self): # The list of flavors should start from the provided marker flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_id = flavor['id'] params = {'marker': flavor_id} flavors = self.flavors_client.list_flavors(**params)['flavors'] self.assertEmpty([i for i in flavors if i['id'] == flavor_id], 'The list of flavors did not start after the marker.') @decorators.idempotent_id('6db2f0c0-ddee-4162-9c84-0703d3dd1107') def test_list_flavors_detailed_using_marker(self): # The list of flavors should start from the provided marker flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_id = flavor['id'] params = {'marker': flavor_id} flavors = self.flavors_client.list_flavors(detail=True, **params)['flavors'] self.assertEmpty([i for i in flavors if i['id'] == flavor_id], 'The list of flavors did not start after the marker.') @decorators.idempotent_id('3df2743e-3034-4e57-a4cb-b6527f6eac79') def test_list_flavors_detailed_filter_by_min_disk(self): # The detailed list of flavors should be filtered by disk space flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_id = flavor['id'] params = {'minDisk': flavor['disk'] + 1} flavors = self.flavors_client.list_flavors(detail=True, **params)['flavors'] self.assertEmpty([i for i in flavors if i['id'] == flavor_id]) @decorators.idempotent_id('09fe7509-b4ee-4b34-bf8b-39532dc47292') def test_list_flavors_detailed_filter_by_min_ram(self): # The detailed list of flavors should be filtered by RAM flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_id = flavor['id'] params = {'minRam': flavor['ram'] + 1} flavors = self.flavors_client.list_flavors(detail=True, **params)['flavors'] self.assertEmpty([i for i in flavors if i['id'] == flavor_id]) @decorators.idempotent_id('10645a4d-96f5-443f-831b-730711e11dd4') def test_list_flavors_filter_by_min_disk(self): # The list of flavors should be filtered by disk space flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_id = flavor['id'] params = {'minDisk': flavor['disk'] + 1} flavors = self.flavors_client.list_flavors(**params)['flavors'] self.assertEmpty([i for i in flavors if i['id'] == flavor_id]) @decorators.idempotent_id('935cf550-e7c8-4da6-8002-00f92d5edfaa') def test_list_flavors_filter_by_min_ram(self): # The list of flavors should be filtered by RAM flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] flavor_id = flavor['id'] params = {'minRam': flavor['ram'] + 1} flavors = self.flavors_client.list_flavors(**params)['flavors'] self.assertEmpty([i for i in flavors if i['id'] == flavor_id]) tempest-17.2.0/tempest/api/compute/flavors/__init__.py0000666000175100017510000000000013207044712022771 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/images/0000775000175100017510000000000013207045130020454 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/images/test_images_negative.py0000666000175100017510000001340613207044712025227 0ustar zuulzuul00000000000000# Copyright 2012 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 tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ImagesNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ImagesNegativeTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) if not CONF.compute_feature_enabled.snapshot: skip_msg = ("%s skipped as instance snapshotting is not supported" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ImagesNegativeTestJSON, cls).setup_clients() cls.client = cls.compute_images_client @decorators.attr(type=['negative']) @decorators.idempotent_id('6cd5a89d-5b47-46a7-93bc-3916f0d84973') def test_create_image_from_deleted_server(self): # An image should not be created if the server instance is removed server = self.create_test_server(wait_until='ACTIVE') # Delete server before trying to create image self.servers_client.delete_server(server['id']) waiters.wait_for_server_termination(self.servers_client, server['id']) # Create a new image after server is deleted meta = {'image_type': 'test'} self.assertRaises(lib_exc.NotFound, self.create_image_from_server, server['id'], metadata=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('82c5b0c4-9dbd-463c-872b-20c4755aae7f') def test_create_image_from_invalid_server(self): # An image should not be created with invalid server id # Create a new image with invalid server id meta = {'image_type': 'test'} self.assertRaises(lib_exc.NotFound, self.create_image_from_server, data_utils.rand_name('invalid'), metadata=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('ec176029-73dc-4037-8d72-2e4ff60cf538') def test_create_image_specify_uuid_35_characters_or_less(self): # Return an error if Image ID passed is 35 characters or less snapshot_name = data_utils.rand_name('test-snap') test_uuid = ('a' * 35) self.assertRaises(lib_exc.NotFound, self.client.create_image, test_uuid, name=snapshot_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('36741560-510e-4cc2-8641-55fe4dfb2437') def test_create_image_specify_uuid_37_characters_or_more(self): # Return an error if Image ID passed is 37 characters or more snapshot_name = data_utils.rand_name('test-snap') test_uuid = ('a' * 37) self.assertRaises(lib_exc.NotFound, self.client.create_image, test_uuid, name=snapshot_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('381acb65-785a-4942-94ce-d8f8c84f1f0f') def test_delete_image_with_invalid_image_id(self): # An image should not be deleted with invalid image id self.assertRaises(lib_exc.NotFound, self.client.delete_image, data_utils.rand_name('invalid')) @decorators.attr(type=['negative']) @decorators.idempotent_id('137aef61-39f7-44a1-8ddf-0adf82511701') def test_delete_non_existent_image(self): # Return an error while trying to delete a non-existent image non_existent_image_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.delete_image, non_existent_image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('e6e41425-af5c-4fe6-a4b5-7b7b963ffda5') def test_delete_image_blank_id(self): # Return an error while trying to delete an image with blank Id self.assertRaises(lib_exc.NotFound, self.client.delete_image, '') @decorators.attr(type=['negative']) @decorators.idempotent_id('924540c3-f1f1-444c-8f58-718958b6724e') def test_delete_image_non_hex_string_id(self): # Return an error while trying to delete an image with non hex id invalid_image_id = data_utils.rand_uuid()[:-1] + "j" self.assertRaises(lib_exc.NotFound, self.client.delete_image, invalid_image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('68e2c175-bd26-4407-ac0f-4ea9ce2139ea') def test_delete_image_negative_image_id(self): # Return an error while trying to delete an image with negative id self.assertRaises(lib_exc.NotFound, self.client.delete_image, -1) @decorators.attr(type=['negative']) @decorators.idempotent_id('b340030d-82cd-4066-a314-c72fb7c59277') def test_delete_image_with_id_over_character_limit(self): # Return an error while trying to delete image with id over limit invalid_image_id = data_utils.rand_uuid() + "1" self.assertRaises(lib_exc.NotFound, self.client.delete_image, invalid_image_id) tempest-17.2.0/tempest/api/compute/images/test_images_oneserver_negative.py0000666000175100017510000001347713207044712027327 0ustar zuulzuul00000000000000# 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. from oslo_log import log as logging from tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common import api_version_utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF LOG = logging.getLogger(__name__) class ImagesOneServerNegativeTestJSON(base.BaseV2ComputeTest): def tearDown(self): """Terminate test instances created after a test is executed.""" self.server_check_teardown() super(ImagesOneServerNegativeTestJSON, self).tearDown() def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ImagesOneServerNegativeTestJSON, self).setUp() # Check if the server is in a clean state after test try: waiters.wait_for_server_status(self.servers_client, self.server_id, 'ACTIVE') except Exception: LOG.exception('server %s timed out to become ACTIVE. rebuilding', self.server_id) # Rebuild server if cannot reach the ACTIVE state # Usually it means the server had a serious accident self._reset_server() def _reset_server(self): self.__class__.server_id = self.recreate_server(self.server_id) @classmethod def skip_checks(cls): super(ImagesOneServerNegativeTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) if not CONF.compute_feature_enabled.snapshot: skip_msg = ("%s skipped as instance snapshotting is not supported" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ImagesOneServerNegativeTestJSON, cls).setup_clients() cls.client = cls.compute_images_client @classmethod def resource_setup(cls): super(ImagesOneServerNegativeTestJSON, cls).resource_setup() server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @decorators.attr(type=['negative']) @decorators.idempotent_id('55d1d38c-dd66-4933-9c8e-7d92aeb60ddc') def test_create_image_specify_invalid_metadata(self): # Return an error when creating image with invalid metadata meta = {'': ''} self.assertRaises(lib_exc.BadRequest, self.create_image_from_server, self.server_id, metadata=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('3d24d11f-5366-4536-bd28-cff32b748eca') def test_create_image_specify_metadata_over_limits(self): # Return an error when creating image with meta data over 255 chars meta = {'a' * 256: 'b' * 256} self.assertRaises(lib_exc.BadRequest, self.create_image_from_server, self.server_id, metadata=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('0460efcf-ee88-4f94-acef-1bf658695456') def test_create_second_image_when_first_image_is_being_saved(self): # Disallow creating another image when first image is being saved # Create first snapshot image = self.create_image_from_server(self.server_id) self.addCleanup(self._reset_server) # Create second snapshot self.assertRaises(lib_exc.Conflict, self.create_image_from_server, self.server_id) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", image.response, "lt"): image_id = image['image_id'] else: image_id = data_utils.parse_image_id(image.response['location']) self.client.delete_image(image_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('084f0cbc-500a-4963-8a4e-312905862581') def test_create_image_specify_name_over_character_limit(self): # Return an error if snapshot name over 255 characters is passed snapshot_name = ('a' * 256) self.assertRaises(lib_exc.BadRequest, self.client.create_image, self.server_id, name=snapshot_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('0894954d-2db2-4195-a45b-ffec0bc0187e') def test_delete_image_that_is_not_yet_active(self): # Return an error while trying to delete an image what is creating image = self.create_image_from_server(self.server_id) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", image.response, "lt"): image_id = image['image_id'] else: image_id = data_utils.parse_image_id(image.response['location']) self.addCleanup(self._reset_server) # Do not wait, attempt to delete the image, ensure it's successful self.client.delete_image(image_id) self.assertRaises(lib_exc.NotFound, self.client.show_image, image_id) tempest-17.2.0/tempest/api/compute/images/test_image_metadata.py0000666000175100017510000001444413207044712025025 0ustar zuulzuul00000000000000# 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 six from tempest.api.compute import base from tempest.common import image as common_image from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class ImagesMetadataTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ImagesMetadataTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ImagesMetadataTestJSON, cls).setup_clients() # Check if glance v1 is available to determine which client to use. We # prefer glance v1 for the compute API tests since the compute image # API proxy was written for glance v1. if CONF.image_feature_enabled.api_v1: cls.glance_client = cls.os_primary.image_client elif CONF.image_feature_enabled.api_v2: cls.glance_client = cls.os_primary.image_client_v2 else: raise exceptions.InvalidConfiguration( 'Either api_v1 or api_v2 must be True in ' '[image-feature-enabled].') cls.client = cls.compute_images_client @classmethod def resource_setup(cls): super(ImagesMetadataTestJSON, cls).resource_setup() cls.image_id = None params = { 'name': data_utils.rand_name('image'), 'container_format': 'bare', 'disk_format': 'raw' } if CONF.image_feature_enabled.api_v1: params.update({'is_public': False}) params = {'headers': common_image.image_meta_to_headers(**params)} else: params.update({'visibility': 'private'}) body = cls.glance_client.create_image(**params) body = body['image'] if 'image' in body else body cls.image_id = body['id'] cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.glance_client.delete_image, cls.image_id) image_file = six.BytesIO((b'*' * 1024)) if CONF.image_feature_enabled.api_v1: cls.glance_client.update_image(cls.image_id, data=image_file) else: cls.glance_client.store_image_file(cls.image_id, data=image_file) waiters.wait_for_image_status(cls.client, cls.image_id, 'ACTIVE') def setUp(self): super(ImagesMetadataTestJSON, self).setUp() meta = {'os_version': 'value1', 'os_distro': 'value2'} self.client.set_image_metadata(self.image_id, meta) @decorators.idempotent_id('37ec6edd-cf30-4c53-bd45-ae74db6b0531') def test_list_image_metadata(self): # All metadata key/value pairs for an image should be returned resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'metadata': { 'os_version': 'value1', 'os_distro': 'value2'}} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('ece7befc-d3ce-42a4-b4be-c3067a418c29') def test_set_image_metadata(self): # The metadata for the image should match the new values req_metadata = {'os_version': 'value2', 'architecture': 'value3'} self.client.set_image_metadata(self.image_id, req_metadata) resp_metadata = (self.client.list_image_metadata(self.image_id) ['metadata']) self.assertEqual(req_metadata, resp_metadata) @decorators.idempotent_id('7b491c11-a9d5-40fe-a696-7f7e03d3fea2') def test_update_image_metadata(self): # The metadata for the image should match the updated values req_metadata = {'os_version': 'alt1', 'architecture': 'value3'} self.client.update_image_metadata(self.image_id, req_metadata) resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'metadata': { 'os_version': 'alt1', 'os_distro': 'value2', 'architecture': 'value3'}} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('4f5db52f-6685-4c75-b848-f4bb363f9aa6') def test_get_image_metadata_item(self): # The value for a specific metadata key should be returned meta = self.client.show_image_metadata_item(self.image_id, 'os_distro')['meta'] self.assertEqual('value2', meta['os_distro']) @decorators.idempotent_id('f2de776a-4778-4d90-a5da-aae63aee64ae') def test_set_image_metadata_item(self): # The value provided for the given meta item should be set for # the image meta = {'os_version': 'alt'} self.client.set_image_metadata_item(self.image_id, 'os_version', meta) resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'metadata': {'os_version': 'alt', 'os_distro': 'value2'}} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('a013796c-ba37-4bb5-8602-d944511def14') def test_delete_image_metadata_item(self): # The metadata value/key pair should be deleted from the image self.client.delete_image_metadata_item(self.image_id, 'os_version') resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'metadata': {'os_distro': 'value2'}} self.assertEqual(expected, resp_metadata) tempest-17.2.0/tempest/api/compute/images/test_list_images.py0000666000175100017510000000416613207044712024403 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest import config from tempest.lib import decorators CONF = config.CONF class ListImagesTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ListImagesTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ListImagesTestJSON, cls).setup_clients() cls.client = cls.compute_images_client @decorators.idempotent_id('490d0898-e12a-463f-aef0-c50156b9f789') def test_get_image(self): # Returns the correct details for a single image image = self.client.show_image(self.image_ref)['image'] self.assertEqual(self.image_ref, image['id']) @decorators.idempotent_id('fd51b7f4-d4a3-4331-9885-866658112a6f') def test_list_images(self): # The list of all images should contain the image images = self.client.list_images()['images'] found = [i for i in images if i['id'] == self.image_ref] self.assertNotEmpty(found) @decorators.idempotent_id('9f94cb6b-7f10-48c5-b911-a0b84d7d4cd6') def test_list_images_with_detail(self): # Detailed list of all images should contain the expected images images = self.client.list_images(detail=True)['images'] found = [i for i in images if i['id'] == self.image_ref] self.assertNotEmpty(found) tempest-17.2.0/tempest/api/compute/images/test_images.py0000666000175100017510000001156613207044712023352 0ustar zuulzuul00000000000000# Copyright 2012 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 tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators import testtools CONF = config.CONF class ImagesTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ImagesTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) if not CONF.compute_feature_enabled.snapshot: skip_msg = ("%s skipped as instance snapshotting is not supported" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ImagesTestJSON, cls).setup_clients() cls.client = cls.compute_images_client @decorators.idempotent_id('aa06b52b-2db5-4807-b218-9441f75d74e3') def test_delete_saving_image(self): server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.servers_client.delete_server, server['id']) image = self.create_image_from_server(server['id'], wait_until='SAVING') self.client.delete_image(image['id']) msg = ('The image with ID {image_id} failed to be deleted' .format(image_id=image['id'])) self.assertTrue(self.client.is_resource_deleted(image['id']), msg) @decorators.idempotent_id('aaacd1d0-55a2-4ce8-818a-b5439df8adc9') def test_create_image_from_stopped_server(self): server = self.create_test_server(wait_until='ACTIVE') self.servers_client.stop_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SHUTOFF') self.addCleanup(self.servers_client.delete_server, server['id']) snapshot_name = data_utils.rand_name('test-snap') image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='ACTIVE', wait_for_server=False) self.addCleanup(self.client.delete_image, image['id']) self.assertEqual(snapshot_name, image['name']) @decorators.idempotent_id('71bcb732-0261-11e7-9086-fa163e4fa634') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') def test_create_image_from_paused_server(self): server = self.create_test_server(wait_until='ACTIVE') self.servers_client.pause_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'PAUSED') self.addCleanup(self.servers_client.delete_server, server['id']) snapshot_name = data_utils.rand_name('test-snap') image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='ACTIVE', wait_for_server=False) self.addCleanup(self.client.delete_image, image['id']) self.assertEqual(snapshot_name, image['name']) @decorators.idempotent_id('8ca07fec-0262-11e7-907e-fa163e4fa634') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') def test_create_image_from_suspended_server(self): server = self.create_test_server(wait_until='ACTIVE') self.servers_client.suspend_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SUSPENDED') self.addCleanup(self.servers_client.delete_server, server['id']) snapshot_name = data_utils.rand_name('test-snap') image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='ACTIVE', wait_for_server=False) self.addCleanup(self.client.delete_image, image['id']) self.assertEqual(snapshot_name, image['name']) tempest-17.2.0/tempest/api/compute/images/__init__.py0000666000175100017510000000000013207044712022562 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/images/test_images_oneserver.py0000666000175100017510000000767113207044712025444 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest import config from tempest.lib.common import api_version_utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ImagesOneServerTestJSON(base.BaseV2ComputeTest): @classmethod def resource_setup(cls): super(ImagesOneServerTestJSON, cls).resource_setup() cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id'] @classmethod def skip_checks(cls): super(ImagesOneServerTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) if not CONF.compute_feature_enabled.snapshot: skip_msg = ("%s skipped as instance snapshotting is not supported" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ImagesOneServerTestJSON, cls).setup_clients() cls.client = cls.compute_images_client def _get_default_flavor_disk_size(self, flavor_id): flavor = self.flavors_client.show_flavor(flavor_id)['flavor'] return flavor['disk'] @decorators.idempotent_id('3731d080-d4c5-4872-b41a-64d0d0021314') def test_create_delete_image(self): # Create a new image name = data_utils.rand_name('image') meta = {'image_type': 'test'} image = self.create_image_from_server(self.server_id, name=name, metadata=meta, wait_until='ACTIVE') # Verify the image was created correctly self.assertEqual(name, image['name']) self.assertEqual('test', image['metadata']['image_type']) original_image = self.client.show_image(self.image_ref)['image'] # Verify minRAM is the same as the original image self.assertEqual(image['minRam'], original_image['minRam']) # Verify minDisk is the same as the original image or the flavor size flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref) self.assertIn(str(image['minDisk']), (str(original_image['minDisk']), str(flavor_disk_size))) # Verify the image was deleted correctly self.client.delete_image(image['id']) self.client.wait_for_resource_deletion(image['id']) @decorators.idempotent_id('3b7c6fe4-dfe7-477c-9243-b06359db51e6') def test_create_image_specify_multibyte_character_image_name(self): # prefix character is: # http://unicode.org/cldr/utility/character.jsp?a=20A1 # We use a string with 3 byte utf-8 character due to nova/glance which # will return 400(Bad Request) if we attempt to send a name which has # 4 byte utf-8 character. utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8')) body = self.client.create_image(self.server_id, name=utf8_name) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", body.response, "lt"): image_id = body['image_id'] else: image_id = data_utils.parse_image_id(body.response['location']) self.addCleanup(self.client.delete_image, image_id) tempest-17.2.0/tempest/api/compute/images/test_image_metadata_negative.py0000666000175100017510000000707213207044712026706 0ustar zuulzuul00000000000000# 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. from tempest.api.compute import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(ImagesMetadataNegativeTestJSON, cls).setup_clients() cls.client = cls.compute_images_client @decorators.attr(type=['negative']) @decorators.idempotent_id('94069db2-792f-4fa8-8bd3-2271a6e0c095') def test_list_nonexistent_image_metadata(self): # Negative test: List on nonexistent image # metadata should not happen self.assertRaises(lib_exc.NotFound, self.client.list_image_metadata, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('a403ef9e-9f95-427c-b70a-3ce3388796f1') def test_update_nonexistent_image_metadata(self): # Negative test:An update should not happen for a non-existent image meta = {'os_distro': 'alt1', 'os_version': 'alt2'} self.assertRaises(lib_exc.NotFound, self.client.update_image_metadata, data_utils.rand_uuid(), meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('41ae052c-6ee6-405c-985e-5712393a620d') def test_get_nonexistent_image_metadata_item(self): # Negative test: Get on non-existent image should not happen self.assertRaises(lib_exc.NotFound, self.client.show_image_metadata_item, data_utils.rand_uuid(), 'os_version') @decorators.attr(type=['negative']) @decorators.idempotent_id('dc64f2ce-77e8-45b0-88c8-e15041d08eaf') def test_set_nonexistent_image_metadata(self): # Negative test: Metadata should not be set to a non-existent image meta = {'os_distro': 'alt1', 'os_version': 'alt2'} self.assertRaises(lib_exc.NotFound, self.client.set_image_metadata, data_utils.rand_uuid(), meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('2154fd03-ab54-457c-8874-e6e3eb56e9cf') def test_set_nonexistent_image_metadata_item(self): # Negative test: Metadata item should not be set to a # nonexistent image meta = {'os_distro': 'alt'} self.assertRaises(lib_exc.NotFound, self.client.set_image_metadata_item, data_utils.rand_uuid(), 'os_distro', meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('848e157f-6bcf-4b2e-a5dd-5124025a8518') def test_delete_nonexistent_image_metadata_item(self): # Negative test: Shouldn't be able to delete metadata # item from non-existent image self.assertRaises(lib_exc.NotFound, self.client.delete_image_metadata_item, data_utils.rand_uuid(), 'os_distro') tempest-17.2.0/tempest/api/compute/images/test_list_image_filters.py0000666000175100017510000003220413207044712025742 0ustar zuulzuul00000000000000# 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 time import six import testtools from tempest.api.compute import base from tempest.common import image as common_image from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class ListImageFiltersTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ListImageFiltersTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ListImageFiltersTestJSON, cls).setup_clients() cls.client = cls.compute_images_client # Check if glance v1 is available to determine which client to use. We # prefer glance v1 for the compute API tests since the compute image # API proxy was written for glance v1. if CONF.image_feature_enabled.api_v1: cls.glance_client = cls.os_primary.image_client elif CONF.image_feature_enabled.api_v2: cls.glance_client = cls.os_primary.image_client_v2 else: raise exceptions.InvalidConfiguration( 'Either api_v1 or api_v2 must be True in ' '[image-feature-enabled].') @classmethod def resource_setup(cls): super(ListImageFiltersTestJSON, cls).resource_setup() def _create_image(): params = { 'name': data_utils.rand_name(cls.__name__ + '-image'), 'container_format': 'bare', 'disk_format': 'raw' } if CONF.image_feature_enabled.api_v1: params.update({'is_public': False}) params = {'headers': common_image.image_meta_to_headers(**params)} else: params.update({'visibility': 'private'}) body = cls.glance_client.create_image(**params) body = body['image'] if 'image' in body else body image_id = body['id'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.compute_images_client.delete_image, image_id) # Wait 1 second between creation and upload to ensure a delta # between created_at and updated_at. time.sleep(1) image_file = six.BytesIO((b'*' * 1024)) if CONF.image_feature_enabled.api_v1: cls.glance_client.update_image(image_id, data=image_file) else: cls.glance_client.store_image_file(image_id, data=image_file) waiters.wait_for_image_status(cls.client, image_id, 'ACTIVE') body = cls.client.show_image(image_id)['image'] return body # Create non-snapshot images via glance cls.image1 = _create_image() cls.image1_id = cls.image1['id'] cls.image2 = _create_image() cls.image2_id = cls.image2['id'] cls.image3 = _create_image() cls.image3_id = cls.image3['id'] if not CONF.compute_feature_enabled.snapshot: return # Create instances and snapshots via nova cls.server1 = cls.create_test_server() cls.server2 = cls.create_test_server(wait_until='ACTIVE') # NOTE(sdague) this is faster than doing the sync wait_util on both waiters.wait_for_server_status(cls.servers_client, cls.server1['id'], 'ACTIVE') # Create images to be used in the filter tests cls.snapshot1 = cls.create_image_from_server( cls.server1['id'], wait_until='ACTIVE') cls.snapshot1_id = cls.snapshot1['id'] # Servers have a hidden property for when they are being imaged # Performing back-to-back create image calls on a single # server will sometimes cause failures cls.snapshot3 = cls.create_image_from_server( cls.server2['id'], wait_until='ACTIVE') cls.snapshot3_id = cls.snapshot3['id'] # Wait for the server to be active after the image upload cls.snapshot2 = cls.create_image_from_server( cls.server1['id'], wait_until='ACTIVE') cls.snapshot2_id = cls.snapshot2['id'] @decorators.idempotent_id('a3f5b513-aeb3-42a9-b18e-f091ef73254d') def test_list_images_filter_by_status(self): # The list of images should contain only images with the # provided status params = {'status': 'ACTIVE'} images = self.client.list_images(**params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.image1_id]) self.assertNotEmpty([i for i in images if i['id'] == self.image2_id]) self.assertNotEmpty([i for i in images if i['id'] == self.image3_id]) @decorators.idempotent_id('33163b73-79f5-4d07-a7ea-9213bcc468ff') def test_list_images_filter_by_name(self): # List of all images should contain the expected images filtered # by name params = {'name': self.image1['name']} images = self.client.list_images(**params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.image1_id]) self.assertEmpty([i for i in images if i['id'] == self.image2_id]) self.assertEmpty([i for i in images if i['id'] == self.image3_id]) @decorators.idempotent_id('9f238683-c763-45aa-b848-232ec3ce3105') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting is not available.') def test_list_images_filter_by_server_id(self): # The images should contain images filtered by server id params = {'server': self.server1['id']} images = self.client.list_images(**params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.snapshot1_id], "Failed to find image %s in images. " "Got images %s" % (self.image1_id, images)) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot2_id]) self.assertEmpty([i for i in images if i['id'] == self.snapshot3_id]) @decorators.idempotent_id('05a377b8-28cf-4734-a1e6-2ab5c38bf606') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting is not available.') def test_list_images_filter_by_server_ref(self): # The list of servers should be filtered by server ref server_links = self.server2['links'] # Try all server link types for link in server_links: params = {'server': link['href']} images = self.client.list_images(**params)['images'] self.assertEmpty([i for i in images if i['id'] == self.snapshot1_id]) self.assertEmpty([i for i in images if i['id'] == self.snapshot2_id]) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot3_id]) @decorators.idempotent_id('e3356918-4d3e-4756-81d5-abc4524ba29f') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting is not available.') def test_list_images_filter_by_type(self): # The list of servers should be filtered by image type params = {'type': 'snapshot'} images = self.client.list_images(**params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.snapshot1_id]) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot2_id]) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot3_id]) self.assertEmpty([i for i in images if i['id'] == self.image_ref]) @decorators.idempotent_id('3a484ca9-67ba-451e-b494-7fcf28d32d62') def test_list_images_limit_results(self): # Verify only the expected number of results are returned params = {'limit': '1'} images = self.client.list_images(**params)['images'] self.assertEqual(1, len([x for x in images if 'id' in x])) @decorators.idempotent_id('18bac3ae-da27-436c-92a9-b22474d13aab') def test_list_images_filter_by_changes_since(self): # Verify only updated images are returned in the detailed list # Becoming ACTIVE will modify the updated time # Filter by the image's created time params = {'changes-since': self.image3['created']} images = self.client.list_images(**params)['images'] found = [i for i in images if i['id'] == self.image3_id] self.assertNotEmpty(found) @decorators.idempotent_id('9b0ea018-6185-4f71-948a-a123a107988e') def test_list_images_with_detail_filter_by_status(self): # Detailed list of all images should only contain images # with the provided status params = {'status': 'ACTIVE'} images = self.client.list_images(detail=True, **params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.image1_id]) self.assertNotEmpty([i for i in images if i['id'] == self.image2_id]) self.assertNotEmpty([i for i in images if i['id'] == self.image3_id]) @decorators.idempotent_id('644ea267-9bd9-4f3b-af9f-dffa02396a17') def test_list_images_with_detail_filter_by_name(self): # Detailed list of all images should contain the expected # images filtered by name params = {'name': self.image1['name']} images = self.client.list_images(detail=True, **params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.image1_id]) self.assertEmpty([i for i in images if i['id'] == self.image2_id]) self.assertEmpty([i for i in images if i['id'] == self.image3_id]) @decorators.idempotent_id('ba2fa9a9-b672-47cc-b354-3b4c0600e2cb') def test_list_images_with_detail_limit_results(self): # Verify only the expected number of results (with full details) # are returned params = {'limit': '1'} images = self.client.list_images(detail=True, **params)['images'] self.assertEqual(1, len(images)) @decorators.idempotent_id('8c78f822-203b-4bf6-8bba-56ebd551cf84') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting is not available.') def test_list_images_with_detail_filter_by_server_ref(self): # Detailed list of servers should be filtered by server ref server_links = self.server2['links'] # Try all server link types for link in server_links: params = {'server': link['href']} images = self.client.list_images(detail=True, **params)['images'] self.assertEmpty([i for i in images if i['id'] == self.snapshot1_id]) self.assertEmpty([i for i in images if i['id'] == self.snapshot2_id]) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot3_id]) @decorators.idempotent_id('888c0cc0-7223-43c5-9db0-b125fd0a393b') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting is not available.') def test_list_images_with_detail_filter_by_type(self): # The detailed list of servers should be filtered by image type params = {'type': 'snapshot'} images = self.client.list_images(detail=True, **params)['images'] self.client.show_image(self.image_ref) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot1_id]) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot2_id]) self.assertNotEmpty([i for i in images if i['id'] == self.snapshot3_id]) self.assertEmpty([i for i in images if i['id'] == self.image_ref]) @decorators.idempotent_id('7d439e18-ac2e-4827-b049-7e18004712c4') def test_list_images_with_detail_filter_by_changes_since(self): # Verify an update image is returned # Becoming ACTIVE will modify the updated time # Filter by the image's created time params = {'changes-since': self.image1['created']} images = self.client.list_images(detail=True, **params)['images'] self.assertNotEmpty([i for i in images if i['id'] == self.image1_id]) tempest-17.2.0/tempest/api/compute/images/test_list_image_filters_negative.py0000666000175100017510000000325213207044712027625 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ListImageFiltersNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ListImageFiltersNegativeTestJSON, cls).skip_checks() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(ListImageFiltersNegativeTestJSON, cls).setup_clients() cls.client = cls.compute_images_client @decorators.attr(type=['negative']) @decorators.idempotent_id('391b0440-432c-4d4b-b5da-c5096aa247eb') def test_get_nonexistent_image(self): # Check raises a NotFound nonexistent_image = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.show_image, nonexistent_image) tempest-17.2.0/tempest/api/compute/test_quotas.py0000666000175100017510000000746413207044712022156 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.common import utils from tempest.lib import decorators class QuotasTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(QuotasTestJSON, cls).skip_checks() if not utils.is_extension_enabled('os-quota-sets', 'compute'): msg = "quotas extension not enabled." raise cls.skipException(msg) def setUp(self): # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests. self.useFixture(fixtures.LockFixture('compute_quotas')) super(QuotasTestJSON, self).setUp() @classmethod def setup_clients(cls): super(QuotasTestJSON, cls).setup_clients() cls.client = cls.quotas_client @classmethod def resource_setup(cls): super(QuotasTestJSON, cls).resource_setup() cls.tenant_id = cls.client.tenant_id cls.user_id = cls.client.user_id cls.default_quota_set = set(('injected_file_content_bytes', 'metadata_items', 'injected_files', 'ram', 'floating_ips', 'fixed_ips', 'key_pairs', 'injected_file_path_bytes', 'instances', 'security_group_rules', 'cores', 'security_groups', 'server_group_members', 'server_groups')) @decorators.idempotent_id('f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107') def test_get_quotas(self): # User can get the quota set for it's tenant expected_quota_set = self.default_quota_set | set(['id']) quota_set = self.client.show_quota_set(self.tenant_id)['quota_set'] self.assertEqual(quota_set['id'], self.tenant_id) for quota in expected_quota_set: self.assertIn(quota, quota_set.keys()) # get the quota set using user id quota_set = self.client.show_quota_set( self.tenant_id, user_id=self.user_id)['quota_set'] self.assertEqual(quota_set['id'], self.tenant_id) for quota in expected_quota_set: self.assertIn(quota, quota_set.keys()) @decorators.idempotent_id('9bfecac7-b966-4f47-913f-1a9e2c12134a') def test_get_default_quotas(self): # User can get the default quota set for it's tenant expected_quota_set = self.default_quota_set | set(['id']) quota_set = (self.client.show_default_quota_set(self.tenant_id) ['quota_set']) self.assertEqual(quota_set['id'], self.tenant_id) for quota in expected_quota_set: self.assertIn(quota, quota_set.keys()) @decorators.idempotent_id('cd65d997-f7e4-4966-a7e9-d5001b674fdc') def test_compare_tenant_quotas_with_default_quotas(self): # Tenants are created with the default quota values default_quota_set = \ self.client.show_default_quota_set(self.tenant_id)['quota_set'] tenant_quota_set = (self.client.show_quota_set(self.tenant_id) ['quota_set']) self.assertEqual(default_quota_set, tenant_quota_set) tempest-17.2.0/tempest/api/compute/servers/0000775000175100017510000000000013207045130020700 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/servers/test_instance_actions.py0000666000175100017510000000601013207044712025641 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.common import waiters from tempest.lib import decorators class InstanceActionsTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(InstanceActionsTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(InstanceActionsTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') cls.request_id = cls.server.response['x-compute-request-id'] @decorators.idempotent_id('77ca5cc5-9990-45e0-ab98-1de8fead201a') def test_list_instance_actions(self): # List actions of the provided server self.client.reboot_server(self.server['id'], type='HARD') waiters.wait_for_server_status(self.client, self.server['id'], 'ACTIVE') body = (self.client.list_instance_actions(self.server['id']) ['instanceActions']) self.assertEqual(len(body), 2, str(body)) self.assertEqual(sorted([i['action'] for i in body]), ['create', 'reboot']) @decorators.idempotent_id('aacc71ca-1d70-4aa5-bbf6-0ff71470e43c') def test_get_instance_action(self): # Get the action details of the provided server body = self.client.show_instance_action( self.server['id'], self.request_id)['instanceAction'] self.assertEqual(self.server['id'], body['instance_uuid']) self.assertEqual('create', body['action']) class InstanceActionsV221TestJSON(base.BaseV2ComputeTest): min_microversion = '2.21' max_microversion = 'latest' @classmethod def setup_clients(cls): super(InstanceActionsV221TestJSON, cls).setup_clients() cls.client = cls.servers_client @decorators.idempotent_id('0a0f85d4-10fa-41f6-bf80-a54fb4aa2ae1') def test_get_list_deleted_instance_actions(self): # List actions of the deleted server server = self.create_test_server(wait_until='ACTIVE') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) body = (self.client.list_instance_actions(server['id']) ['instanceActions']) self.assertEqual(len(body), 2, str(body)) self.assertEqual(sorted([i['action'] for i in body]), ['create', 'delete']) tempest-17.2.0/tempest/api/compute/servers/test_server_addresses_negative.py0000666000175100017510000000421313207044712027545 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServerAddressesNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): cls.set_network_resources(network=True, subnet=True) super(ServerAddressesNegativeTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(ServerAddressesNegativeTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerAddressesNegativeTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') @decorators.attr(type=['negative']) @decorators.idempotent_id('02c3f645-2d2e-4417-8525-68c0407d001b') @utils.services('network') def test_list_server_addresses_invalid_server_id(self): # List addresses request should fail if server id not in system self.assertRaises(lib_exc.NotFound, self.client.list_addresses, '999') @decorators.attr(type=['negative']) @decorators.idempotent_id('a2ab5144-78c0-4942-a0ed-cc8edccfd9ba') @utils.services('network') def test_list_server_addresses_by_network_neg(self): # List addresses by network should fail if network name not valid self.assertRaises(lib_exc.NotFound, self.client.list_addresses_by_network, self.server['id'], 'invalid') tempest-17.2.0/tempest/api/compute/servers/test_server_metadata_negative.py0000666000175100017510000001715213207044712027356 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServerMetadataNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(ServerMetadataNegativeTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerMetadataNegativeTestJSON, cls).resource_setup() cls.tenant_id = cls.client.tenant_id cls.server = cls.create_test_server(metadata={}, wait_until='ACTIVE') @decorators.attr(type=['negative']) @decorators.idempotent_id('fe114a8f-3a57-4eff-9ee2-4e14628df049') def test_server_create_metadata_key_too_long(self): # Attempt to start a server with a meta-data key that is > 255 # characters # Tryset_server_metadata_item a few values for sz in [256, 257, 511, 1023]: key = "k" * sz meta = {key: 'data1'} self.assertRaises((lib_exc.BadRequest, lib_exc.OverLimit), self.create_test_server, metadata=meta) # no teardown - all creates should fail @decorators.attr(type=['negative']) @decorators.idempotent_id('92431555-4d8b-467c-b95b-b17daa5e57ff') def test_create_server_metadata_blank_key(self): # Blank key should trigger an error. meta = {'': 'data1'} self.assertRaises(lib_exc.BadRequest, self.create_test_server, metadata=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('4d9cd7a3-2010-4b41-b8fe-3bbf0b169466') def test_server_metadata_non_existent_server(self): # GET on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.show_server_metadata_item, non_existent_server_id, 'test2') @decorators.attr(type=['negative']) @decorators.idempotent_id('f408e78e-3066-4097-9299-3b0182da812e') def test_list_server_metadata_non_existent_server(self): # List metadata on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.list_server_metadata, non_existent_server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('0025fbd6-a4ba-4cde-b8c2-96805dcfdabc') def test_wrong_key_passed_in_body(self): # Raise BadRequest if key in uri does not match # the key passed in body. meta = {'testkey': 'testvalue'} self.assertRaises(lib_exc.BadRequest, self.client.set_server_metadata_item, self.server['id'], 'key', meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('0df38c2a-3d4e-4db5-98d8-d4d9fa843a12') def test_set_metadata_non_existent_server(self): # Set metadata on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() meta = {'meta1': 'data1'} self.assertRaises(lib_exc.NotFound, self.client.set_server_metadata, non_existent_server_id, meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('904b13dc-0ef2-4e4c-91cd-3b4a0f2f49d8') def test_update_metadata_non_existent_server(self): # An update should not happen for a non-existent server non_existent_server_id = data_utils.rand_uuid() meta = {'key1': 'value1', 'key2': 'value2'} self.assertRaises(lib_exc.NotFound, self.client.update_server_metadata, non_existent_server_id, meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('a452f38c-05c2-4b47-bd44-a4f0bf5a5e48') def test_update_metadata_with_blank_key(self): # Blank key should trigger an error meta = {'': 'data1'} self.assertRaises(lib_exc.BadRequest, self.client.update_server_metadata, self.server['id'], meta=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('6bbd88e1-f8b3-424d-ba10-ae21c45ada8d') def test_delete_metadata_non_existent_server(self): # Should not be able to delete metadata item from a non-existent server non_existent_server_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.delete_server_metadata_item, non_existent_server_id, 'd') @decorators.attr(type=['negative']) @decorators.idempotent_id('d8c0a210-a5c3-4664-be04-69d96746b547') def test_metadata_items_limit(self): # A 403 Forbidden or 413 Overlimit (old behaviour) exception # will be raised while exceeding metadata items limit for # tenant. quota_set = self.quotas_client.show_quota_set( self.tenant_id)['quota_set'] quota_metadata = quota_set['metadata_items'] if quota_metadata == -1: raise self.skipException("No limit for metadata_items") req_metadata = {} for num in range(1, quota_metadata + 2): req_metadata['key' + str(num)] = 'val' + str(num) self.assertRaises((lib_exc.OverLimit, lib_exc.Forbidden), self.client.set_server_metadata, self.server['id'], req_metadata) # A 403 Forbidden or 413 Overlimit (old behaviour) exception # will be raised while exceeding metadata items limit for # tenant. self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.client.update_server_metadata, self.server['id'], req_metadata) @decorators.attr(type=['negative']) @decorators.idempotent_id('96100343-7fa9-40d8-80fa-d29ef588ce1c') def test_set_server_metadata_blank_key(self): # Raise a bad request error for blank key. # set_server_metadata will replace all metadata with new value meta = {'': 'data1'} self.assertRaises(lib_exc.BadRequest, self.client.set_server_metadata, self.server['id'], meta=meta) @decorators.attr(type=['negative']) @decorators.idempotent_id('64a91aee-9723-4863-be44-4c9d9f1e7d0e') def test_set_server_metadata_missing_metadata(self): # Raise a bad request error for a missing metadata field # set_server_metadata will replace all metadata with new value meta = {'meta1': 'data1'} self.assertRaises(lib_exc.BadRequest, self.client.set_server_metadata, self.server['id'], meta=meta, no_metadata_field=True) tempest-17.2.0/tempest/api/compute/servers/test_create_server.py0000666000175100017510000001511713207044712025156 0ustar zuulzuul00000000000000# 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 netaddr import testtools from tempest.api.compute import base from tempest.common import utils from tempest.common.utils.linux import remote_client from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ServersTestJSON(base.BaseV2ComputeTest): disk_config = 'AUTO' volume_backed = False @classmethod def setup_credentials(cls): cls.prepare_instance_network() super(ServersTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(ServersTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServersTestJSON, cls).resource_setup() validation_resources = cls.get_class_validation_resources( cls.os_primary) cls.meta = {'hello': 'world'} cls.accessIPv4 = '1.1.1.1' cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2' cls.name = data_utils.rand_name(cls.__name__ + '-server') cls.password = data_utils.rand_password() disk_config = cls.disk_config server_initial = cls.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE', name=cls.name, metadata=cls.meta, accessIPv4=cls.accessIPv4, accessIPv6=cls.accessIPv6, disk_config=disk_config, adminPass=cls.password, volume_backed=cls.volume_backed) cls.server = (cls.client.show_server(server_initial['id']) ['server']) @decorators.attr(type='smoke') @decorators.idempotent_id('5de47127-9977-400a-936f-abcfbec1218f') def test_verify_server_details(self): # Verify the specified server attributes are set correctly self.assertEqual(self.accessIPv4, self.server['accessIPv4']) # NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4) # Here we compare directly with the canonicalized format. self.assertEqual(self.server['accessIPv6'], str(netaddr.IPAddress(self.accessIPv6))) self.assertEqual(self.name, self.server['name']) if self.volume_backed: # Image is an empty string as per documentation self.assertEqual("", self.server['image']) else: self.assertEqual(self.image_ref, self.server['image']['id']) self.assertEqual(self.flavor_ref, self.server['flavor']['id']) self.assertEqual(self.meta, self.server['metadata']) @decorators.attr(type='smoke') @decorators.idempotent_id('9a438d88-10c6-4bcd-8b5b-5b6e25e1346f') def test_list_servers(self): # The created server should be in the list of all servers body = self.client.list_servers() servers = body['servers'] found = [i for i in servers if i['id'] == self.server['id']] self.assertNotEmpty(found) @decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997') def test_list_servers_with_detail(self): # The created server should be in the detailed list of all servers body = self.client.list_servers(detail=True) servers = body['servers'] found = [i for i in servers if i['id'] == self.server['id']] self.assertNotEmpty(found) @decorators.idempotent_id('cbc0f52f-05aa-492b-bdc1-84b575ca294b') @testtools.skipUnless(CONF.validation.run_validation, 'Instance validation tests are disabled.') def test_verify_created_server_vcpus(self): # Verify that the number of vcpus reported by the instance matches # the amount stated by the flavor flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] validation_resources = self.get_class_validation_resources( self.os_primary) linux_client = remote_client.RemoteClient( self.get_server_ip(self.server, validation_resources), self.ssh_user, self.password, validation_resources['keypair']['private_key'], server=self.server, servers_client=self.client) output = linux_client.exec_command('grep -c ^processor /proc/cpuinfo') self.assertEqual(flavor['vcpus'], int(output)) @decorators.idempotent_id('ac1ad47f-984b-4441-9274-c9079b7a0666') @testtools.skipUnless(CONF.validation.run_validation, 'Instance validation tests are disabled.') def test_host_name_is_same_as_server_name(self): # Verify the instance host name is the same as the server name validation_resources = self.get_class_validation_resources( self.os_primary) linux_client = remote_client.RemoteClient( self.get_server_ip(self.server, validation_resources), self.ssh_user, self.password, validation_resources['keypair']['private_key'], server=self.server, servers_client=self.client) hostname = linux_client.exec_command("hostname").rstrip() msg = ('Failed while verifying servername equals hostname. Expected ' 'hostname "%s" but got "%s".' % (self.name, hostname)) self.assertEqual(self.name.lower(), hostname, msg) class ServersTestManualDisk(ServersTestJSON): disk_config = 'MANUAL' @classmethod def skip_checks(cls): super(ServersTestManualDisk, cls).skip_checks() if not CONF.compute_feature_enabled.disk_config: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) class ServersTestBootFromVolume(ServersTestJSON): """Run the `ServersTestJSON` tests with a volume backed VM""" volume_backed = True @classmethod def skip_checks(cls): super(ServersTestBootFromVolume, cls).skip_checks() if not utils.get_service_list()['volume']: msg = "Volume service not enabled." raise cls.skipException(msg) tempest-17.2.0/tempest/api/compute/servers/test_server_metadata.py0000666000175100017510000001171613207044712025474 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators class ServerMetadataTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(ServerMetadataTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerMetadataTestJSON, cls).resource_setup() cls.server = cls.create_test_server(metadata={}, wait_until='ACTIVE') def setUp(self): super(ServerMetadataTestJSON, self).setUp() meta = {'key1': 'value1', 'key2': 'value2'} self.client.set_server_metadata(self.server['id'], meta) @decorators.idempotent_id('479da087-92b3-4dcf-aeb3-fd293b2d14ce') def test_list_server_metadata(self): # All metadata key/value pairs for a server should be returned resp_metadata = (self.client.list_server_metadata(self.server['id']) ['metadata']) # Verify the expected metadata items are in the list expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('211021f6-21de-4657-a68f-908878cfe251') def test_set_server_metadata(self): # The server's metadata should be replaced with the provided values # Create a new set of metadata for the server req_metadata = {'meta2': 'data2', 'meta3': 'data3'} self.client.set_server_metadata(self.server['id'], req_metadata) # Verify the expected values are correct, and that the # previous values have been removed resp_metadata = (self.client.list_server_metadata(self.server['id']) ['metadata']) self.assertEqual(resp_metadata, req_metadata) @decorators.idempotent_id('344d981e-0c33-4997-8a5d-6c1d803e4134') def test_update_server_metadata(self): # The server's metadata values should be updated to the # provided values meta = {'key1': 'alt1', 'key3': 'value3'} self.client.update_server_metadata(self.server['id'], meta) # Verify the values have been updated to the proper values resp_metadata = (self.client.list_server_metadata(self.server['id']) ['metadata']) expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('0f58d402-e34a-481d-8af8-b392b17426d9') def test_update_metadata_empty_body(self): # The original metadata should not be lost if empty metadata body is # passed meta = {} self.client.update_server_metadata(self.server['id'], meta) resp_metadata = (self.client.list_server_metadata(self.server['id']) ['metadata']) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('3043c57d-7e0e-49a6-9a96-ad569c265e6a') def test_get_server_metadata_item(self): # The value for a specific metadata key should be returned meta = self.client.show_server_metadata_item(self.server['id'], 'key2')['meta'] self.assertEqual('value2', meta['key2']) @decorators.idempotent_id('58c02d4f-5c67-40be-8744-d3fa5982eb1c') def test_set_server_metadata_item(self): # The item's value should be updated to the provided value # Update the metadata value meta = {'nova': 'alt'} self.client.set_server_metadata_item(self.server['id'], 'nova', meta) # Verify the meta item's value has been updated resp_metadata = (self.client.list_server_metadata(self.server['id']) ['metadata']) expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'} self.assertEqual(expected, resp_metadata) @decorators.idempotent_id('127642d6-4c7b-4486-b7cd-07265a378658') def test_delete_server_metadata_item(self): # The metadata value/key pair should be deleted from the server self.client.delete_server_metadata_item(self.server['id'], 'key1') # Verify the metadata item has been removed resp_metadata = (self.client.list_server_metadata(self.server['id']) ['metadata']) expected = {'key2': 'value2'} self.assertEqual(expected, resp_metadata) tempest-17.2.0/tempest/api/compute/servers/test_list_servers_negative.py0000666000175100017510000001365313207044712026736 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import waiters from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ListServersNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(ListServersNegativeTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ListServersNegativeTestJSON, cls).resource_setup() # The following servers are created for use # by the test methods in this class. These # servers are cleaned up automatically in the # tearDownClass method of the super-class. body = cls.create_test_server(wait_until='ACTIVE', min_count=3) # delete one of the created servers cls.deleted_id = body['server']['id'] cls.client.delete_server(cls.deleted_id) waiters.wait_for_server_termination(cls.client, cls.deleted_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('24a26f1a-1ddc-4eea-b0d7-a90cc874ad8f') def test_list_servers_with_a_deleted_server(self): # Verify deleted servers do not show by default in list servers # List servers and verify server not returned body = self.client.list_servers() servers = body['servers'] actual = [srv for srv in servers if srv['id'] == self.deleted_id] self.assertEmpty(actual) @decorators.attr(type=['negative']) @decorators.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe') def test_list_servers_by_non_existing_image(self): # Listing servers for a non existing image returns empty list body = self.client.list_servers(image='non_existing_image') servers = body['servers'] self.assertEmpty(servers) @decorators.attr(type=['negative']) @decorators.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75') def test_list_servers_by_non_existing_flavor(self): # Listing servers by non existing flavor returns empty list body = self.client.list_servers(flavor='non_existing_flavor') servers = body['servers'] self.assertEmpty(servers) @decorators.attr(type=['negative']) @decorators.idempotent_id('e2c77c4a-000a-4af3-a0bd-629a328bde7c') def test_list_servers_by_non_existing_server_name(self): # Listing servers for a non existent server name returns empty list body = self.client.list_servers(name='non_existing_server_name') servers = body['servers'] self.assertEmpty(servers) @decorators.attr(type=['negative']) @decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4') def test_list_servers_status_non_existing(self): # Return an empty list when invalid status is specified body = self.client.list_servers(status='non_existing_status') servers = body['servers'] self.assertEmpty(servers) @decorators.attr(type=['negative']) @decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18') def test_list_servers_by_limits_greater_than_actual_count(self): # Gather the complete list of servers in the project for reference full_list = self.client.list_servers()['servers'] # List servers by specifying a greater value for limit limit = len(full_list) + 100 body = self.client.list_servers(limit=limit) self.assertEqual(len(full_list), len(body['servers'])) @decorators.attr(type=['negative']) @decorators.idempotent_id('679bc053-5e70-4514-9800-3dfab1a380a6') def test_list_servers_by_limits_pass_string(self): # Return an error if a string value is passed for limit self.assertRaises(lib_exc.BadRequest, self.client.list_servers, limit='testing') @decorators.attr(type=['negative']) @decorators.idempotent_id('62610dd9-4713-4ee0-8beb-fd2c1aa7f950') def test_list_servers_by_limits_pass_negative_value(self): # Return an error if a negative value for limit is passed self.assertRaises(lib_exc.BadRequest, self.client.list_servers, limit=-1) @decorators.attr(type=['negative']) @decorators.idempotent_id('87d12517-e20a-4c9c-97b6-dd1628d6d6c9') def test_list_servers_by_changes_since_invalid_date(self): # Return an error when invalid date format is passed params = {'changes-since': '2011/01/01'} self.assertRaises(lib_exc.BadRequest, self.client.list_servers, **params) @decorators.attr(type=['negative']) @decorators.idempotent_id('74745ad8-b346-45b5-b9b8-509d7447fc1f') def test_list_servers_by_changes_since_future_date(self): # Return an empty list when a date in the future is passed changes_since = {'changes-since': '2051-01-01T12:34:00Z'} body = self.client.list_servers(**changes_since) self.assertEmpty(body['servers']) @decorators.attr(type=['negative']) @decorators.idempotent_id('93055106-2d34-46fe-af68-d9ddbf7ee570') def test_list_servers_detail_server_is_deleted(self): # Server details are not listed for a deleted server body = self.client.list_servers(detail=True) servers = body['servers'] actual = [srv for srv in servers if srv['id'] == self.deleted_id] self.assertEmpty(actual) tempest-17.2.0/tempest/api/compute/servers/test_list_server_filters.py0000666000175100017510000003524113207044712026416 0ustar zuulzuul00000000000000# 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 from tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common import fixed_network from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ListServerFiltersTestJSON(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): cls.set_network_resources(network=True, subnet=True, dhcp=True) super(ListServerFiltersTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(ListServerFiltersTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ListServerFiltersTestJSON, cls).resource_setup() network = cls.get_tenant_network() if network: cls.fixed_network_name = network.get('name') else: cls.fixed_network_name = None network_kwargs = fixed_network.set_networks_kwarg(network) cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance') cls.s1 = cls.create_test_server(name=cls.s1_name, **network_kwargs) cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance') # If image_ref_alt is "" or None then we still want to boot a server # but we rely on `testtools.skipUnless` decorator to actually skip # the irrelevant tests. cls.s2 = cls.create_test_server( name=cls.s2_name, image_id=cls.image_ref_alt or cls.image_ref) cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance') cls.s3 = cls.create_test_server(name=cls.s3_name, flavor=cls.flavor_ref_alt, wait_until='ACTIVE') waiters.wait_for_server_status(cls.client, cls.s1['id'], 'ACTIVE') waiters.wait_for_server_status(cls.client, cls.s2['id'], 'ACTIVE') @decorators.idempotent_id('05e8a8e7-9659-459a-989d-92c2f501f4ba') @testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt, "Need distinct images to run this test") def test_list_servers_filter_by_image(self): # Filter the list of servers by image params = {'image': self.image_ref} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @decorators.idempotent_id('573637f5-7325-47bb-9144-3476d0416908') def test_list_servers_filter_by_flavor(self): # Filter the list of servers by flavor params = {'flavor': self.flavor_ref_alt} body = self.client.list_servers(**params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @decorators.idempotent_id('9b067a7b-7fee-4f6a-b29c-be43fe18fc5a') def test_list_servers_filter_by_server_name(self): # Filter the list of servers by server name params = {'name': self.s1_name} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @decorators.idempotent_id('ca78e20e-fddb-4ce6-b7f7-bcbf8605e66e') def test_list_servers_filter_by_active_status(self): # Filter the list of servers by server active status params = {'status': 'active'} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @decorators.idempotent_id('451dbbb2-f330-4a9f-b0e1-5f5d2cb0f34c') def test_list_servers_filter_by_shutoff_status(self): # Filter the list of servers by server shutoff status params = {'status': 'shutoff'} self.client.stop_server(self.s1['id']) waiters.wait_for_server_status(self.client, self.s1['id'], 'SHUTOFF') body = self.client.list_servers(**params) self.client.start_server(self.s1['id']) waiters.wait_for_server_status(self.client, self.s1['id'], 'ACTIVE') servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s3['id'], map(lambda x: x['id'], servers)) @decorators.idempotent_id('614cdfc1-d557-4bac-915b-3e67b48eee76') def test_list_servers_filter_by_limit(self): # Verify only the expected number of servers are returned params = {'limit': 1} servers = self.client.list_servers(**params) self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x])) @decorators.idempotent_id('b1495414-2d93-414c-8019-849afe8d319e') def test_list_servers_filter_by_zero_limit(self): # Verify only the expected number of servers are returned params = {'limit': 0} servers = self.client.list_servers(**params) self.assertEmpty(servers['servers']) @decorators.idempotent_id('37791bbd-90c0-4de0-831e-5f38cba9c6b3') def test_list_servers_filter_by_exceed_limit(self): # Verify only the expected number of servers are returned params = {'limit': 100000} servers = self.client.list_servers(**params) all_servers = self.client.list_servers() self.assertEqual(len([x for x in all_servers['servers'] if 'id' in x]), len([x for x in servers['servers'] if 'id' in x])) @decorators.idempotent_id('b3304c3b-97df-46d2-8cd3-e2b6659724e7') @testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt, "Need distinct images to run this test") def test_list_servers_detailed_filter_by_image(self): # Filter the detailed list of servers by image params = {'image': self.image_ref} body = self.client.list_servers(detail=True, **params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @decorators.idempotent_id('80c574cc-0925-44ba-8602-299028357dd9') def test_list_servers_detailed_filter_by_flavor(self): # Filter the detailed list of servers by flavor params = {'flavor': self.flavor_ref_alt} body = self.client.list_servers(detail=True, **params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @decorators.idempotent_id('f9eb2b70-735f-416c-b260-9914ac6181e4') def test_list_servers_detailed_filter_by_server_name(self): # Filter the detailed list of servers by server name params = {'name': self.s1_name} body = self.client.list_servers(detail=True, **params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @decorators.idempotent_id('de2612ab-b7dd-4044-b0b1-d2539601911f') def test_list_servers_detailed_filter_by_server_status(self): # Filter the detailed list of servers by server status params = {'status': 'active'} body = self.client.list_servers(detail=True, **params) servers = body['servers'] test_ids = [s['id'] for s in (self.s1, self.s2, self.s3)] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers if x['id'] in test_ids]) @decorators.idempotent_id('e9f624ee-92af-4562-8bec-437945a18dcb') def test_list_servers_filtered_by_name_wildcard(self): # List all servers that contains '-instance' in name params = {'name': '-instance'} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) # Let's take random part of name and try to search it part_name = self.s1_name[6:-1] params = {'name': part_name} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @decorators.idempotent_id('24a89b0c-0d55-4a28-847f-45075f19b27b') def test_list_servers_filtered_by_name_regex(self): # list of regex that should match s1, s2 and s3 regexes = ['^.*\-instance\-[0-9]+$', '^.*\-instance\-.*$'] for regex in regexes: params = {'name': regex} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) # Let's take random part of name and try to search it part_name = self.s1_name[-10:] params = {'name': part_name} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @decorators.idempotent_id('43a1242e-7b31-48d1-88f2-3f72aa9f2077') def test_list_servers_filtered_by_ip(self): # Filter servers by ip # Here should be listed 1 server if not self.fixed_network_name: msg = 'fixed_network_name needs to be configured to run this test' raise self.skipException(msg) # list servers filter by ip is something "regexp match", i.e, # filter by "10.1.1.1" will return both "10.1.1.1" and "10.1.1.10". # so here look for the longest server ip, and filter by that ip, # so as to ensure only one server is returned. ip_list = {} self.s1 = self.client.show_server(self.s1['id'])['server'] # Get first ip address in spite of v4 or v6 ip_addr = self.s1['addresses'][self.fixed_network_name][0]['addr'] ip_list[ip_addr] = self.s1['id'] self.s2 = self.client.show_server(self.s2['id'])['server'] ip_addr = self.s2['addresses'][self.fixed_network_name][0]['addr'] ip_list[ip_addr] = self.s2['id'] self.s3 = self.client.show_server(self.s3['id'])['server'] ip_addr = self.s3['addresses'][self.fixed_network_name][0]['addr'] ip_list[ip_addr] = self.s3['id'] longest_ip = max([[len(ip), ip] for ip in ip_list])[1] params = {'ip': longest_ip} body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(ip_list[longest_ip], map(lambda x: x['id'], servers)) del ip_list[longest_ip] for ip in ip_list: self.assertNotIn(ip_list[ip], map(lambda x: x['id'], servers)) @decorators.skip_because(bug="1540645") @decorators.idempotent_id('a905e287-c35e-42f2-b132-d02b09f3654a') def test_list_servers_filtered_by_ip_regex(self): # Filter servers by regex ip # List all servers filtered by part of ip address. # Here should be listed all servers if not self.fixed_network_name: msg = 'fixed_network_name needs to be configured to run this test' raise self.skipException(msg) self.s1 = self.client.show_server(self.s1['id'])['server'] addr_spec = self.s1['addresses'][self.fixed_network_name][0] ip = addr_spec['addr'][0:-3] if addr_spec['version'] == 4: params = {'ip': ip} else: params = {'ip6': ip} # capture all servers in case something goes wrong all_servers = self.client.list_servers(detail=True) body = self.client.list_servers(**params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers), "%s not found in %s, all servers %s" % (self.s1_name, servers, all_servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers), "%s not found in %s, all servers %s" % (self.s2_name, servers, all_servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers), "%s not found in %s, all servers %s" % (self.s3_name, servers, all_servers)) @decorators.idempotent_id('67aec2d0-35fe-4503-9f92-f13272b867ed') def test_list_servers_detailed_limit_results(self): # Verify only the expected number of detailed results are returned params = {'limit': 1} servers = self.client.list_servers(detail=True, **params) self.assertEqual(1, len(servers['servers'])) tempest-17.2.0/tempest/api/compute/servers/test_server_rescue.py0000666000175100017510000001002213207044712025167 0ustar zuulzuul00000000000000# 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 testtools from tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ServerRescueTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ServerRescueTestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.rescue: msg = "Server rescue not available." raise cls.skipException(msg) @classmethod def setup_credentials(cls): cls.set_network_resources(network=True, subnet=True, router=True) super(ServerRescueTestJSON, cls).setup_credentials() @classmethod def resource_setup(cls): super(ServerRescueTestJSON, cls).resource_setup() password = data_utils.rand_password() server = cls.create_test_server(adminPass=password, wait_until='ACTIVE') cls.servers_client.rescue_server(server['id'], adminPass=password) waiters.wait_for_server_status(cls.servers_client, server['id'], 'RESCUE') cls.rescued_server_id = server['id'] @decorators.idempotent_id('fd032140-714c-42e4-a8fd-adcd8df06be6') def test_rescue_unrescue_instance(self): password = data_utils.rand_password() server = self.create_test_server(adminPass=password, wait_until='ACTIVE') self.servers_client.rescue_server(server['id'], adminPass=password) waiters.wait_for_server_status(self.servers_client, server['id'], 'RESCUE') self.servers_client.unrescue_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') @decorators.idempotent_id('4842e0cf-e87d-4d9d-b61f-f4791da3cacc') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') @testtools.skipUnless(CONF.network_feature_enabled.floating_ips, "Floating ips are not available") def test_rescued_vm_associate_dissociate_floating_ip(self): # Association of floating IP to a rescued vm floating_ip_body = self.floating_ips_client.create_floating_ip( pool=CONF.network.floating_network_name)['floating_ip'] self.addCleanup(self.floating_ips_client.delete_floating_ip, floating_ip_body['id']) self.floating_ips_client.associate_floating_ip_to_server( str(floating_ip_body['ip']).strip(), self.rescued_server_id) # Disassociation of floating IP that was associated in this method self.floating_ips_client.disassociate_floating_ip_from_server( str(floating_ip_body['ip']).strip(), self.rescued_server_id) @decorators.idempotent_id('affca41f-7195-492d-8065-e09eee245404') def test_rescued_vm_add_remove_security_group(self): # Add Security group sg = self.create_security_group() self.servers_client.add_security_group(self.rescued_server_id, name=sg['name']) # Delete Security group self.servers_client.remove_security_group(self.rescued_server_id, name=sg['name']) tempest-17.2.0/tempest/api/compute/servers/test_multiple_create.py0000666000175100017510000000361213207044712025500 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import compute from tempest.lib import decorators class MultipleCreateTestJSON(base.BaseV2ComputeTest): @decorators.idempotent_id('61e03386-89c3-449c-9bb1-a06f423fd9d1') def test_multiple_create(self): tenant_network = self.get_tenant_network() body, servers = compute.create_test_server( self.os_primary, wait_until='ACTIVE', min_count=2, tenant_network=tenant_network) for server in servers: self.addCleanup(self.servers_client.delete_server, server['id']) # NOTE(maurosr): do status response check and also make sure that # reservation_id is not in the response body when the request send # contains return_reservation_id=False self.assertNotIn('reservation_id', body) self.assertEqual(2, len(servers)) @decorators.idempotent_id('864777fb-2f1e-44e3-b5b9-3eb6fa84f2f7') def test_multiple_create_with_reservation_return(self): body = self.create_test_server(wait_until='ACTIVE', min_count=1, max_count=2, return_reservation_id=True) self.assertIn('reservation_id', body) tempest-17.2.0/tempest/api/compute/servers/test_servers.py0000666000175100017510000002000113207044712024002 0ustar zuulzuul00000000000000# 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 from tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class ServersTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(ServersTestJSON, cls).setup_clients() cls.client = cls.servers_client @decorators.idempotent_id('b92d5ec7-b1dd-44a2-87e4-45e888c46ef0') @testtools.skipUnless(CONF.compute_feature_enabled. enable_instance_password, 'Instance password not available.') def test_create_server_with_admin_password(self): # If an admin password is provided on server creation, the server's # root password should be set to that password. server = self.create_test_server(adminPass='testpassword') self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server['id']) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server['id']) # Verify the password is set correctly in the response self.assertEqual('testpassword', server['adminPass']) @decorators.idempotent_id('8fea6be7-065e-47cf-89b8-496e6f96c699') def test_create_with_existing_server_name(self): # Creating a server with a name that already exists is allowed # TODO(sdague): clear out try, we do cleanup one layer up server_name = data_utils.rand_name( self.__class__.__name__ + '-server') server = self.create_test_server(name=server_name, wait_until='ACTIVE') id1 = server['id'] self.addCleanup(waiters.wait_for_server_termination, self.servers_client, id1) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, id1) server = self.create_test_server(name=server_name, wait_until='ACTIVE') id2 = server['id'] self.addCleanup(waiters.wait_for_server_termination, self.servers_client, id2) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, id2) self.assertNotEqual(id1, id2, "Did not create a new server") server = self.client.show_server(id1)['server'] name1 = server['name'] server = self.client.show_server(id2)['server'] name2 = server['name'] self.assertEqual(name1, name2) @decorators.idempotent_id('f9e15296-d7f9-4e62-b53f-a04e89160833') def test_create_specify_keypair(self): # Specify a keypair while creating a server key_name = data_utils.rand_name('key') self.keypairs_client.create_keypair(name=key_name) self.addCleanup(self.keypairs_client.delete_keypair, key_name) self.keypairs_client.list_keypairs() server = self.create_test_server(key_name=key_name) self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server['id']) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server['id']) waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') server = self.client.show_server(server['id'])['server'] self.assertEqual(key_name, server['key_name']) def _update_server_name(self, server_id, status, prefix_name='server'): # The server name should be changed to the provided value new_name = data_utils.rand_name(prefix_name) # Update the server with a new name self.client.update_server(server_id, name=new_name) waiters.wait_for_server_status(self.client, server_id, status) # Verify the name of the server has changed server = self.client.show_server(server_id)['server'] self.assertEqual(new_name, server['name']) return server @decorators.idempotent_id('5e6ccff8-349d-4852-a8b3-055df7988dd2') def test_update_server_name(self): # The server name should be changed to the provided value server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server['id']) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server['id']) # Update instance name with non-ASCII characters prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9' self._update_server_name(server['id'], 'ACTIVE', prefix_name) # stop server and check server name update again self.client.stop_server(server['id']) waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF') # Update instance name with non-ASCII characters updated_server = self._update_server_name(server['id'], 'SHUTOFF', prefix_name) self.assertNotIn('progress', updated_server) @decorators.idempotent_id('89b90870-bc13-4b73-96af-f9d4f2b70077') def test_update_access_server_address(self): # The server's access addresses should reflect the provided values server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server['id']) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server['id']) # Update the IPv4 and IPv6 access addresses self.client.update_server(server['id'], accessIPv4='1.1.1.1', accessIPv6='::babe:202:202') waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') # Verify the access addresses have been updated server = self.client.show_server(server['id'])['server'] self.assertEqual('1.1.1.1', server['accessIPv4']) self.assertEqual('::babe:202:202', server['accessIPv6']) @decorators.idempotent_id('38fb1d02-c3c5-41de-91d3-9bc2025a75eb') def test_create_server_with_ipv6_addr_only(self): # Create a server without an IPv4 address(only IPv6 address). server = self.create_test_server(accessIPv6='2001:2001::3') self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server['id']) self.addCleanup( test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server['id']) waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') server = self.client.show_server(server['id'])['server'] self.assertEqual('2001:2001::3', server['accessIPv6']) class ServerShowV247Test(base.BaseV2ComputeTest): min_microversion = '2.47' max_microversion = 'latest' @decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33') def test_show_server(self): server = self.create_test_server() # All fields will be checked by API schema self.servers_client.show_server(server['id']) tempest-17.2.0/tempest/api/compute/servers/test_server_rescue_negative.py0000666000175100017510000001456213207044712027066 0ustar zuulzuul00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 testtools from tempest.api.compute import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ServerRescueNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ServerRescueNegativeTestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.rescue: msg = "Server rescue not available." raise cls.skipException(msg) @classmethod def setup_credentials(cls): cls.set_network_resources(network=True, subnet=True, router=True) super(ServerRescueNegativeTestJSON, cls).setup_credentials() @classmethod def resource_setup(cls): super(ServerRescueNegativeTestJSON, cls).resource_setup() cls.device = CONF.compute.volume_device_name cls.password = data_utils.rand_password() rescue_password = data_utils.rand_password() # Server for negative tests server = cls.create_test_server(adminPass=cls.password, wait_until='BUILD') resc_server = cls.create_test_server(adminPass=rescue_password, wait_until='ACTIVE') cls.server_id = server['id'] cls.rescue_id = resc_server['id'] cls.servers_client.rescue_server( cls.rescue_id, adminPass=rescue_password) waiters.wait_for_server_status(cls.servers_client, cls.rescue_id, 'RESCUE') waiters.wait_for_server_status(cls.servers_client, cls.server_id, 'ACTIVE') def _unrescue(self, server_id): self.servers_client.unrescue_server(server_id) waiters.wait_for_server_status(self.servers_client, server_id, 'ACTIVE') def _unpause(self, server_id): self.servers_client.unpause_server(server_id) waiters.wait_for_server_status(self.servers_client, server_id, 'ACTIVE') @decorators.idempotent_id('cc3a883f-43c0-4fb6-a9bb-5579d64984ed') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @decorators.attr(type=['negative']) def test_rescue_paused_instance(self): # Rescue a paused server self.servers_client.pause_server(self.server_id) self.addCleanup(self._unpause, self.server_id) waiters.wait_for_server_status(self.servers_client, self.server_id, 'PAUSED') self.assertRaises(lib_exc.Conflict, self.servers_client.rescue_server, self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('db22b618-f157-4566-a317-1b6d467a8094') def test_rescued_vm_reboot(self): self.assertRaises(lib_exc.Conflict, self.servers_client.reboot_server, self.rescue_id, type='HARD') @decorators.attr(type=['negative']) @decorators.idempotent_id('6dfc0a55-3a77-4564-a144-1587b7971dde') def test_rescue_non_existent_server(self): # Rescue a non-existing server non_existent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.servers_client.rescue_server, non_existent_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('70cdb8a1-89f8-437d-9448-8844fd82bf46') def test_rescued_vm_rebuild(self): self.assertRaises(lib_exc.Conflict, self.servers_client.rebuild_server, self.rescue_id, self.image_ref_alt) @decorators.idempotent_id('d0ccac79-0091-4cf4-a1ce-26162d0cc55f') @utils.services('volume') @decorators.attr(type=['negative']) def test_rescued_vm_attach_volume(self): volume = self.create_volume() # Rescue the server self.servers_client.rescue_server(self.server_id, adminPass=self.password) waiters.wait_for_server_status(self.servers_client, self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) # Attach the volume to the server self.assertRaises(lib_exc.Conflict, self.servers_client.attach_volume, self.server_id, volumeId=volume['id'], device='/dev/%s' % self.device) @decorators.idempotent_id('f56e465b-fe10-48bf-b75d-646cda3a8bc9') @utils.services('volume') @decorators.attr(type=['negative']) def test_rescued_vm_detach_volume(self): volume = self.create_volume() # Attach the volume to the server server = self.servers_client.show_server(self.server_id)['server'] self.attach_volume(server, volume, device='/dev/%s' % self.device) # Rescue the server self.servers_client.rescue_server(self.server_id, adminPass=self.password) waiters.wait_for_server_status(self.servers_client, self.server_id, 'RESCUE') # addCleanup is a LIFO queue self.addCleanup(self._unrescue, self.server_id) # Detach the volume from the server expecting failure self.assertRaises(lib_exc.Conflict, self.servers_client.detach_volume, self.server_id, volume['id']) tempest-17.2.0/tempest/api/compute/servers/test_servers_negative.py0000666000175100017510000006042213207044712025677 0ustar zuulzuul00000000000000# 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 sys import testtools from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ServersNegativeTestJSON(base.BaseV2ComputeTest): def setUp(self): super(ServersNegativeTestJSON, self).setUp() try: waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') except Exception: self.__class__.server_id = self.recreate_server(self.server_id) def tearDown(self): self.server_check_teardown() super(ServersNegativeTestJSON, self).tearDown() @classmethod def setup_clients(cls): super(ServersNegativeTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServersNegativeTestJSON, cls).resource_setup() server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] server = cls.create_test_server() cls.client.delete_server(server['id']) waiters.wait_for_server_termination(cls.client, server['id']) cls.deleted_server_id = server['id'] @decorators.attr(type=['negative']) @decorators.idempotent_id('dbbfd247-c40c-449e-8f6c-d2aa7c7da7cf') def test_server_name_blank(self): # Create a server with name parameter empty self.assertRaises(lib_exc.BadRequest, self.create_test_server, name='') @decorators.attr(type=['negative']) @decorators.idempotent_id('b8a7235e-5246-4a8f-a08e-b34877c6586f') @testtools.skipUnless(CONF.compute_feature_enabled.personality, 'Nova personality feature disabled') def test_personality_file_contents_not_encoded(self): # Use an unencoded file when creating a server with personality file_contents = 'This is a test file.' person = [{'path': '/etc/testfile.txt', 'contents': file_contents}] self.assertRaises(lib_exc.BadRequest, self.create_test_server, personality=person) @decorators.attr(type=['negative']) @decorators.idempotent_id('fcba1052-0a50-4cf3-b1ac-fae241edf02f') def test_create_with_invalid_image(self): # Create a server with an unknown image self.assertRaises(lib_exc.BadRequest, self.create_test_server, image_id=-1) @decorators.attr(type=['negative']) @decorators.idempotent_id('18f5227f-d155-4429-807c-ccb103887537') def test_create_with_invalid_flavor(self): # Create a server with an unknown flavor self.assertRaises(lib_exc.BadRequest, self.create_test_server, flavor=-1,) @decorators.attr(type=['negative']) @decorators.idempotent_id('7f70a4d1-608f-4794-9e56-cb182765972c') def test_invalid_access_ip_v4_address(self): # An access IPv4 address must match a valid address pattern IPv4 = '1.1.1.1.1.1' self.assertRaises(lib_exc.BadRequest, self.create_test_server, accessIPv4=IPv4) @decorators.attr(type=['negative']) @decorators.idempotent_id('5226dd80-1e9c-4d8a-b5f9-b26ca4763fd0') def test_invalid_ip_v6_address(self): # An access IPv6 address must match a valid address pattern IPv6 = 'notvalid' self.assertRaises(lib_exc.BadRequest, self.create_test_server, accessIPv6=IPv6) @decorators.idempotent_id('7ea45b3e-e770-46fa-bfcc-9daaf6d987c0') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @decorators.attr(type=['negative']) def test_resize_nonexistent_server(self): # Resize a non-existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.resize_server, nonexistent_server, self.flavor_ref) @decorators.idempotent_id('ced1a1d7-2ab6-45c9-b90f-b27d87b30efd') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @decorators.attr(type=['negative']) def test_resize_server_with_non_existent_flavor(self): # Resize a server with non-existent flavor nonexistent_flavor = data_utils.rand_uuid() self.assertRaises(lib_exc.BadRequest, self.client.resize_server, self.server_id, flavor_ref=nonexistent_flavor) @decorators.idempotent_id('45436a7d-a388-4a35-a9d8-3adc5d0d940b') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @decorators.attr(type=['negative']) def test_resize_server_with_null_flavor(self): # Resize a server with null flavor self.assertRaises(lib_exc.BadRequest, self.client.resize_server, self.server_id, flavor_ref="") @decorators.attr(type=['negative']) @decorators.idempotent_id('d4c023a0-9c55-4747-9dd5-413b820143c7') def test_reboot_non_existent_server(self): # Reboot a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.reboot_server, nonexistent_server, type='SOFT') @decorators.idempotent_id('d1417e7f-a509-41b5-a102-d5eed8613369') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @decorators.attr(type=['negative']) def test_pause_paused_server(self): # Pause a paused server. self.client.pause_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED') self.assertRaises(lib_exc.Conflict, self.client.pause_server, self.server_id) self.client.unpause_server(self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('98fa0458-1485-440f-873b-fe7f0d714930') def test_rebuild_deleted_server(self): # Rebuild a deleted server self.assertRaises(lib_exc.NotFound, self.client.rebuild_server, self.deleted_server_id, self.image_ref) @decorators.related_bug('1660878', status_code=409) @decorators.attr(type=['negative']) @decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984') def test_reboot_deleted_server(self): # Reboot a deleted server self.assertRaises(lib_exc.NotFound, self.client.reboot_server, self.deleted_server_id, type='SOFT') @decorators.attr(type=['negative']) @decorators.idempotent_id('d86141a7-906e-4731-b187-d64a2ea61422') def test_rebuild_non_existent_server(self): # Rebuild a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.rebuild_server, nonexistent_server, self.image_ref) @decorators.attr(type=['negative']) @decorators.idempotent_id('fd57f159-68d6-4c2a-902b-03070828a87e') def test_create_numeric_server_name(self): server_name = 12345 self.assertRaises(lib_exc.BadRequest, self.create_test_server, name=server_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('c3e0fb12-07fc-4d76-a22e-37409887afe8') def test_create_server_name_length_exceeds_256(self): # Create a server with name length exceeding 255 characters server_name = 'a' * 256 self.assertRaises(lib_exc.BadRequest, self.create_test_server, name=server_name) @decorators.attr(type=['negative']) @decorators.related_bug('1651064', status_code=500) @utils.services('volume') @decorators.idempotent_id('12146ac1-d7df-4928-ad25-b1f99e5286cd') def test_create_server_invalid_bdm_in_2nd_dict(self): volume = self.create_volume() bdm_1st = {"source_type": "image", "delete_on_termination": True, "boot_index": 0, "uuid": self.image_ref, "destination_type": "local"} bdm_2nd = {"source_type": "volume", "uuid": volume["id"], "destination_type": "invalid"} bdm = [bdm_1st, bdm_2nd] self.assertRaises(lib_exc.BadRequest, self.create_test_server, image_id=self.image_ref, block_device_mapping_v2=bdm) @decorators.attr(type=['negative']) @decorators.idempotent_id('4e72dc2d-44c5-4336-9667-f7972e95c402') def test_create_with_invalid_network_uuid(self): # Pass invalid network uuid while creating a server networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}] self.assertRaises(lib_exc.BadRequest, self.create_test_server, networks=networks) @decorators.attr(type=['negative']) @decorators.idempotent_id('7a2efc39-530c-47de-b875-2dd01c8d39bd') def test_create_with_non_existent_keypair(self): # Pass a non-existent keypair while creating a server key_name = data_utils.rand_name('key') self.assertRaises(lib_exc.BadRequest, self.create_test_server, key_name=key_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('7fc74810-0bd2-4cd7-8244-4f33a9db865a') def test_create_server_metadata_exceeds_length_limit(self): # Pass really long metadata while creating a server metadata = {'a': 'b' * 260} self.assertRaises((lib_exc.BadRequest, lib_exc.OverLimit), self.create_test_server, metadata=metadata) @decorators.attr(type=['negative']) @decorators.idempotent_id('aa8eed43-e2cb-4ebf-930b-da14f6a21d81') def test_update_name_of_non_existent_server(self): # Update name of a non-existent server nonexistent_server = data_utils.rand_uuid() new_name = data_utils.rand_name( self.__class__.__name__ + '-server') + '_updated' self.assertRaises(lib_exc.NotFound, self.client.update_server, nonexistent_server, name=new_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('38204696-17c6-44da-9590-40f87fb5a899') def test_update_server_set_empty_name(self): # Update name of the server to an empty string new_name = '' self.assertRaises(lib_exc.BadRequest, self.client.update_server, self.server_id, name=new_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('5c8e244c-dada-4590-9944-749c455b431f') def test_update_server_name_length_exceeds_256(self): # Update name of server exceed the name length limit new_name = 'a' * 256 self.assertRaises(lib_exc.BadRequest, self.client.update_server, self.server_id, name=new_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('1041b4e6-514b-4855-96a5-e974b60870a3') def test_delete_non_existent_server(self): # Delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.delete_server, nonexistent_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('75f79124-277c-45e6-a373-a1d6803f4cc4') def test_delete_server_pass_negative_id(self): # Pass an invalid string parameter to delete server self.assertRaises(lib_exc.NotFound, self.client.delete_server, -1) @decorators.attr(type=['negative']) @decorators.idempotent_id('f4d7279b-5fd2-4bf2-9ba4-ae35df0d18c5') def test_delete_server_pass_id_exceeding_length_limit(self): # Pass a server ID that exceeds length limit to delete server self.assertRaises(lib_exc.NotFound, self.client.delete_server, sys.maxsize + 1) @decorators.attr(type=['negative']) @decorators.idempotent_id('c5fa6041-80cd-483b-aa6d-4e45f19d093c') def test_create_with_nonexistent_security_group(self): # Create a server with a nonexistent security group security_groups = [{'name': 'does_not_exist'}] self.assertRaises(lib_exc.BadRequest, self.create_test_server, security_groups=security_groups) @decorators.attr(type=['negative']) @decorators.idempotent_id('3436b02f-1b1e-4f03-881e-c6a602327439') def test_get_non_existent_server(self): # Get a non existent server details nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.show_server, nonexistent_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('a31460a9-49e1-42aa-82ee-06e0bb7c2d03') def test_stop_non_existent_server(self): # Stop a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.servers_client.stop_server, nonexistent_server) @decorators.idempotent_id('6a8dc0c6-6cd4-4c0a-9f32-413881828091') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @decorators.attr(type=['negative']) def test_pause_non_existent_server(self): # pause a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.pause_server, nonexistent_server) @decorators.idempotent_id('705b8e3a-e8a7-477c-a19b-6868fc24ac75') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @decorators.attr(type=['negative']) def test_unpause_non_existent_server(self): # unpause a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.unpause_server, nonexistent_server) @decorators.idempotent_id('c8e639a7-ece8-42dd-a2e0-49615917ba4f') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @decorators.attr(type=['negative']) def test_unpause_server_invalid_state(self): # unpause an active server. self.assertRaises(lib_exc.Conflict, self.client.unpause_server, self.server_id) @decorators.idempotent_id('d1f032d5-7b6e-48aa-b252-d5f16dd994ca') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @decorators.attr(type=['negative']) def test_suspend_non_existent_server(self): # suspend a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.suspend_server, nonexistent_server) @decorators.idempotent_id('7f323206-05a9-4bf8-996b-dd5b2036501b') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @decorators.attr(type=['negative']) def test_suspend_server_invalid_state(self): # suspend a suspended server. self.client.suspend_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'SUSPENDED') self.assertRaises(lib_exc.Conflict, self.client.suspend_server, self.server_id) self.client.resume_server(self.server_id) @decorators.idempotent_id('221cd282-bddb-4837-a683-89c2487389b6') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @decorators.attr(type=['negative']) def test_resume_non_existent_server(self): # resume a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.resume_server, nonexistent_server) @decorators.idempotent_id('ccb6294d-c4c9-498f-8a43-554c098bfadb') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @decorators.attr(type=['negative']) def test_resume_server_invalid_state(self): # resume an active server. self.assertRaises(lib_exc.Conflict, self.client.resume_server, self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('7dd919e7-413f-4198-bebb-35e2a01b13e9') def test_get_console_output_of_non_existent_server(self): # get the console output for a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.get_console_output, nonexistent_server, length=10) @decorators.attr(type=['negative']) @decorators.idempotent_id('6f47992b-5144-4250-9f8b-f00aa33950f3') def test_force_delete_nonexistent_server_id(self): # force-delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.force_delete_server, nonexistent_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('9c6d38cc-fcfb-437a-85b9-7b788af8bf01') def test_restore_nonexistent_server_id(self): # restore-delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.restore_soft_deleted_server, nonexistent_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('7fcadfab-bd6a-4753-8db7-4a51e51aade9') def test_restore_server_invalid_state(self): # we can only restore-delete a server in 'soft-delete' state self.assertRaises(lib_exc.Conflict, self.client.restore_soft_deleted_server, self.server_id) @decorators.idempotent_id('abca56e2-a892-48ea-b5e5-e07e69774816') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') @decorators.attr(type=['negative']) def test_shelve_non_existent_server(self): # shelve a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.shelve_server, nonexistent_server) @decorators.idempotent_id('443e4f9b-e6bf-4389-b601-3a710f15fddd') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') @decorators.attr(type=['negative']) def test_shelve_shelved_server(self): # shelve a shelved server. compute.shelve_server(self.client, self.server_id) server = self.client.show_server(self.server_id)['server'] image_name = server['name'] + '-shelved' params = {'name': image_name} images = self.compute_images_client.list_images(**params)['images'] self.assertEqual(1, len(images)) self.assertEqual(image_name, images[0]['name']) self.assertRaises(lib_exc.Conflict, self.client.shelve_server, self.server_id) self.client.unshelve_server(self.server_id) @decorators.idempotent_id('23d23b37-afaf-40d7-aa5d-5726f82d8821') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') @decorators.attr(type=['negative']) def test_unshelve_non_existent_server(self): # unshelve a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.unshelve_server, nonexistent_server) @decorators.idempotent_id('8f198ded-1cca-4228-9e65-c6b449c54880') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') @decorators.attr(type=['negative']) def test_unshelve_server_invalid_state(self): # unshelve an active server. self.assertRaises(lib_exc.Conflict, self.client.unshelve_server, self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('74085be3-a370-4ca2-bc51-2d0e10e0f573') @utils.services('volume', 'image') def test_create_server_from_non_bootable_volume(self): # Create a volume volume = self.create_volume() # Update volume bootable status to false self.volumes_client.set_bootable_volume(volume['id'], bootable=False) # Verify bootable flag was updated nonbootable_vol = self.volumes_client.show_volume( volume['id'])['volume'] self.assertEqual('false', nonbootable_vol['bootable']) # Block device mapping bd_map = [{'boot_index': '0', 'uuid': volume['id'], 'source_type': 'volume', 'destination_type': 'volume', 'delete_on_termination': False}] # Try creating a server from non-bootable volume self.assertRaises(lib_exc.BadRequest, self.create_test_server, image_id='', wait_until='ACTIVE', block_device_mapping_v2=bd_map) class ServersNegativeTestMultiTenantJSON(base.BaseV2ComputeTest): credentials = ['primary', 'alt'] def setUp(self): super(ServersNegativeTestMultiTenantJSON, self).setUp() try: waiters.wait_for_server_status(self.servers_client, self.server_id, 'ACTIVE') except Exception: self.__class__.server_id = self.recreate_server(self.server_id) @classmethod def setup_clients(cls): super(ServersNegativeTestMultiTenantJSON, cls).setup_clients() cls.alt_client = cls.os_alt.servers_client @classmethod def resource_setup(cls): super(ServersNegativeTestMultiTenantJSON, cls).resource_setup() server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @decorators.attr(type=['negative']) @decorators.idempotent_id('543d84c1-dd2e-4c6d-8cb2-b9da0efaa384') def test_update_server_of_another_tenant(self): # Update name of a server that belongs to another tenant new_name = self.server_id + '_new' self.assertRaises(lib_exc.NotFound, self.alt_client.update_server, self.server_id, name=new_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('5c75009d-3eea-423e-bea3-61b09fd25f9c') def test_delete_a_server_of_another_tenant(self): # Delete a server that belongs to another tenant self.assertRaises(lib_exc.NotFound, self.alt_client.delete_server, self.server_id) tempest-17.2.0/tempest/api/compute/servers/test_virtual_interfaces_negative.py0000666000175100017510000000337213207044712030100 0ustar zuulzuul00000000000000# 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. from tempest.api.compute import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): # For this test no network resources are needed cls.set_network_resources() super(VirtualInterfacesNegativeTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(VirtualInterfacesNegativeTestJSON, cls).setup_clients() cls.client = cls.servers_client @decorators.attr(type=['negative']) @decorators.idempotent_id('64ebd03c-1089-4306-93fa-60f5eb5c803c') @utils.services('network') def test_list_virtual_interfaces_invalid_server_id(self): # Negative test: Should not be able to GET virtual interfaces # for an invalid server_id invalid_server_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.list_virtual_interfaces, invalid_server_id) tempest-17.2.0/tempest/api/compute/servers/test_server_personality.py0000666000175100017510000001675313207044712026273 0ustar zuulzuul00000000000000# 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 base64 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.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ServerPersonalityTestJSON(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): cls.prepare_instance_network() super(ServerPersonalityTestJSON, cls).setup_credentials() @classmethod def skip_checks(cls): super(ServerPersonalityTestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.personality: raise cls.skipException("Nova personality feature disabled") @classmethod def setup_clients(cls): super(ServerPersonalityTestJSON, cls).setup_clients() cls.client = cls.servers_client @decorators.idempotent_id('3cfe87fd-115b-4a02-b942-7dc36a337fdf') def test_create_server_with_personality(self): file_contents = 'This is a test file.' file_path = '/test.txt' personality = [{'path': file_path, 'contents': base64.encode_as_text(file_contents)}] password = data_utils.rand_password() validation_resources = self.get_test_validation_resources( self.os_primary) created_server = self.create_test_server( personality=personality, adminPass=password, wait_until='ACTIVE', validatable=True, validation_resources=validation_resources) self.addCleanup(waiters.wait_for_server_termination, self.servers_client, created_server['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, created_server['id']) server = self.client.show_server(created_server['id'])['server'] if CONF.validation.run_validation: linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, password, validation_resources['keypair']['private_key'], server=server, servers_client=self.client) self.assertEqual(file_contents, linux_client.exec_command( 'sudo cat %s' % file_path)) @decorators.idempotent_id('128966d8-71fc-443c-8cab-08e24114ecc9') def test_rebuild_server_with_personality(self): validation_resources = self.get_test_validation_resources( self.os_primary) server = self.create_test_server( wait_until='ACTIVE', validatable=True, validation_resources=validation_resources) server_id = server['id'] self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server_id) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server_id) file_contents = 'Test server rebuild.' personality = [{'path': 'rebuild.txt', 'contents': base64.encode_as_text(file_contents)}] rebuilt_server = self.client.rebuild_server(server_id, self.image_ref_alt, personality=personality) waiters.wait_for_server_status(self.client, server_id, 'ACTIVE') self.assertEqual(self.image_ref_alt, rebuilt_server['server']['image']['id']) @decorators.idempotent_id('176cd8c9-b9e8-48ee-a480-180beab292bf') def test_personality_files_exceed_limit(self): # Server creation should fail if greater than the maximum allowed # number of files are injected into the server. file_contents = 'This is a test file.' personality = [] limits = self.limits_client.show_limits()['limits'] max_file_limit = limits['absolute']['maxPersonality'] if max_file_limit == -1: raise self.skipException("No limit for personality files") for i in range(0, max_file_limit + 1): path = 'etc/test' + str(i) + '.txt' personality.append({'path': path, 'contents': base64.encode_as_text( file_contents)}) # A 403 Forbidden or 413 Overlimit (old behaviour) exception # will be raised when out of quota self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.create_test_server, personality=personality) @decorators.idempotent_id('52f12ee8-5180-40cc-b417-31572ea3d555') def test_can_create_server_with_max_number_personality_files(self): # Server should be created successfully if maximum allowed number of # files is injected into the server during creation. file_contents = 'This is a test file.' limits = self.limits_client.show_limits()['limits'] max_file_limit = limits['absolute']['maxPersonality'] if max_file_limit == -1: raise self.skipException("No limit for personality files") person = [] for i in range(0, max_file_limit): # NOTE(andreaf) The cirros disk image is blank before boot # so we can only inject safely to / path = '/test' + str(i) + '.txt' person.append({ 'path': path, 'contents': base64.encode_as_text(file_contents + str(i)), }) password = data_utils.rand_password() validation_resources = self.get_test_validation_resources( self.os_primary) created_server = self.create_test_server( personality=person, adminPass=password, wait_until='ACTIVE', validatable=True, validation_resources=validation_resources) self.addCleanup(waiters.wait_for_server_termination, self.servers_client, created_server['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, created_server['id']) server = self.client.show_server(created_server['id'])['server'] if CONF.validation.run_validation: linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, password, validation_resources['keypair']['private_key'], server=server, servers_client=self.client) for i in person: self.assertEqual(base64.decode_as_text(i['contents']), linux_client.exec_command( 'sudo cat %s' % i['path'])) tempest-17.2.0/tempest/api/compute/servers/test_server_tags.py0000666000175100017510000001047513207044712024653 0ustar zuulzuul00000000000000# Copyright 2017 AT&T 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 six from tempest.api.compute import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ServerTagsTestJSON(base.BaseV2ComputeTest): min_microversion = '2.26' max_microversion = 'latest' @classmethod def skip_checks(cls): super(ServerTagsTestJSON, cls).skip_checks() if not utils.is_extension_enabled('os-server-tags', 'compute'): msg = "os-server-tags extension is not enabled." raise cls.skipException(msg) @classmethod def setup_clients(cls): super(ServerTagsTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerTagsTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') def _update_server_tags(self, server_id, tags): if not isinstance(tags, (list, tuple)): tags = [tags] for tag in tags: self.client.update_tag(server_id, tag) self.addCleanup(self.client.delete_all_tags, server_id) @decorators.idempotent_id('8d95abe2-c658-4c42-9a44-c0258500306b') def test_create_delete_tag(self): # Check that no tags exist. fetched_tags = self.client.list_tags(self.server['id'])['tags'] self.assertEmpty(fetched_tags) # Add server tag to the server. assigned_tag = data_utils.rand_name('tag') self._update_server_tags(self.server['id'], assigned_tag) # Check that added tag exists. fetched_tags = self.client.list_tags(self.server['id'])['tags'] self.assertEqual([assigned_tag], fetched_tags) # Remove assigned tag from server and check that it was removed. self.client.delete_tag(self.server['id'], assigned_tag) fetched_tags = self.client.list_tags(self.server['id'])['tags'] self.assertEmpty(fetched_tags) @decorators.idempotent_id('a2c1af8c-127d-417d-974b-8115f7e3d831') def test_update_all_tags(self): # Add server tags to the server. tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')] self._update_server_tags(self.server['id'], tags) # Replace tags with new tags and check that they are present. new_tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')] replaced_tags = self.client.update_all_tags( self.server['id'], new_tags)['tags'] six.assertCountEqual(self, new_tags, replaced_tags) # List the tags and check that the tags were replaced. fetched_tags = self.client.list_tags(self.server['id'])['tags'] six.assertCountEqual(self, new_tags, fetched_tags) @decorators.idempotent_id('a63b2a74-e918-4b7c-bcab-10c855f3a57e') def test_delete_all_tags(self): # Add server tags to the server. assigned_tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')] self._update_server_tags(self.server['id'], assigned_tags) # Delete tags from the server and check that they were deleted. self.client.delete_all_tags(self.server['id']) fetched_tags = self.client.list_tags(self.server['id'])['tags'] self.assertEmpty(fetched_tags) @decorators.idempotent_id('81279a66-61c3-4759-b830-a2dbe64cbe08') def test_check_tag_existence(self): # Add server tag to the server. assigned_tag = data_utils.rand_name('tag') self._update_server_tags(self.server['id'], assigned_tag) # Check that added tag exists. Throws a 404 if not found, else a 204, # which was already checked by the schema validation. self.client.check_tag_existence(self.server['id'], assigned_tag) tempest-17.2.0/tempest/api/compute/servers/test_availability_zone.py0000666000175100017510000000237413207044712026033 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.lib import decorators class AZV2TestJSON(base.BaseV2ComputeTest): """Tests Availability Zone API List""" @classmethod def setup_clients(cls): super(AZV2TestJSON, cls).setup_clients() cls.client = cls.availability_zone_client @decorators.idempotent_id('a8333aa2-205c-449f-a828-d38c2489bf25') def test_get_availability_zone_list_with_non_admin_user(self): # List of availability zone with non-administrator user availability_zone = self.client.list_availability_zones() self.assertNotEmpty(availability_zone['availabilityZoneInfo']) tempest-17.2.0/tempest/api/compute/servers/test_instance_actions_negative.py0000666000175100017510000000373013207044712027531 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class InstanceActionsNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(InstanceActionsNegativeTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(InstanceActionsNegativeTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') @decorators.attr(type=['negative']) @decorators.idempotent_id('67e1fce6-7ec2-45c6-92d4-0a8f1a632910') def test_list_instance_actions_non_existent_server(self): # List actions of the non-existent server id non_existent_server_id = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.client.list_instance_actions, non_existent_server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('0269f40a-6f18-456c-b336-c03623c897f1') def test_get_instance_action_invalid_request(self): # Get the action details of the provided server with invalid request self.assertRaises(lib_exc.NotFound, self.client.show_instance_action, self.server['id'], '999') tempest-17.2.0/tempest/api/compute/servers/__init__.py0000666000175100017510000000000013207044712023006 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/servers/test_novnc.py0000666000175100017510000002047713207044712023455 0ustar zuulzuul00000000000000# Copyright 2016-2017 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 struct import six import urllib3 from tempest.api.compute import base from tempest.common import compute from tempest import config from tempest.lib import decorators CONF = config.CONF if six.PY2: ord_func = ord else: ord_func = int class NoVNCConsoleTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(NoVNCConsoleTestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.vnc_console: raise cls.skipException('VNC Console feature is disabled.') def setUp(self): super(NoVNCConsoleTestJSON, self).setUp() self._websocket = None def tearDown(self): self.server_check_teardown() super(NoVNCConsoleTestJSON, self).tearDown() if self._websocket is not None: self._websocket.close() @classmethod def setup_clients(cls): super(NoVNCConsoleTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(NoVNCConsoleTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until="ACTIVE") def _validate_novnc_html(self, vnc_url): """Verify we can connect to novnc and get back the javascript.""" resp = urllib3.PoolManager().request('GET', vnc_url) # Make sure that the GET request was accepted by the novncproxy self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the ' 'initial call: ' + six.text_type(resp.status)) # Do some basic validation to make sure it is an expected HTML document resp_data = resp.data.decode() self.assertIn('', resp_data, 'Not a valid html document in the response.') self.assertIn('', resp_data, 'Not a valid html document in the response.') # Just try to make sure we got JavaScript back for noVNC, since we # won't actually use it since not inside of a browser self.assertIn('noVNC', resp_data, 'Not a valid noVNC javascript html document.') self.assertIn('L", data[20:24])[0] + 24), 'Server initialization was not the right format.') # Since the rest of the data on the screen is arbitrary, we will # close the socket and end our validation of the data at this point # Assert that the latest check was false, meaning that the server # initialization was the right format self.assertFalse(data_length <= 24 or data_length != (struct.unpack(">L", data[20:24])[0] + 24)) def _validate_websocket_upgrade(self): self.assertTrue( self._websocket.response.startswith(b'HTTP/1.1 101 Switching ' b'Protocols\r\n'), 'Did not get the expected 101 on the websockify call: ' + six.text_type(self._websocket.response)) self.assertTrue( self._websocket.response.find(b'Server: WebSockify') > 0, 'Did not get the expected WebSocket HTTP Response.') @decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc') def test_novnc(self): body = self.client.get_vnc_console(self.server['id'], type='novnc')['console'] self.assertEqual('novnc', body['type']) # Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript self._validate_novnc_html(body['url']) # Do the WebSockify HTTP Request to novncproxy to do the RFB connection self._websocket = compute.create_websocket(body['url']) # Validate that we successfully connected and upgraded to Web Sockets self._validate_websocket_upgrade() # Validate the RFB Negotiation to determine if a valid VNC session self._validate_rfb_negotiation() @decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7') def test_novnc_bad_token(self): body = self.client.get_vnc_console(self.server['id'], type='novnc')['console'] self.assertEqual('novnc', body['type']) # Do the WebSockify HTTP Request to novncproxy with a bad token url = body['url'].replace('token=', 'token=bad') self._websocket = compute.create_websocket(url) # Make sure the novncproxy rejected the connection and closed it data = self._websocket.receive_frame() self.assertTrue(data is None or not data, "The novnc proxy actually sent us some data, but we " "expected it to close the connection.") tempest-17.2.0/tempest/api/compute/servers/test_delete_server.py0000666000175100017510000001264413207044712025157 0ustar zuulzuul00000000000000# 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 from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class DeleteServersTestJSON(base.BaseV2ComputeTest): # NOTE: Server creations of each test class should be under 10 # for preventing "Quota exceeded for instances" @classmethod def setup_clients(cls): super(DeleteServersTestJSON, cls).setup_clients() cls.client = cls.servers_client @decorators.idempotent_id('9e6e0c87-3352-42f7-9faf-5d6210dbd159') def test_delete_server_while_in_building_state(self): # Delete a server while it's VM state is Building server = self.create_test_server(wait_until='BUILD') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('925fdfb4-5b13-47ea-ac8a-c36ae6fddb05') def test_delete_active_server(self): # Delete a server while it's VM state is Active server = self.create_test_server(wait_until='ACTIVE') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('546d368c-bb6c-4645-979a-83ed16f3a6be') def test_delete_server_while_in_shutoff_state(self): # Delete a server while it's VM state is Shutoff server = self.create_test_server(wait_until='ACTIVE') self.client.stop_server(server['id']) waiters.wait_for_server_status(self.client, server['id'], 'SHUTOFF') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('943bd6e8-4d7a-4904-be83-7a6cc2d4213b') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') def test_delete_server_while_in_pause_state(self): # Delete a server while it's VM state is Pause server = self.create_test_server(wait_until='ACTIVE') self.client.pause_server(server['id']) waiters.wait_for_server_status(self.client, server['id'], 'PAUSED') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('1f82ebd3-8253-4f4e-b93f-de9b7df56d8b') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') def test_delete_server_while_in_suspended_state(self): # Delete a server while it's VM state is Suspended server = self.create_test_server(wait_until='ACTIVE') self.client.suspend_server(server['id']) waiters.wait_for_server_status(self.client, server['id'], 'SUSPENDED') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('bb0cb402-09dd-4947-b6e5-5e7e1cfa61ad') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') def test_delete_server_while_in_shelved_state(self): # Delete a server while it's VM state is Shelved server = self.create_test_server(wait_until='ACTIVE') compute.shelve_server(self.client, server['id']) self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('ab0c38b4-cdd8-49d3-9b92-0cb898723c01') @testtools.skipIf(not CONF.compute_feature_enabled.resize, 'Resize not available.') def test_delete_server_while_in_verify_resize_state(self): # Delete a server while it's VM state is VERIFY_RESIZE server = self.create_test_server(wait_until='ACTIVE') self.client.resize_server(server['id'], self.flavor_ref_alt) waiters.wait_for_server_status(self.client, server['id'], 'VERIFY_RESIZE') self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) @decorators.idempotent_id('d0f3f0d6-d9b6-4a32-8da4-23015dcab23c') @utils.services('volume') def test_delete_server_while_in_attached_volume(self): # Delete a server while a volume is attached to it device = '/dev/%s' % CONF.compute.volume_device_name server = self.create_test_server(wait_until='ACTIVE') volume = self.create_volume() self.attach_volume(server, volume, device=device) self.client.delete_server(server['id']) waiters.wait_for_server_termination(self.client, server['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') tempest-17.2.0/tempest/api/compute/servers/test_server_group.py0000666000175100017510000001254113207044712025045 0ustar zuulzuul00000000000000# Copyright 2014 NEC Technologies India Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ServerGroupTestJSON(base.BaseV2ComputeTest): """These tests check for the server-group APIs. They create/delete server-groups with different policies. policies = affinity/anti-affinity It also adds the tests for list and get details of server-groups """ @classmethod def skip_checks(cls): super(ServerGroupTestJSON, cls).skip_checks() if not utils.is_extension_enabled('os-server-groups', 'compute'): msg = "os-server-groups extension is not enabled." raise cls.skipException(msg) @classmethod def setup_clients(cls): super(ServerGroupTestJSON, cls).setup_clients() cls.client = cls.server_groups_client @classmethod def resource_setup(cls): super(ServerGroupTestJSON, cls).resource_setup() cls.policy = ['affinity'] cls.created_server_group = cls.create_test_server_group( policy=cls.policy) def _create_server_group(self, name, policy): # create the test server-group with given policy server_group = {'name': name, 'policies': policy} body = self.create_test_server_group(name, policy) for key in ['name', 'policies']: self.assertEqual(server_group[key], body[key]) return body def _delete_server_group(self, server_group): # delete the test server-group self.client.delete_server_group(server_group['id']) # validation of server-group deletion server_group_list = self.client.list_server_groups()['server_groups'] self.assertNotIn(server_group, server_group_list) def _create_delete_server_group(self, policy): # Create and Delete the server-group with given policy name = data_utils.rand_name('server-group') server_group = self._create_server_group(name, policy) self._delete_server_group(server_group) @decorators.idempotent_id('5dc57eda-35b7-4af7-9e5f-3c2be3d2d68b') def test_create_delete_server_group_with_affinity_policy(self): # Create and Delete the server-group with affinity policy self._create_delete_server_group(self.policy) @decorators.idempotent_id('3645a102-372f-4140-afad-13698d850d23') def test_create_delete_server_group_with_anti_affinity_policy(self): # Create and Delete the server-group with anti-affinity policy policy = ['anti-affinity'] self._create_delete_server_group(policy) @decorators.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 server_groups = [] server_group_name = data_utils.rand_name('server-group') for _ in range(0, 2): server_groups.append(self._create_server_group(server_group_name, self.policy)) for key in ['name', 'policies']: self.assertEqual(server_groups[0][key], server_groups[1][key]) self.assertNotEqual(server_groups[0]['id'], server_groups[1]['id']) for i in range(0, 2): self._delete_server_group(server_groups[i]) @decorators.idempotent_id('b3545034-dd78-48f0-bdc2-a4adfa6d0ead') def test_show_server_group(self): # Get the server-group body = self.client.show_server_group( self.created_server_group['id'])['server_group'] self.assertEqual(self.created_server_group, body) @decorators.idempotent_id('d4874179-27b4-4d7d-80e4-6c560cdfe321') def test_list_server_groups(self): # List the server-group body = self.client.list_server_groups()['server_groups'] self.assertIn(self.created_server_group, body) @decorators.idempotent_id('ed20d3fb-9d1f-4329-b160-543fbd5d9811') @testtools.skipUnless( compute.is_scheduler_filter_enabled("ServerGroupAffinityFilter"), 'ServerGroupAffinityFilter is not available.') def test_create_server_with_scheduler_hint_group(self): # Create a server with the scheduler hint "group". hints = {'group': self.created_server_group['id']} server = self.create_test_server(scheduler_hints=hints, wait_until='ACTIVE') self.addCleanup(self.delete_server, server['id']) # Check a server is in the group server_group = (self.server_groups_client.show_server_group( self.created_server_group['id'])['server_group']) self.assertIn(server['id'], server_group['members']) tempest-17.2.0/tempest/api/compute/servers/test_attach_interfaces.py0000666000175100017510000002700013207044712025766 0ustar zuulzuul00000000000000# 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 time import six from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest.common.utils import net_utils from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class AttachInterfacesTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(AttachInterfacesTestJSON, cls).skip_checks() if not CONF.service_available.neutron: raise cls.skipException("Neutron is required") if not CONF.compute_feature_enabled.interface_attach: raise cls.skipException("Interface attachment is not available.") @classmethod def setup_credentials(cls): # This test class requires network and subnet cls.set_network_resources(network=True, subnet=True) super(AttachInterfacesTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(AttachInterfacesTestJSON, cls).setup_clients() cls.subnets_client = cls.os_primary.subnets_client cls.ports_client = cls.os_primary.ports_client def wait_for_port_detach(self, port_id): """Waits for the port's device_id to be unset. :param port_id: The id of the port being detached. :returns: The final port dict from the show_port response. """ port = self.ports_client.show_port(port_id)['port'] device_id = port['device_id'] start = int(time.time()) # NOTE(mriedem): Nova updates the port's device_id to '' rather than # None, but it's not contractual so handle Falsey either way. while device_id: time.sleep(self.build_interval) port = self.ports_client.show_port(port_id)['port'] device_id = port['device_id'] timed_out = int(time.time()) - start >= self.build_timeout if device_id and timed_out: message = ('Port %s failed to detach (device_id %s) within ' 'the required time (%s s).' % (port_id, device_id, self.build_timeout)) raise lib_exc.TimeoutException(message) return port def _check_interface(self, iface, port_id=None, network_id=None, fixed_ip=None, mac_addr=None): if port_id: self.assertEqual(iface['port_id'], port_id) if network_id: self.assertEqual(iface['net_id'], network_id) if fixed_ip: self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip) if mac_addr: self.assertEqual(iface['mac_addr'], mac_addr) def _create_server_get_interfaces(self): server = self.create_test_server(wait_until='ACTIVE') ifs = (self.interfaces_client.list_interfaces(server['id']) ['interfaceAttachments']) body = waiters.wait_for_interface_status( self.interfaces_client, server['id'], ifs[0]['port_id'], 'ACTIVE') ifs[0]['port_state'] = body['port_state'] return server, ifs def _test_create_interface(self, server): iface = (self.interfaces_client.create_interface(server['id']) ['interfaceAttachment']) iface = waiters.wait_for_interface_status( self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE') return iface def _test_create_interface_by_network_id(self, server, ifs): network_id = ifs[0]['net_id'] iface = self.interfaces_client.create_interface( server['id'], net_id=network_id)['interfaceAttachment'] iface = waiters.wait_for_interface_status( self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE') 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.interfaces_client.create_interface( server['id'], port_id=port_id)['interfaceAttachment'] iface = waiters.wait_for_interface_status( self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface, port_id=port_id) return iface def _test_create_interface_by_fixed_ips(self, server, ifs): network_id = ifs[0]['net_id'] subnet_id = ifs[0]['fixed_ips'][0]['subnet_id'] ip_list = net_utils.get_unused_ip_addresses(self.ports_client, self.subnets_client, network_id, subnet_id, 1) fixed_ips = [{'ip_address': ip_list[0]}] iface = self.interfaces_client.create_interface( server['id'], net_id=network_id, fixed_ips=fixed_ips)['interfaceAttachment'] self.addCleanup(self.ports_client.delete_port, iface['port_id']) iface = waiters.wait_for_interface_status( self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface, fixed_ip=ip_list[0]) return iface def _test_show_interface(self, server, ifs): iface = ifs[0] _iface = self.interfaces_client.show_interface( server['id'], iface['port_id'])['interfaceAttachment'] self._check_interface(iface, port_id=_iface['port_id'], network_id=_iface['net_id'], fixed_ip=_iface['fixed_ips'][0]['ip_address'], mac_addr=_iface['mac_addr']) def _test_delete_interface(self, server, ifs): # NOTE(danms): delete not the first or last, but one in the middle iface = ifs[1] self.interfaces_client.delete_interface(server['id'], iface['port_id']) _ifs = (self.interfaces_client.list_interfaces(server['id']) ['interfaceAttachments']) start = int(time.time()) while len(ifs) == len(_ifs): time.sleep(self.build_interval) _ifs = (self.interfaces_client.list_interfaces(server['id']) ['interfaceAttachments']) timed_out = int(time.time()) - start >= self.build_timeout if len(ifs) == len(_ifs) and timed_out: message = ('Failed to delete interface within ' 'the required time: %s sec.' % self.build_timeout) raise lib_exc.TimeoutException(message) self.assertNotIn(iface['port_id'], [i['port_id'] for i in _ifs]) return _ifs def _compare_iface_list(self, list1, list2): # NOTE(danms): port_state will likely have changed, so just # confirm the port_ids are the same at least list1 = [x['port_id'] for x in list1] list2 = [x['port_id'] for x in list2] self.assertEqual(sorted(list1), sorted(list2)) @decorators.idempotent_id('73fe8f02-590d-4bf1-b184-e9ca81065051') @utils.services('network') def test_create_list_show_delete_interfaces(self): server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertGreater(interface_count, 0) try: iface = self._test_create_interface(server) except lib_exc.BadRequest as e: msg = ('Multiple possible networks found, use a Network ID to be ' 'more specific.') if not CONF.compute.fixed_network_name and six.text_type(e) == msg: raise else: ifs.append(iface) 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) iface = self._test_create_interface_by_fixed_ips(server, ifs) ifs.append(iface) _ifs = (self.interfaces_client.list_interfaces(server['id']) ['interfaceAttachments']) self._compare_iface_list(ifs, _ifs) self._test_show_interface(server, ifs) _ifs = self._test_delete_interface(server, ifs) self.assertEqual(len(ifs) - 1, len(_ifs)) @decorators.attr(type='smoke') @decorators.idempotent_id('c7e0e60b-ee45-43d0-abeb-8596fd42a2f9') @utils.services('network') def test_add_remove_fixed_ip(self): # Add and Remove the fixed IP to server. server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertGreater(interface_count, 0) network_id = ifs[0]['net_id'] self.servers_client.add_fixed_ip(server['id'], networkId=network_id) # Remove the fixed IP from server. server_detail = self.os_primary.servers_client.show_server( server['id'])['server'] # Get the Fixed IP from server. fixed_ip = None for ip_set in server_detail['addresses']: for ip in server_detail['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': fixed_ip = ip['addr'] break if fixed_ip is not None: break self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip) @decorators.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4') def test_reassign_port_between_servers(self): """Tests the following: 1. Create a port in Neutron. 2. Create two servers in Nova. 3. Attach the port to the first server. 4. Detach the port from the first server. 5. Attach the port to the second server. 6. Detach the port from the second server. """ network = self.get_tenant_network() network_id = network['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) # create two servers _, servers = compute.create_test_server( self.os_primary, tenant_network=network, wait_until='ACTIVE', min_count=2) # add our cleanups for the servers since we bypassed the base class for server in servers: self.addCleanup(self.delete_server, server['id']) for server in servers: # attach the port to the server iface = self.interfaces_client.create_interface( server['id'], port_id=port_id)['interfaceAttachment'] self._check_interface(iface, port_id=port_id) # detach the port from the server; this is a cast in the compute # API so we have to poll the port until the device_id is unset. self.interfaces_client.delete_interface(server['id'], port_id) self.wait_for_port_detach(port_id) tempest-17.2.0/tempest/api/compute/servers/test_server_actions.py0000666000175100017510000007177013207044725025366 0ustar zuulzuul00000000000000# 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_log import log as logging from six.moves.urllib import parse as urlparse import testtools from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest.common.utils.linux import remote_client from tempest.common import waiters from tempest import config from tempest.lib.common import api_version_utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF LOG = logging.getLogger(__name__) class ServerActionsTestJSON(base.BaseV2ComputeTest): def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ServerActionsTestJSON, self).setUp() # Check if the server is in a clean state after test try: waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') except lib_exc.NotFound: # The server was deleted by previous test, create a new one # Use class level validation resources to avoid them being # deleted once a test is over validation_resources = self.get_class_validation_resources( self.os_primary) server = self.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE') self.__class__.server_id = server['id'] except Exception: # Rebuild server if something happened to it during a test self.__class__.server_id = self.recreate_server( self.server_id, validatable=True) def tearDown(self): self.server_check_teardown() super(ServerActionsTestJSON, self).tearDown() @classmethod def setup_credentials(cls): cls.prepare_instance_network() super(ServerActionsTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(ServerActionsTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerActionsTestJSON, cls).resource_setup() cls.server_id = cls.recreate_server(None, validatable=True) @decorators.idempotent_id('6158df09-4b82-4ab3-af6d-29cf36af858d') @testtools.skipUnless(CONF.compute_feature_enabled.change_password, 'Change password not available.') def test_change_server_password(self): # Since this test messes with the password and makes the # server unreachable, it should create its own server validation_resources = self.get_test_validation_resources( self.os_primary) newserver = self.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE') # The server's password should be set to the provided password new_password = 'Newpass1234' self.client.change_password(newserver['id'], adminPass=new_password) waiters.wait_for_server_status(self.client, newserver['id'], 'ACTIVE') if CONF.validation.run_validation: # Verify that the user can authenticate with the new password server = self.client.show_server(newserver['id'])['server'] linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, new_password, server=server, servers_client=self.client) linux_client.validate_authentication() def _test_reboot_server(self, reboot_type): if CONF.validation.run_validation: validation_resources = self.get_class_validation_resources( self.os_primary) # Get the time the server was last rebooted, server = self.client.show_server(self.server_id)['server'] linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, self.password, validation_resources['keypair']['private_key'], server=server, servers_client=self.client) boot_time = linux_client.get_boot_time() # NOTE: This sync is for avoiding the loss of pub key data # in a server linux_client.exec_command("sync") self.client.reboot_server(self.server_id, type=reboot_type) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') if CONF.validation.run_validation: # Log in and verify the boot time has changed linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.ssh_user, self.password, validation_resources['keypair']['private_key'], server=server, servers_client=self.client) new_boot_time = linux_client.get_boot_time() self.assertGreater(new_boot_time, boot_time, '%s > %s' % (new_boot_time, boot_time)) @decorators.attr(type='smoke') @decorators.idempotent_id('2cb1baf6-ac8d-4429-bf0d-ba8a0ba53e32') def test_reboot_server_hard(self): # The server should be power cycled self._test_reboot_server('HARD') @decorators.skip_because(bug="1014647") @decorators.idempotent_id('4640e3ef-a5df-482e-95a1-ceeeb0faa84d') def test_reboot_server_soft(self): # The server should be signaled to reboot gracefully self._test_reboot_server('SOFT') @decorators.idempotent_id('1d1c9104-1b0a-11e7-a3d4-fa163e65f5ce') def test_remove_server_all_security_groups(self): server = self.create_test_server(wait_until='ACTIVE') # Remove all Security group self.client.remove_security_group( server['id'], name=server['security_groups'][0]['name']) # Verify all Security group server = self.client.show_server(server['id'])['server'] self.assertNotIn('security_groups', server) def _rebuild_server_and_check(self, image_ref): rebuilt_server = (self.client.rebuild_server(self.server_id, image_ref) ['server']) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') msg = ('Server was not rebuilt to the original image. ' 'The original image: {0}. The current image: {1}' .format(image_ref, rebuilt_server['image']['id'])) self.assertEqual(image_ref, rebuilt_server['image']['id'], msg) def _test_rebuild_server(self): # Get the IPs the server has before rebuilding it original_addresses = (self.client.show_server(self.server_id)['server'] ['addresses']) # The server should be rebuilt using the provided image and data meta = {'rebuild': 'server'} new_name = data_utils.rand_name(self.__class__.__name__ + '-server') password = 'rebuildPassw0rd' rebuilt_server = self.client.rebuild_server( self.server_id, self.image_ref_alt, name=new_name, metadata=meta, adminPass=password)['server'] # If the server was rebuilt on a different image, restore it to the # original image once the test ends if self.image_ref_alt != self.image_ref: self.addCleanup(self._rebuild_server_and_check, self.image_ref) # Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) # Verify the server properties after the rebuild completes waiters.wait_for_server_status(self.client, rebuilt_server['id'], 'ACTIVE') server = self.client.show_server(rebuilt_server['id'])['server'] rebuilt_image_id = server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(new_name, server['name']) self.assertEqual(original_addresses, server['addresses']) if CONF.validation.run_validation: validation_resources = self.get_class_validation_resources( self.os_primary) # Authentication is attempted in the following order of priority: # 1.The key passed in, if one was passed in. # 2.Any key we can find through an SSH agent (if allowed). # 3.Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in # ~/.ssh/ (if allowed). # 4.Plain username/password auth, if a password was given. linux_client = remote_client.RemoteClient( self.get_server_ip(rebuilt_server, validation_resources), self.ssh_user, password, validation_resources['keypair']['private_key'], server=rebuilt_server, servers_client=self.client) linux_client.validate_authentication() @decorators.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c') def test_rebuild_server(self): self._test_rebuild_server() @decorators.idempotent_id('30449a88-5aff-4f9b-9866-6ee9b17f906d') def test_rebuild_server_in_stop_state(self): # The server in stop state should be rebuilt using the provided # image and remain in SHUTOFF state server = self.client.show_server(self.server_id)['server'] old_image = server['image']['id'] new_image = (self.image_ref_alt if old_image == self.image_ref else self.image_ref) self.client.stop_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF') rebuilt_server = (self.client.rebuild_server(self.server_id, new_image) ['server']) # If the server was rebuilt on a different image, restore it to the # original image once the test ends if self.image_ref_alt != self.image_ref: self.addCleanup(self._rebuild_server_and_check, old_image) # Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertEqual(new_image, rebuilt_image_id) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) # Verify the server properties after the rebuild completes waiters.wait_for_server_status(self.client, rebuilt_server['id'], 'SHUTOFF') server = self.client.show_server(rebuilt_server['id'])['server'] rebuilt_image_id = server['image']['id'] self.assertEqual(new_image, rebuilt_image_id) self.client.start_server(self.server_id) @decorators.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace') @utils.services('volume') def test_rebuild_server_with_volume_attached(self): # create a new volume and attach it to the server volume = self.create_volume() server = self.client.show_server(self.server_id)['server'] self.attach_volume(server, volume) # run general rebuild test self._test_rebuild_server() # make sure the volume is attached to the instance after rebuild vol_after_rebuild = self.volumes_client.show_volume(volume['id']) vol_after_rebuild = vol_after_rebuild['volume'] self.assertEqual('in-use', vol_after_rebuild['status']) self.assertEqual(self.server_id, vol_after_rebuild['attachments'][0]['server_id']) def _test_resize_server_confirm(self, server_id, stop=False): # The server's RAM and disk space should be modified to that of # the provided flavor if stop: self.client.stop_server(server_id) waiters.wait_for_server_status(self.client, server_id, 'SHUTOFF') self.client.resize_server(server_id, self.flavor_ref_alt) # NOTE(jlk): Explicitly delete the server to get a new one for later # tests. Avoids resize down race issues. self.addCleanup(self.delete_server, server_id) waiters.wait_for_server_status(self.client, server_id, 'VERIFY_RESIZE') self.client.confirm_resize_server(server_id) expected_status = 'SHUTOFF' if stop else 'ACTIVE' waiters.wait_for_server_status(self.client, server_id, expected_status) server = self.client.show_server(server_id)['server'] self.assertEqual(self.flavor_ref_alt, server['flavor']['id']) if stop: # NOTE(mriedem): tearDown requires the server to be started. self.client.start_server(server_id) @decorators.idempotent_id('1499262a-9328-4eda-9068-db1ac57498d2') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_confirm(self): self._test_resize_server_confirm(self.server_id, stop=False) @decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b') @decorators.related_bug('1728603') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @utils.services('volume') def test_resize_volume_backed_server_confirm(self): # We have to create a new server that is volume-backed since the one # from setUp is not volume-backed. server = self.create_test_server( volume_backed=True, wait_until='ACTIVE') self._test_resize_server_confirm(server['id']) # Now do something interactive with the guest like get its console # output; we don't actually care about the output, just that it doesn't # raise an error. self.client.get_console_output(server['id']) @decorators.idempotent_id('138b131d-66df-48c9-a171-64f45eb92962') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_confirm_from_stopped(self): self._test_resize_server_confirm(self.server_id, stop=True) @decorators.idempotent_id('c03aab19-adb1-44f5-917d-c419577e9e68') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_revert(self): # The server's RAM and disk space should return to its original # values after a resize is reverted self.client.resize_server(self.server_id, self.flavor_ref_alt) # NOTE(zhufl): Explicitly delete the server to get a new one for later # tests. Avoids resize down race issues. self.addCleanup(self.delete_server, self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'VERIFY_RESIZE') self.client.revert_resize_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') server = self.client.show_server(self.server_id)['server'] self.assertEqual(self.flavor_ref, server['flavor']['id']) @decorators.idempotent_id('b963d4f1-94b3-4c40-9e97-7b583f46e470') @testtools.skipUnless(CONF.compute_feature_enabled.snapshot, 'Snapshotting not available, backup not possible.') @utils.services('image') def test_create_backup(self): # Positive test:create backup successfully and rotate backups correctly # create the first and the second backup # Check if glance v1 is available to determine which client to use. We # prefer glance v1 for the compute API tests since the compute image # API proxy was written for glance v1. if CONF.image_feature_enabled.api_v1: glance_client = self.os_primary.image_client elif CONF.image_feature_enabled.api_v2: glance_client = self.os_primary.image_client_v2 else: raise lib_exc.InvalidConfiguration( 'Either api_v1 or api_v2 must be True in ' '[image-feature-enabled].') backup1 = data_utils.rand_name('backup-1') resp = self.client.create_backup(self.server_id, backup_type='daily', rotation=2, name=backup1).response oldest_backup_exist = True # the oldest one should be deleted automatically in this test def _clean_oldest_backup(oldest_backup): if oldest_backup_exist: try: glance_client.delete_image(oldest_backup) except lib_exc.NotFound: pass else: LOG.warning("Deletion of oldest backup %s should not have " "been successful as it should have been " "deleted during rotation.", oldest_backup) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", resp, "lt"): image1_id = resp['image_id'] else: image1_id = data_utils.parse_image_id(resp['location']) self.addCleanup(_clean_oldest_backup, image1_id) waiters.wait_for_image_status(glance_client, image1_id, 'active') backup2 = data_utils.rand_name('backup-2') waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') resp = self.client.create_backup(self.server_id, backup_type='daily', rotation=2, name=backup2).response if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", resp, "lt"): image2_id = resp['image_id'] else: image2_id = data_utils.parse_image_id(resp['location']) self.addCleanup(glance_client.delete_image, image2_id) waiters.wait_for_image_status(glance_client, image2_id, 'active') # verify they have been created properties = { 'image_type': 'backup', 'backup_type': "daily", 'instance_uuid': self.server_id, } params = { 'status': 'active', 'sort_key': 'created_at', 'sort_dir': 'asc' } if CONF.image_feature_enabled.api_v1: for key, value in properties.items(): params['property-%s' % key] = value image_list = glance_client.list_images( detail=True, **params)['images'] else: # Additional properties are flattened in glance v2. params.update(properties) image_list = glance_client.list_images(params)['images'] self.assertEqual(2, len(image_list)) self.assertEqual((backup1, backup2), (image_list[0]['name'], image_list[1]['name'])) # create the third one, due to the rotation is 2, # the first one will be deleted backup3 = data_utils.rand_name('backup-3') waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') resp = self.client.create_backup(self.server_id, backup_type='daily', rotation=2, name=backup3).response if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", resp, "lt"): image3_id = resp['image_id'] else: image3_id = data_utils.parse_image_id(resp['location']) self.addCleanup(glance_client.delete_image, image3_id) # the first back up should be deleted waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') glance_client.wait_for_resource_deletion(image1_id) oldest_backup_exist = False if CONF.image_feature_enabled.api_v1: image_list = glance_client.list_images( detail=True, **params)['images'] else: image_list = glance_client.list_images(params)['images'] self.assertEqual(2, len(image_list), 'Unexpected number of images for ' 'v2:test_create_backup; was the oldest backup not ' 'yet deleted? Image list: %s' % [image['name'] for image in image_list]) self.assertEqual((backup2, backup3), (image_list[0]['name'], image_list[1]['name'])) def _get_output(self): output = self.client.get_console_output( self.server_id, length=10)['output'] self.assertTrue(output, "Console output was empty.") lines = len(output.split('\n')) self.assertEqual(lines, 10) @decorators.idempotent_id('4b8867e6-fffa-4d54-b1d1-6fdda57be2f3') @testtools.skipUnless(CONF.compute_feature_enabled.console_output, 'Console output not supported.') def test_get_console_output(self): # Positive test:Should be able to GET the console output # for a given server_id and number of lines # This reboot is necessary for outputting some console log after # creating an instance backup. If an instance backup, the console # log file is truncated and we cannot get any console log through # "console-log" API. # The detail is https://bugs.launchpad.net/nova/+bug/1251920 self.client.reboot_server(self.server_id, type='HARD') waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') self.wait_for(self._get_output) @decorators.idempotent_id('89104062-69d8-4b19-a71b-f47b7af093d7') @testtools.skipUnless(CONF.compute_feature_enabled.console_output, 'Console output not supported.') def test_get_console_output_with_unlimited_size(self): server = self.create_test_server(wait_until='ACTIVE') def _check_full_length_console_log(): output = self.client.get_console_output(server['id'])['output'] self.assertTrue(output, "Console output was empty.") lines = len(output.split('\n')) # NOTE: This test tries to get full length console log, and the # length should be bigger than the one of test_get_console_output. self.assertGreater(lines, 10, "Cannot get enough console log " "length. (lines: %s)" % lines) self.wait_for(_check_full_length_console_log) @decorators.idempotent_id('5b65d4e7-4ecd-437c-83c0-d6b79d927568') @testtools.skipUnless(CONF.compute_feature_enabled.console_output, 'Console output not supported.') def test_get_console_output_server_id_in_shutoff_status(self): # Positive test:Should be able to GET the console output # for a given server_id in SHUTOFF status # NOTE: SHUTOFF is irregular status. To avoid test instability, # one server is created only for this test without using # the server that was created in setUpClass. server = self.create_test_server(wait_until='ACTIVE') temp_server_id = server['id'] self.client.stop_server(temp_server_id) waiters.wait_for_server_status(self.client, temp_server_id, 'SHUTOFF') self.wait_for(self._get_output) @decorators.idempotent_id('bd61a9fd-062f-4670-972b-2d6c3e3b9e73') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') def test_pause_unpause_server(self): self.client.pause_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'PAUSED') self.client.unpause_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') @decorators.idempotent_id('0d8ee21e-b749-462d-83da-b85b41c86c7f') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') def test_suspend_resume_server(self): self.client.suspend_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'SUSPENDED') self.client.resume_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') @decorators.idempotent_id('77eba8e0-036e-4635-944b-f7a8f3b78dc9') @testtools.skipUnless(CONF.compute_feature_enabled.shelve, 'Shelve is not available.') @utils.services('image') def test_shelve_unshelve_server(self): if CONF.image_feature_enabled.api_v2: glance_client = self.os_primary.image_client_v2 elif CONF.image_feature_enabled.api_v1: glance_client = self.os_primary.image_client else: raise lib_exc.InvalidConfiguration( 'Either api_v1 or api_v2 must be True in ' '[image-feature-enabled].') compute.shelve_server(self.client, self.server_id, force_shelve_offload=True) server = self.client.show_server(self.server_id)['server'] image_name = server['name'] + '-shelved' params = {'name': image_name} if CONF.image_feature_enabled.api_v2: images = glance_client.list_images(params)['images'] elif CONF.image_feature_enabled.api_v1: images = glance_client.list_images( detail=True, **params)['images'] self.assertEqual(1, len(images)) self.assertEqual(image_name, images[0]['name']) self.client.unshelve_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') glance_client.wait_for_resource_deletion(images[0]['id']) @decorators.idempotent_id('af8eafd4-38a7-4a4b-bdbc-75145a580560') def test_stop_start_server(self): self.client.stop_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF') self.client.start_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') @decorators.idempotent_id('80a8094c-211e-440a-ab88-9e59d556c7ee') def test_lock_unlock_server(self): # Lock the server,try server stop(exceptions throw),unlock it and retry self.client.lock_server(self.server_id) self.addCleanup(self.client.unlock_server, self.server_id) server = self.client.show_server(self.server_id)['server'] self.assertEqual(server['status'], 'ACTIVE') # Locked server is not allowed to be stopped by non-admin user self.assertRaises(lib_exc.Conflict, self.client.stop_server, self.server_id) self.client.unlock_server(self.server_id) self.client.stop_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'SHUTOFF') self.client.start_server(self.server_id) waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') def _validate_url(self, url): valid_scheme = ['http', 'https'] parsed_url = urlparse.urlparse(url) self.assertNotEqual('None', parsed_url.port) self.assertNotEqual('None', parsed_url.hostname) self.assertIn(parsed_url.scheme, valid_scheme) @decorators.idempotent_id('c6bc11bf-592e-4015-9319-1c98dc64daf5') @testtools.skipUnless(CONF.compute_feature_enabled.vnc_console, 'VNC Console feature is disabled.') def test_get_vnc_console(self): # Get the VNC console of type 'novnc' and 'xvpvnc' console_types = ['novnc', 'xvpvnc'] for console_type in console_types: body = self.client.get_vnc_console(self.server_id, type=console_type)['console'] self.assertEqual(console_type, body['type']) self.assertNotEqual('', body['url']) self._validate_url(body['url']) tempest-17.2.0/tempest/api/compute/servers/test_server_addresses.py0000666000175100017510000000543713207044712025674 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import utils from tempest.lib import decorators class ServerAddressesTestJSON(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): # This test module might use a network and a subnet cls.set_network_resources(network=True, subnet=True) super(ServerAddressesTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(ServerAddressesTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerAddressesTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') @decorators.attr(type='smoke') @decorators.idempotent_id('6eb718c0-02d9-4d5e-acd1-4e0c269cef39') @utils.services('network') def test_list_server_addresses(self): # All public and private addresses for # a server should be returned addresses = self.client.list_addresses(self.server['id'])['addresses'] # We do not know the exact network configuration, but an instance # should at least have a single public or private address self.assertNotEmpty(addresses) for network_addresses in addresses.values(): self.assertNotEmpty(network_addresses) @decorators.attr(type='smoke') @decorators.idempotent_id('87bbc374-5538-4f64-b673-2b0e4443cc30') @utils.services('network') def test_list_server_addresses_by_network(self): # Providing a network type should filter # the addresses return by that type addresses = self.client.list_addresses(self.server['id'])['addresses'] # Once again we don't know the environment's exact network config, # but the response for each individual network should be the same # as the partial result of the full address list id = self.server['id'] for addr_type in addresses: addr = self.client.list_addresses_by_network(id, addr_type) addr = addr[addr_type] for address in addresses[addr_type]: self.assertTrue(any([a for a in addr if a == address])) tempest-17.2.0/tempest/api/compute/servers/test_server_password.py0000666000175100017510000000260213207044712025550 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators class ServerPasswordTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(ServerPasswordTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(ServerPasswordTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until="ACTIVE") @decorators.idempotent_id('f83b582f-62a8-4f22-85b0-0dee50ff783a') def test_get_server_password(self): self.client.show_password(self.server['id']) @decorators.idempotent_id('f8229e8b-b625-4493-800a-bde86ac611ea') def test_delete_server_password(self): self.client.delete_password(self.server['id']) tempest-17.2.0/tempest/api/compute/servers/test_multiple_create_negative.py0000666000175100017510000000456413207044712027371 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class MultipleCreateNegativeTestJSON(base.BaseV2ComputeTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('daf29d8d-e928-4a01-9a8c-b129603f3fc0') def test_min_count_less_than_one(self): invalid_min_count = 0 self.assertRaises(lib_exc.BadRequest, self.create_test_server, min_count=invalid_min_count) @decorators.attr(type=['negative']) @decorators.idempotent_id('999aa722-d624-4423-b813-0d1ac9884d7a') def test_min_count_non_integer(self): invalid_min_count = 2.5 self.assertRaises(lib_exc.BadRequest, self.create_test_server, min_count=invalid_min_count) @decorators.attr(type=['negative']) @decorators.idempotent_id('a6f9c2ab-e060-4b82-b23c-4532cb9390ff') def test_max_count_less_than_one(self): invalid_max_count = 0 self.assertRaises(lib_exc.BadRequest, self.create_test_server, max_count=invalid_max_count) @decorators.attr(type=['negative']) @decorators.idempotent_id('9c5698d1-d7af-4c80-b971-9d403135eea2') def test_max_count_non_integer(self): invalid_max_count = 2.5 self.assertRaises(lib_exc.BadRequest, self.create_test_server, max_count=invalid_max_count) @decorators.attr(type=['negative']) @decorators.idempotent_id('476da616-f1ef-4271-a9b1-b9fc87727cdf') def test_max_count_less_than_min_count(self): min_count = 3 max_count = 2 self.assertRaises(lib_exc.BadRequest, self.create_test_server, min_count=min_count, max_count=max_count) tempest-17.2.0/tempest/api/compute/servers/test_device_tagging.py0000666000175100017510000002740513207044712025267 0ustar zuulzuul00000000000000# Copyright (C) 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. import json from oslo_log import log as logging from tempest.api.compute import base from tempest.common import utils from tempest.common.utils.linux import remote_client from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF LOG = logging.getLogger(__name__) class DeviceTaggingTest(base.BaseV2ComputeTest): min_microversion = '2.32' # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a # bug in the 2.32 microversion, tags on block devices only worked with the # 2.32 microversion specifically. And tags on networks only worked between # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks. max_microversion = '2.32' @classmethod def skip_checks(cls): super(DeviceTaggingTest, cls).skip_checks() if not CONF.service_available.neutron: raise cls.skipException('Neutron is required') if not CONF.validation.run_validation: raise cls.skipException('Validation must be enabled') if (not CONF.compute_feature_enabled.config_drive and not CONF.compute_feature_enabled.metadata_service): raise cls.skipException('One of metadata or config drive must be ' 'enabled') @classmethod def setup_clients(cls): super(DeviceTaggingTest, cls).setup_clients() cls.networks_client = cls.os_primary.networks_client cls.ports_client = cls.os_primary.ports_client cls.subnets_client = cls.os_primary.subnets_client cls.interfaces_client = cls.os_primary.interfaces_client @classmethod def setup_credentials(cls): cls.set_network_resources(network=True, subnet=True, router=True, dhcp=True) super(DeviceTaggingTest, cls).setup_credentials() def verify_device_metadata(self, md_json): md_dict = json.loads(md_json) for d in md_dict['devices']: if d['type'] == 'nic': if d['mac'] == self.port1['mac_address']: self.assertEqual(d['tags'], ['port-1']) if d['mac'] == self.port2['mac_address']: self.assertEqual(d['tags'], ['port-2']) if d['mac'] == self.net_2_100_mac: self.assertEqual(d['tags'], ['net-2-100']) if d['mac'] == self.net_2_200_mac: self.assertEqual(d['tags'], ['net-2-200']) # A hypervisor may present multiple paths to a tagged disk, so # there may be duplicated tags in the metadata, use set() to # remove duplicated tags. found_devices = [d['tags'][0] for d in md_dict['devices']] self.assertEqual(set(found_devices), set(['port-1', 'port-2', 'net-1', 'net-2-100', 'net-2-200', 'boot', 'other'])) @decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96') @utils.services('network', 'volume', 'image') def test_device_tagging(self): # Create volumes # The create_volume methods waits for the volumes to be available and # the base class will clean them up on tearDown. boot_volume = self.create_volume(CONF.compute.image_ref) other_volume = self.create_volume() untagged_volume = self.create_volume() # Create networks net1 = self.networks_client.create_network( name=data_utils.rand_name('device-tagging-net1'))['network'] self.addCleanup(self.networks_client.delete_network, net1['id']) net2 = self.networks_client.create_network( name=data_utils.rand_name('device-tagging-net2'))['network'] self.addCleanup(self.networks_client.delete_network, net2['id']) # Create subnets subnet1 = self.subnets_client.create_subnet( network_id=net1['id'], cidr='10.1.1.0/24', ip_version=4)['subnet'] self.addCleanup(self.subnets_client.delete_subnet, subnet1['id']) subnet2 = self.subnets_client.create_subnet( network_id=net2['id'], cidr='10.2.2.0/24', ip_version=4)['subnet'] self.addCleanup(self.subnets_client.delete_subnet, subnet2['id']) # Create ports self.port1 = self.ports_client.create_port( network_id=net1['id'], fixed_ips=[{'subnet_id': subnet1['id']}])['port'] self.addCleanup(self.ports_client.delete_port, self.port1['id']) self.port2 = self.ports_client.create_port( network_id=net1['id'], fixed_ips=[{'subnet_id': subnet1['id']}])['port'] self.addCleanup(self.ports_client.delete_port, self.port2['id']) # Create server admin_pass = data_utils.rand_password() config_drive_enabled = CONF.compute_feature_enabled.config_drive validation_resources = self.get_test_validation_resources( self.os_primary) server = self.create_test_server( validatable=True, validation_resources=validation_resources, config_drive=config_drive_enabled, adminPass=admin_pass, name=data_utils.rand_name('device-tagging-server'), networks=[ # Validation network for ssh { 'uuid': self.get_tenant_network()['id'] }, # Different tags for different ports { 'port': self.port1['id'], 'tag': 'port-1' }, { 'port': self.port2['id'], 'tag': 'port-2' }, # Two nics on same net, one tagged one not { 'uuid': net1['id'], 'tag': 'net-1' }, { 'uuid': net1['id'] }, # Two nics on same net, different IP { 'uuid': net2['id'], 'fixed_ip': '10.2.2.100', 'tag': 'net-2-100' }, { 'uuid': net2['id'], 'fixed_ip': '10.2.2.200', 'tag': 'net-2-200' } ], block_device_mapping_v2=[ # Boot volume { 'uuid': boot_volume['id'], 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'tag': 'boot' }, # Other volume { 'uuid': other_volume['id'], 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 1, 'tag': 'other' }, # Untagged volume { 'uuid': untagged_volume['id'], 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 2 } ]) self.addCleanup(self.delete_server, server['id']) self.ssh_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), CONF.validation.image_ssh_user, admin_pass, validation_resources['keypair']['private_key'], server=server, servers_client=self.servers_client) # Find the MAC addresses of our fixed IPs self.net_2_100_mac = None self.net_2_200_mac = None ifaces = self.interfaces_client.list_interfaces(server['id']) for iface in ifaces['interfaceAttachments']: if 'fixed_ips' in iface: for ip in iface['fixed_ips']: if ip['ip_address'] == '10.2.2.100': self.net_2_100_mac = iface['mac_addr'] if ip['ip_address'] == '10.2.2.200': self.net_2_200_mac = iface['mac_addr'] # Make sure we have the MACs we need, there's no reason for some to be # missing self.assertTrue(self.net_2_100_mac) self.assertTrue(self.net_2_200_mac) # Verify metadata from metadata service if CONF.compute_feature_enabled.metadata_service: md_url = 'http://169.254.169.254/openstack/latest/meta_data.json' LOG.info('Attempting to verify tagged devices in server %s via ' 'the metadata service: %s', server['id'], md_url) def get_and_verify_metadata(): try: self.ssh_client.exec_command('curl -V') except exceptions.SSHExecCommandFailed: if not CONF.compute_feature_enabled.config_drive: raise self.skipException('curl not found in guest ' 'and config drive is ' 'disabled') LOG.warning('curl was not found in the guest, device ' 'tagging metadata was not checked in the ' 'metadata API') return True cmd = 'curl %s' % md_url md_json = self.ssh_client.exec_command(cmd) self.verify_device_metadata(md_json) return True if not test_utils.call_until_true(get_and_verify_metadata, CONF.compute.build_timeout, CONF.compute.build_interval): raise exceptions.TimeoutException('Timeout while verifying ' 'metadata on server.') # Verify metadata on config drive if CONF.compute_feature_enabled.config_drive: cmd_blkid = 'blkid -t LABEL=config-2 -o device' LOG.info('Attempting to verify tagged devices in server %s via ' 'the config drive.', server['id']) dev_name = self.ssh_client.exec_command(cmd_blkid) dev_name = dev_name.rstrip() try: self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name) except exceptions.SSHExecCommandFailed: # So the command failed, let's try to know why and print some # useful information. lsblk = self.ssh_client.exec_command('sudo lsblk --fs --ascii') LOG.error("Mounting %s on /mnt failed. Right after the " "failure 'lsblk' in the guest reported:\n%s", dev_name, lsblk) raise cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json' md_json = self.ssh_client.exec_command(cmd_md) self.verify_device_metadata(md_json) class DeviceTaggingTestV2_42(DeviceTaggingTest): min_microversion = '2.42' max_microversion = 'latest' tempest-17.2.0/tempest/api/compute/servers/test_create_server_multi_nic.py0000666000175100017510000001216013207044712027214 0ustar zuulzuul00000000000000# 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 netaddr import testtools from tempest.api.compute import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class ServersTestMultiNic(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): cls.prepare_instance_network() super(ServersTestMultiNic, cls).setup_credentials() @classmethod def setup_clients(cls): super(ServersTestMultiNic, cls).setup_clients() cls.client = cls.servers_client cls.networks_client = cls.os_primary.networks_client cls.subnets_client = cls.os_primary.subnets_client def _create_net_subnet_ret_net_from_cidr(self, cidr): name_net = data_utils.rand_name(self.__class__.__name__) net = self.networks_client.create_network(name=name_net) self.addCleanup(self.networks_client.delete_network, net['network']['id']) subnet = self.subnets_client.create_subnet( network_id=net['network']['id'], cidr=cidr, ip_version=4) self.addCleanup(self.subnets_client.delete_subnet, subnet['subnet']['id']) return net @decorators.idempotent_id('0578d144-ed74-43f8-8e57-ab10dbf9b3c2') @testtools.skipUnless(CONF.service_available.neutron, 'Neutron service must be available.') def test_verify_multiple_nics_order(self): # Verify that the networks order given at the server creation is # preserved within the server. net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24') net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24') networks = [{'uuid': net1['network']['id']}, {'uuid': net2['network']['id']}] server_multi_nics = self.create_test_server( networks=networks, wait_until='ACTIVE') # Cleanup server; this is needed in the test case because with the LIFO # nature of the cleanups, if we don't delete the server first, the port # will still be part of the subnet and we'll get a 409 from Neutron # when trying to delete the subnet. The tear down in the base class # will try to delete the server and get a 404 but it's ignored so # we're OK. self.addCleanup(self.delete_server, server_multi_nics['id']) addresses = (self.client.list_addresses(server_multi_nics['id']) ['addresses']) # We can't predict the ip addresses assigned to the server on networks. # Sometimes the assigned addresses are ['19.80.0.2', '19.86.0.2'], at # other times ['19.80.0.3', '19.86.0.3']. So we check if the first # address is in first network, similarly second address is in second # network. addr = [addresses[net1['network']['name']][0]['addr'], addresses[net2['network']['name']][0]['addr']] networks = [netaddr.IPNetwork('19.80.0.0/24'), netaddr.IPNetwork('19.86.0.0/24')] for address, network in zip(addr, networks): self.assertIn(address, network) @decorators.idempotent_id('1678d144-ed74-43f8-8e57-ab10dbf9b3c2') @testtools.skipUnless(CONF.service_available.neutron, 'Neutron service must be available.') def test_verify_duplicate_network_nics(self): # Verify that server creation does not fail when more than one nic # is created on the same network. net1 = self._create_net_subnet_ret_net_from_cidr('19.80.0.0/24') net2 = self._create_net_subnet_ret_net_from_cidr('19.86.0.0/24') networks = [{'uuid': net1['network']['id']}, {'uuid': net2['network']['id']}, {'uuid': net1['network']['id']}] server_multi_nics = self.create_test_server( networks=networks, wait_until='ACTIVE') self.addCleanup(self.delete_server, server_multi_nics['id']) addresses = (self.client.list_addresses(server_multi_nics['id']) ['addresses']) addr = [addresses[net1['network']['name']][0]['addr'], addresses[net2['network']['name']][0]['addr'], addresses[net1['network']['name']][1]['addr']] networks = [netaddr.IPNetwork('19.80.0.0/24'), netaddr.IPNetwork('19.86.0.0/24'), netaddr.IPNetwork('19.80.0.0/24')] for address, network in zip(addr, networks): self.assertIn(address, network) tempest-17.2.0/tempest/api/compute/servers/test_virtual_interfaces.py0000666000175100017510000000513113207044712026211 0ustar zuulzuul00000000000000# 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 netaddr import testtools from tempest.api.compute import base from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class VirtualInterfacesTestJSON(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): # This test needs a network and a subnet cls.set_network_resources(network=True, subnet=True) super(VirtualInterfacesTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(VirtualInterfacesTestJSON, cls).setup_clients() cls.client = cls.servers_client @classmethod def resource_setup(cls): super(VirtualInterfacesTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') @decorators.idempotent_id('96c4e2ef-5e4d-4d7f-87f5-fed6dca18016') @utils.services('network') def test_list_virtual_interfaces(self): # Positive test:Should be able to GET the virtual interfaces list # for a given server_id if CONF.service_available.neutron: # TODO(mriedem): After a microversion implements the API for # neutron, a 400 should be a failure for nova-network and neutron. with testtools.ExpectedException(exceptions.BadRequest): self.client.list_virtual_interfaces(self.server['id']) else: output = self.client.list_virtual_interfaces(self.server['id']) virt_ifaces = output['virtual_interfaces'] self.assertNotEmpty(virt_ifaces, 'Expected virtual interfaces, got 0 ' 'interfaces.') for virt_iface in virt_ifaces: mac_address = virt_iface['mac_address'] self.assertTrue(netaddr.valid_mac(mac_address), "Invalid mac address detected. mac address: %s" % mac_address) tempest-17.2.0/tempest/api/compute/servers/test_disk_config.py0000666000175100017510000001422313207044712024601 0ustar zuulzuul00000000000000# 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 from tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class ServerDiskConfigTestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ServerDiskConfigTestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.disk_config: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) @classmethod def setup_clients(cls): super(ServerDiskConfigTestJSON, cls).setup_clients() cls.client = cls.os_primary.servers_client def _update_server_with_disk_config(self, server_id, disk_config): server = self.client.show_server(server_id)['server'] if disk_config != server['OS-DCF:diskConfig']: server = self.client.update_server( server_id, disk_config=disk_config)['server'] waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') server = self.client.show_server(server['id'])['server'] self.assertEqual(disk_config, server['OS-DCF:diskConfig']) @decorators.idempotent_id('bef56b09-2e8c-4883-a370-4950812f430e') def test_rebuild_server_with_manual_disk_config(self): # A server should be rebuilt using the manual disk config option server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.client.delete_server, server['id']) self._update_server_with_disk_config(server['id'], disk_config='AUTO') server = self.client.rebuild_server(server['id'], self.image_ref_alt, disk_config='MANUAL')['server'] # Wait for the server to become active waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') # Verify the specified attributes are set correctly server = self.client.show_server(server['id'])['server'] self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) @decorators.idempotent_id('9c9fae77-4feb-402f-8450-bf1c8b609713') def test_rebuild_server_with_auto_disk_config(self): # A server should be rebuilt using the auto disk config option server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.client.delete_server, server['id']) self._update_server_with_disk_config(server['id'], disk_config='MANUAL') server = self.client.rebuild_server(server['id'], self.image_ref_alt, disk_config='AUTO')['server'] # Wait for the server to become active waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') # Verify the specified attributes are set correctly server = self.client.show_server(server['id'])['server'] self.assertEqual('AUTO', server['OS-DCF:diskConfig']) @decorators.idempotent_id('414e7e93-45b5-44bc-8e03-55159c6bfc97') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_from_manual_to_auto(self): # A server should be resized from manual to auto disk config server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.client.delete_server, server['id']) self._update_server_with_disk_config(server['id'], disk_config='MANUAL') # Resize with auto option self.resize_server(server['id'], self.flavor_ref_alt, disk_config='AUTO') server = self.client.show_server(server['id'])['server'] self.assertEqual('AUTO', server['OS-DCF:diskConfig']) @decorators.idempotent_id('693d16f3-556c-489a-8bac-3d0ca2490bad') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_from_auto_to_manual(self): # A server should be resized from auto to manual disk config server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.client.delete_server, server['id']) self._update_server_with_disk_config(server['id'], disk_config='AUTO') # Resize with manual option self.resize_server(server['id'], self.flavor_ref_alt, disk_config='MANUAL') server = self.client.show_server(server['id'])['server'] self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) @decorators.idempotent_id('5ef18867-358d-4de9-b3c9-94d4ba35742f') def test_update_server_from_auto_to_manual(self): # A server should be updated from auto to manual disk config server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.client.delete_server, server['id']) self._update_server_with_disk_config(server['id'], disk_config='AUTO') # Update the disk_config attribute to manual server = self.client.update_server(server['id'], disk_config='MANUAL')['server'] waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE') # Verify the disk_config attribute is set correctly server = self.client.show_server(server['id'])['server'] self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) tempest-17.2.0/tempest/api/compute/floating_ips/0000775000175100017510000000000013207045130021665 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py0000666000175100017510000001255313207044712031373 0ustar zuulzuul00000000000000# 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 from tempest.api.compute.floating_ips import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class FloatingIPsNegativeTestJSON(base.BaseFloatingIPsTest): max_microversion = '2.35' @classmethod def resource_setup(cls): super(FloatingIPsNegativeTestJSON, cls).resource_setup() # Generating a nonexistent floatingIP id body = cls.client.list_floating_ips()['floating_ips'] floating_ip_ids = [floating_ip['id'] for floating_ip in body] while True: if CONF.service_available.neutron: cls.non_exist_id = data_utils.rand_uuid() else: cls.non_exist_id = data_utils.rand_int_id(start=999) if cls.non_exist_id not in floating_ip_ids: break @decorators.attr(type=['negative']) @decorators.idempotent_id('6e0f059b-e4dd-48fb-8207-06e3bba5b074') def test_allocate_floating_ip_from_nonexistent_pool(self): # Negative test:Allocation of a new floating IP from a nonexistent_pool # to a project should fail self.assertRaises(lib_exc.NotFound, self.client.create_floating_ip, pool="non_exist_pool") @decorators.attr(type=['negative']) @decorators.idempotent_id('ae1c55a8-552b-44d4-bfb6-2a115a15d0ba') def test_delete_nonexistent_floating_ip(self): # Negative test:Deletion of a nonexistent floating IP # from project should fail # Deleting the non existent floating IP self.assertRaises(lib_exc.NotFound, self.client.delete_floating_ip, self.non_exist_id) class FloatingIPsAssociationNegativeTestJSON(base.BaseFloatingIPsTest): max_microversion = '2.43' @classmethod def resource_setup(cls): super(FloatingIPsAssociationNegativeTestJSON, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = cls.server['id'] @decorators.attr(type=['negative']) @decorators.idempotent_id('595fa616-1a71-4670-9614-46564ac49a4c') def test_associate_nonexistent_floating_ip(self): # Negative test:Association of a non existent floating IP # to specific server should fail # Associating non existent floating IP self.assertRaises(lib_exc.NotFound, self.client.associate_floating_ip_to_server, "0.0.0.0", self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('0a081a66-e568-4e6b-aa62-9587a876dca8') def test_dissociate_nonexistent_floating_ip(self): # Negative test:Dissociation of a non existent floating IP should fail # Dissociating non existent floating IP self.assertRaises(lib_exc.NotFound, self.client.disassociate_floating_ip_from_server, "0.0.0.0", self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('804b4fcb-bbf5-412f-925d-896672b61eb3') def test_associate_ip_to_server_without_passing_floating_ip(self): # Negative test:Association of empty floating IP to specific server # should raise NotFound or BadRequest(In case of Nova V2.1) exception. self.assertRaises((lib_exc.NotFound, lib_exc.BadRequest), self.client.associate_floating_ip_to_server, '', self.server_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('58a80596-ffb2-11e6-9393-fa163e4fa634') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_associate_ip_to_server_with_floating_ip(self): # The VM have one port # Associate floating IP A to the VM # Associate floating IP B which is from same pool with floating IP A # to the VM, should raise BadRequest exception body = self.client.create_floating_ip( pool=CONF.network.public_network_id)['floating_ip'] self.addCleanup(self.client.delete_floating_ip, body['id']) self.client.associate_floating_ip_to_server(body['ip'], self.server_id) self.addCleanup(self.client.disassociate_floating_ip_from_server, body['ip'], self.server_id) body = self.client.create_floating_ip( pool=CONF.network.public_network_id)['floating_ip'] self.addCleanup(self.client.delete_floating_ip, body['id']) self.assertRaises(lib_exc.BadRequest, self.client.associate_floating_ip_to_server, body['ip'], self.server_id) tempest-17.2.0/tempest/api/compute/floating_ips/test_list_floating_ips.py0000666000175100017510000000621613207044712027023 0ustar zuulzuul00000000000000# 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 tempest.api.compute.floating_ips import base from tempest import config from tempest.lib import decorators CONF = config.CONF class FloatingIPDetailsTestJSON(base.BaseFloatingIPsTest): max_microversion = '2.35' @classmethod def resource_setup(cls): super(FloatingIPDetailsTestJSON, cls).resource_setup() cls.floating_ip = [] for _ in range(3): body = cls.client.create_floating_ip( pool=CONF.network.floating_network_name)['floating_ip'] cls.addClassResourceCleanup(cls.client.delete_floating_ip, body['id']) cls.floating_ip.append(body) @decorators.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f') def test_list_floating_ips(self): # Positive test:Should return the list of floating IPs body = self.client.list_floating_ips()['floating_ips'] floating_ips = body self.assertNotEmpty(floating_ips, "Expected floating IPs. Got zero.") for i in range(3): self.assertIn(self.floating_ip[i], floating_ips) @decorators.idempotent_id('eef497e0-8ff7-43c8-85ef-558440574f84') def test_get_floating_ip_details(self): # Positive test:Should be able to GET the details of floatingIP # Creating a floating IP for which details are to be checked body = self.client.create_floating_ip( pool=CONF.network.floating_network_name)['floating_ip'] floating_ip_id = body['id'] self.addCleanup(self.client.delete_floating_ip, floating_ip_id) floating_ip_instance_id = body['instance_id'] floating_ip_ip = body['ip'] floating_ip_fixed_ip = body['fixed_ip'] body = self.client.show_floating_ip(floating_ip_id)['floating_ip'] # Comparing the details of floating IP self.assertEqual(floating_ip_instance_id, body['instance_id']) self.assertEqual(floating_ip_ip, body['ip']) self.assertEqual(floating_ip_fixed_ip, body['fixed_ip']) self.assertEqual(floating_ip_id, body['id']) @decorators.idempotent_id('df389fc8-56f5-43cc-b290-20eda39854d3') def test_list_floating_ip_pools(self): # Positive test:Should return the list of floating IP Pools floating_ip_pools = self.pools_client.list_floating_ip_pools() self.assertNotEmpty(floating_ip_pools['floating_ip_pools'], "Expected floating IP Pools. Got zero.") tempest-17.2.0/tempest/api/compute/floating_ips/__init__.py0000666000175100017510000000000013207044712023773 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/floating_ips/base.py0000666000175100017510000000316613207044712023166 0ustar zuulzuul00000000000000# Copyright 2014 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 tempest.api.compute import base from tempest.common import utils from tempest import config CONF = config.CONF class BaseFloatingIPsTest(base.BaseV2ComputeTest): @classmethod def setup_credentials(cls): # Floating IP actions might need a full network configuration cls.set_network_resources(network=True, subnet=True, router=True, dhcp=True) super(BaseFloatingIPsTest, cls).setup_credentials() @classmethod def skip_checks(cls): super(BaseFloatingIPsTest, cls).skip_checks() if not utils.get_service_list()['network']: raise cls.skipException("network service not enabled.") if not CONF.network_feature_enabled.floating_ips: raise cls.skipException("Floating ips are not available") @classmethod def setup_clients(cls): super(BaseFloatingIPsTest, cls).setup_clients() cls.client = cls.floating_ips_client cls.pools_client = cls.floating_ip_pools_client tempest-17.2.0/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py0000666000175100017510000000302713207044712030702 0ustar zuulzuul00000000000000# 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 tempest.api.compute.floating_ips import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class FloatingIPDetailsNegativeTestJSON(base.BaseFloatingIPsTest): max_microversion = '2.35' @decorators.attr(type=['negative']) @decorators.idempotent_id('7ab18834-4a4b-4f28-a2c5-440579866695') def test_get_nonexistent_floating_ip_details(self): # Negative test:Should not be able to GET the details # of non-existent floating IP # Creating a non-existent floatingIP id if CONF.service_available.neutron: non_exist_id = data_utils.rand_uuid() else: non_exist_id = data_utils.rand_int_id(start=999) self.assertRaises(lib_exc.NotFound, self.client.show_floating_ip, non_exist_id) tempest-17.2.0/tempest/api/compute/floating_ips/test_floating_ips_actions.py0000666000175100017510000001277413207044712027516 0ustar zuulzuul00000000000000# 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 from tempest.api.compute.floating_ips import base from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class FloatingIPsTestJSON(base.BaseFloatingIPsTest): max_microversion = '2.35' @decorators.idempotent_id('f7bfb946-297e-41b8-9e8c-aba8e9bb5194') def test_allocate_floating_ip(self): # Positive test:Allocation of a new floating IP to a project # should be successful body = self.client.create_floating_ip( pool=CONF.network.floating_network_name)['floating_ip'] floating_ip_id_allocated = body['id'] self.addCleanup(self.client.delete_floating_ip, floating_ip_id_allocated) floating_ip_details = self.client.show_floating_ip( floating_ip_id_allocated)['floating_ip'] # Checking if the details of allocated IP is in list of floating IP body = self.client.list_floating_ips()['floating_ips'] self.assertIn(floating_ip_details, body) @decorators.idempotent_id('de45e989-b5ca-4a9b-916b-04a52e7bbb8b') def test_delete_floating_ip(self): # Positive test:Deletion of valid floating IP from project # should be successful # Creating the floating IP that is to be deleted in this method floating_ip_body = self.client.create_floating_ip( pool=CONF.network.floating_network_name)['floating_ip'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_floating_ip, floating_ip_body['id']) # Deleting the floating IP from the project self.client.delete_floating_ip(floating_ip_body['id']) # Check it was really deleted. self.client.wait_for_resource_deletion(floating_ip_body['id']) class FloatingIPsAssociationTestJSON(base.BaseFloatingIPsTest): max_microversion = '2.43' @classmethod def resource_setup(cls): super(FloatingIPsAssociationTestJSON, cls).resource_setup() # Server creation cls.server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = cls.server['id'] # Floating IP creation body = cls.client.create_floating_ip( pool=CONF.network.floating_network_name)['floating_ip'] cls.addClassResourceCleanup(cls.client.delete_floating_ip, body['id']) cls.floating_ip_id = body['id'] cls.floating_ip = body['ip'] @decorators.idempotent_id('307efa27-dc6f-48a0-8cd2-162ce3ef0b52') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_associate_disassociate_floating_ip(self): # Positive test:Associate and disassociate the provided floating IP # to a specific server should be successful # Association of floating IP to fixed IP address self.client.associate_floating_ip_to_server( self.floating_ip, self.server_id) # Check instance_id in the floating_ip body body = (self.client.show_floating_ip(self.floating_ip_id) ['floating_ip']) self.assertEqual(self.server_id, body['instance_id']) # Disassociation of floating IP that was associated in this method self.client.disassociate_floating_ip_from_server( self.floating_ip, self.server_id) @decorators.idempotent_id('6edef4b2-aaf1-4abc-bbe3-993e2561e0fe') @testtools.skipUnless(CONF.network.public_network_id, 'The public_network_id option must be specified.') def test_associate_already_associated_floating_ip(self): # positive test:Association of an already associated floating IP # to specific server should change the association of the Floating IP # Create server so as to use for Multiple association body = self.create_test_server(wait_until='ACTIVE') self.new_server_id = body['id'] self.addCleanup(self.servers_client.delete_server, self.new_server_id) # Associating floating IP for the first time self.client.associate_floating_ip_to_server( self.floating_ip, self.server_id) # Associating floating IP for the second time self.client.associate_floating_ip_to_server( self.floating_ip, self.new_server_id) self.addCleanup(self.client.disassociate_floating_ip_from_server, self.floating_ip, self.new_server_id) # Make sure no longer associated with old server self.assertRaises((lib_exc.NotFound, lib_exc.UnprocessableEntity, lib_exc.Conflict), self.client.disassociate_floating_ip_from_server, self.floating_ip, self.server_id) tempest-17.2.0/tempest/api/compute/admin/0000775000175100017510000000000013207045130020277 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/admin/test_fixed_ips_negative.py0000666000175100017510000000772313207044712025564 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.common import utils from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class FixedIPsNegativeTestJson(base.BaseV2ComputeAdminTest): @classmethod def skip_checks(cls): super(FixedIPsNegativeTestJson, cls).skip_checks() if CONF.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg) if not utils.get_service_list()['network']: raise cls.skipException("network service not enabled.") @classmethod def setup_clients(cls): super(FixedIPsNegativeTestJson, cls).setup_clients() cls.client = cls.os_admin.fixed_ips_client cls.non_admin_client = cls.fixed_ips_client @classmethod def resource_setup(cls): super(FixedIPsNegativeTestJson, cls).resource_setup() server = cls.create_test_server(wait_until='ACTIVE') server = cls.servers_client.show_server(server['id'])['server'] cls.ip = None for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break if cls.ip is None: raise cls.skipException("No fixed ip found for server: %s" % server['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('9f17f47d-daad-4adc-986e-12370c93e407') def test_list_fixed_ip_details_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.show_fixed_ip, self.ip) @decorators.attr(type=['negative']) @decorators.idempotent_id('ce60042c-fa60-4836-8d43-1c8e3359dc47') def test_set_reserve_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.reserve_fixed_ip, self.ip, reserve="None") @decorators.attr(type=['negative']) @decorators.idempotent_id('f1f7a35b-0390-48c5-9803-5f27461439db') def test_set_unreserve_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.reserve_fixed_ip, self.ip, unreserve="None") @decorators.attr(type=['negative']) @decorators.idempotent_id('f51cf464-7fc5-4352-bc3e-e75cfa2cb717') def test_set_reserve_with_invalid_ip(self): # NOTE(maurosr): since this exercises the same code snippet, we do it # only for reserve action # NOTE(eliqiao): in Juno, the exception is NotFound, but in master, we # change the error code to BadRequest, both exceptions should be # accepted by tempest self.assertRaises((lib_exc.NotFound, lib_exc.BadRequest), self.client.reserve_fixed_ip, "my.invalid.ip", reserve="None") @decorators.attr(type=['negative']) @decorators.idempotent_id('fd26ef50-f135-4232-9d32-281aab3f9176') def test_fixed_ip_with_invalid_action(self): self.assertRaises(lib_exc.BadRequest, self.client.reserve_fixed_ip, self.ip, invalid_action="None") tempest-17.2.0/tempest/api/compute/admin/test_flavors_access.py0000666000175100017510000000627613207044712024727 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.common import utils from tempest.lib import decorators class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest): """Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ @classmethod def skip_checks(cls): super(FlavorsAccessTestJSON, cls).skip_checks() if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'): msg = "OS-FLV-EXT-DATA extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(FlavorsAccessTestJSON, cls).resource_setup() # Non admin tenant ID cls.tenant_id = cls.flavors_client.tenant_id cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @decorators.idempotent_id('ea2c2211-29fa-4db9-97c3-906d36fad3e0') def test_flavor_access_list_with_private_flavor(self): # Test to make sure that list flavor access on a newly created # private flavor will return an empty access list flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='False') flavor_access = (self.admin_flavors_client.list_flavor_access( flavor['id'])['flavor_access']) self.assertEmpty(flavor_access) @decorators.idempotent_id('59e622f6-bdf6-45e3-8ba8-fedad905a6b4') def test_flavor_access_add_remove(self): # Test to add and remove flavor access to a given tenant. flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='False') # Add flavor access to a tenant. resp_body = { "tenant_id": str(self.tenant_id), "flavor_id": str(flavor['id']), } add_body = (self.admin_flavors_client.add_flavor_access( flavor['id'], self.tenant_id)['flavor_access']) self.assertIn(resp_body, add_body) # The flavor is present in list. flavors = self.flavors_client.list_flavors(detail=True)['flavors'] self.assertIn(flavor['id'], map(lambda x: x['id'], flavors)) # Remove flavor access from a tenant. remove_body = (self.admin_flavors_client.remove_flavor_access( flavor['id'], self.tenant_id)['flavor_access']) self.assertNotIn(resp_body, remove_body) # The flavor is not present in list. flavors = self.flavors_client.list_flavors(detail=True)['flavors'] self.assertNotIn(flavor['id'], map(lambda x: x['id'], flavors)) tempest-17.2.0/tempest/api/compute/admin/test_create_server.py0000666000175100017510000001157413207044712024560 0ustar zuulzuul00000000000000# 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 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.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setup_credentials(cls): cls.prepare_instance_network() super(ServersWithSpecificFlavorTestJSON, cls).setup_credentials() @classmethod def setup_clients(cls): super(ServersWithSpecificFlavorTestJSON, cls).setup_clients() cls.client = cls.servers_client @decorators.idempotent_id('b3c7bcfc-bb5b-4e22-b517-c7f686b802ca') @testtools.skipUnless(CONF.validation.run_validation, 'Instance validation tests are disabled.') def test_verify_created_server_ephemeral_disk(self): # Verify that the ephemeral disk is created when creating server flavor_base = self.flavors_client.show_flavor( self.flavor_ref)['flavor'] def create_flavor_with_ephemeral(ephem_disk): name = 'flavor_with_ephemeral_%s' % ephem_disk flavor_name = data_utils.rand_name(name) ram = flavor_base['ram'] vcpus = flavor_base['vcpus'] disk = flavor_base['disk'] # Create a flavor with ephemeral disk flavor = self.create_flavor(name=flavor_name, ram=ram, vcpus=vcpus, disk=disk, ephemeral=ephem_disk) return flavor['id'] flavor_with_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=1) flavor_no_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=0) admin_pass = self.image_ssh_password validation_resources = self.get_test_validation_resources( self.os_primary) server_no_eph_disk = self.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE', adminPass=admin_pass, flavor=flavor_no_eph_disk_id) self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server_no_eph_disk['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server_no_eph_disk['id']) # Get partition number of server without ephemeral disk. server_no_eph_disk = self.client.show_server( server_no_eph_disk['id'])['server'] linux_client = remote_client.RemoteClient( self.get_server_ip(server_no_eph_disk, validation_resources), self.ssh_user, admin_pass, validation_resources['keypair']['private_key'], server=server_no_eph_disk, servers_client=self.client) disks_num = len(linux_client.get_disks().split('\n')) # Explicit server deletion necessary for Juno compatibility self.client.delete_server(server_no_eph_disk['id']) server_with_eph_disk = self.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE', adminPass=admin_pass, flavor=flavor_with_eph_disk_id) self.addCleanup(waiters.wait_for_server_termination, self.servers_client, server_with_eph_disk['id']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.servers_client.delete_server, server_with_eph_disk['id']) server_with_eph_disk = self.client.show_server( server_with_eph_disk['id'])['server'] linux_client = remote_client.RemoteClient( self.get_server_ip(server_with_eph_disk, validation_resources), self.ssh_user, admin_pass, validation_resources['keypair']['private_key'], server=server_with_eph_disk, servers_client=self.client) disks_num_eph = len(linux_client.get_disks().split('\n')) self.assertEqual(disks_num + 1, disks_num_eph) tempest-17.2.0/tempest/api/compute/admin/test_hosts.py0000666000175100017510000000541213207044712023061 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.lib import decorators class HostsAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests hosts API using admin privileges.""" @classmethod def setup_clients(cls): super(HostsAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.hosts_client @decorators.idempotent_id('9bfaf98d-e2cb-44b0-a07e-2558b2821e4f') def test_list_hosts(self): hosts = self.client.list_hosts()['hosts'] self.assertGreaterEqual(len(hosts), 2, str(hosts)) @decorators.idempotent_id('5dc06f5b-d887-47a2-bb2a-67762ef3c6de') def test_list_hosts_with_zone(self): self.useFixture(fixtures.LockFixture('availability_zone')) hosts = self.client.list_hosts()['hosts'] host = hosts[0] hosts = self.client.list_hosts(zone=host['zone'])['hosts'] self.assertNotEmpty(hosts) self.assertIn(host, hosts) @decorators.idempotent_id('9af3c171-fbf4-4150-a624-22109733c2a6') def test_list_hosts_with_a_blank_zone(self): # If send the request with a blank zone, the request will be successful # and it will return all the hosts list hosts = self.client.list_hosts(zone='')['hosts'] self.assertNotEmpty(hosts) @decorators.idempotent_id('c6ddbadb-c94e-4500-b12f-8ffc43843ff8') def test_list_hosts_with_nonexistent_zone(self): # If send the request with a nonexistent zone, the request will be # successful and no hosts will be returned hosts = self.client.list_hosts(zone='xxx')['hosts'] self.assertEmpty(hosts) @decorators.idempotent_id('38adbb12-aee2-4498-8aec-329c72423aa4') def test_show_host_detail(self): hosts = self.client.list_hosts()['hosts'] hosts = [host for host in hosts if host['service'] == 'compute'] self.assertNotEmpty(hosts) for host in hosts: hostname = host['host_name'] resources = self.client.show_host(hostname)['host'] self.assertNotEmpty(resources) host_resource = resources[0]['resource'] self.assertEqual(hostname, host_resource['host']) tempest-17.2.0/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py0000666000175100017510000000411213207044712030616 0ustar zuulzuul00000000000000# 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. import datetime from six.moves.urllib import parse as urllib from tempest.api.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class InstanceUsageAuditLogNegativeTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setup_clients(cls): super(InstanceUsageAuditLogNegativeTestJSON, cls).setup_clients() cls.adm_client = cls.os_admin.instance_usages_audit_log_client @decorators.attr(type=['negative']) @decorators.idempotent_id('a9d33178-d2c9-4131-ad3b-f4ca8d0308a2') def test_instance_usage_audit_logs_with_nonadmin_user(self): # the instance_usage_audit_logs API just can be accessed by admin user self.assertRaises(lib_exc.Forbidden, self.instance_usages_audit_log_client. list_instance_usage_audit_logs) now = datetime.datetime.now() self.assertRaises(lib_exc.Forbidden, self.instance_usages_audit_log_client. show_instance_usage_audit_log, urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S"))) @decorators.attr(type=['negative']) @decorators.idempotent_id('9b952047-3641-41c7-ba91-a809fc5974c8') def test_get_instance_usage_audit_logs_with_invalid_time(self): self.assertRaises(lib_exc.BadRequest, self.adm_client.show_instance_usage_audit_log, "invalid_time") tempest-17.2.0/tempest/api/compute/admin/test_auto_allocate_network.py0000666000175100017510000002231213207044712026304 0ustar zuulzuul00000000000000# Copyright 2016 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_log import log from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_excs CONF = config.CONF LOG = log.getLogger(__name__) # NOTE(mriedem): This is in the admin directory only because it requires # force_tenant_isolation=True, but doesn't extend BaseV2ComputeAdminTest # because it doesn't actually use any admin credentials in the tests. class AutoAllocateNetworkTest(base.BaseV2ComputeTest): """Tests auto-allocating networks with the v2.37 microversion. These tests rely on Neutron being enabled. Also, the tenant must not have any network resources available to it so we can make sure that Nova calls to Neutron to automatically allocate the network topology. """ force_tenant_isolation = True min_microversion = '2.37' max_microversion = 'latest' @classmethod def skip_checks(cls): super(AutoAllocateNetworkTest, cls).skip_checks() if not CONF.service_available.neutron: raise cls.skipException('Neutron is required') if not utils.is_extension_enabled('auto-allocated-topology', 'network'): raise cls.skipException( 'auto-allocated-topology extension is not available') @classmethod def setup_credentials(cls): # Do not create network resources for these tests. cls.set_network_resources() super(AutoAllocateNetworkTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(AutoAllocateNetworkTest, cls).setup_clients() cls.networks_client = cls.os_primary.networks_client cls.routers_client = cls.os_primary.routers_client cls.subnets_client = cls.os_primary.subnets_client cls.ports_client = cls.os_primary.ports_client @classmethod def resource_setup(cls): super(AutoAllocateNetworkTest, cls).resource_setup() # Sanity check that there are no networks available to the tenant. # This is essentially what Nova does for getting available networks. tenant_id = cls.networks_client.tenant_id # (1) Retrieve non-public network list owned by the tenant. search_opts = {'tenant_id': tenant_id, 'shared': False} nets = cls.networks_client.list_networks( **search_opts).get('networks', []) if nets: raise lib_excs.TempestException( 'Found tenant networks: %s' % nets) # (2) Retrieve shared network list. search_opts = {'shared': True} nets = cls.networks_client.list_networks( **search_opts).get('networks', []) if nets: raise lib_excs.TempestException( 'Found shared networks: %s' % nets) @classmethod def resource_cleanup(cls): """Deletes any auto_allocated_network and it's associated resources.""" # Find the auto-allocated router for the tenant. # This is a bit hacky since we don't have a great way to find the # auto-allocated router given the private tenant network we have. routers = cls.routers_client.list_routers().get('routers', []) if len(routers) > 1: # This indicates a race where nova is concurrently calling the # neutron auto-allocated-topology API for multiple server builds # at the same time (it's called from nova-compute when setting up # networking for a server). Neutron will detect duplicates and # automatically clean them up, but there is a window where the API # can return multiple and we don't have a good way to filter those # out right now, so we'll just handle them. LOG.info('(%s) Found more than one router for tenant.', test_utils.find_test_caller()) # Remove any networks, duplicate or otherwise, that these tests # created. All such networks will be in the current tenant. Neutron # will cleanup duplicate resources automatically, so ignore 404s. search_opts = {'tenant_id': cls.networks_client.tenant_id} networks = cls.networks_client.list_networks( **search_opts).get('networks', []) for router in routers: # Disassociate the subnets from the router. Because of the race # mentioned above the subnets might not be associated with the # router so ignore any 404. for network in networks: for subnet_id in network['subnets']: test_utils.call_and_ignore_notfound_exc( cls.routers_client.remove_router_interface, router['id'], subnet_id=subnet_id) # Delete the router. cls.routers_client.delete_router(router['id']) for network in networks: # Get and delete the ports for the given network. ports = cls.ports_client.list_ports( network_id=network['id']).get('ports', []) for port in ports: test_utils.call_and_ignore_notfound_exc( cls.ports_client.delete_port, port['id']) # Delete the subnets. for subnet_id in network['subnets']: test_utils.call_and_ignore_notfound_exc( cls.subnets_client.delete_subnet, subnet_id) # Delete the network. test_utils.call_and_ignore_notfound_exc( cls.networks_client.delete_network, network['id']) super(AutoAllocateNetworkTest, cls).resource_cleanup() @decorators.idempotent_id('5eb7b8fa-9c23-47a2-9d7d-02ed5809dd34') def test_server_create_no_allocate(self): """Tests that no networking is allocated for the server.""" # create the server with no networking server, _ = compute.create_test_server( self.os_primary, networks='none', wait_until='ACTIVE') self.addCleanup(self.delete_server, server['id']) # get the server ips addresses = self.servers_client.list_addresses( server['id'])['addresses'] # assert that there is no networking self.assertEqual({}, addresses) @decorators.idempotent_id('2e6cf129-9e28-4e8a-aaaa-045ea826b2a6') def test_server_multi_create_auto_allocate(self): """Tests that networking is auto-allocated for multiple servers.""" # Create multiple servers with auto networking to make sure the # automatic network allocation is atomic. Using a minimum of three # servers is essential for this scenario because: # # - First request sees no networks for the tenant so it auto-allocates # one from Neutron, let's call that net1. # - Second request sees no networks for the tenant so it auto-allocates # one from Neutron. Neutron creates net2 but sees it's a duplicate # so it queues net2 for deletion and returns net1 from the API and # Nova uses that for the second server request. # - Third request sees net1 and net2 for the tenant and fails with a # NetworkAmbiguous 400 error. _, servers = compute.create_test_server( self.os_primary, networks='auto', wait_until='ACTIVE', min_count=3) for server in servers: self.addCleanup(self.delete_server, server['id']) server_nets = set() for server in servers: # get the server ips addresses = self.servers_client.list_addresses( server['id'])['addresses'] # assert that there is networking (should only be one) self.assertEqual(1, len(addresses)) server_nets.add(list(addresses.keys())[0]) # all servers should be on the same network self.assertEqual(1, len(server_nets)) # List the networks for the tenant; we filter on admin_state_up=True # because the auto-allocated-topology code in Neutron won't set that # to True until the network is ready and is returned from the API. # Duplicate networks created from a race should have # admin_state_up=False. search_opts = {'tenant_id': self.networks_client.tenant_id, 'shared': False, 'admin_state_up': True} nets = self.networks_client.list_networks( **search_opts).get('networks', []) self.assertEqual(1, len(nets)) # verify the single private tenant network is the one that the servers # are using also self.assertIn(nets[0]['name'], server_nets) tempest-17.2.0/tempest/api/compute/admin/test_migrations.py0000666000175100017510000001322213207044712024073 0ustar zuulzuul00000000000000# 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 testtools from tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class MigrationsAdminTest(base.BaseV2ComputeAdminTest): @classmethod def setup_clients(cls): super(MigrationsAdminTest, cls).setup_clients() cls.client = cls.os_admin.migrations_client @decorators.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f') def test_list_migrations(self): # Admin can get the migrations list self.client.list_migrations() @decorators.idempotent_id('1b512062-8093-438e-b47a-37d2f597cd64') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_list_migrations_in_flavor_resize_situation(self): # Admin can get the migrations list which contains the resized server server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] self.resize_server(server_id, self.flavor_ref_alt) body = self.client.list_migrations()['migrations'] instance_uuids = [x['instance_uuid'] for x in body] self.assertIn(server_id, instance_uuids) def _flavor_clean_up(self, flavor_id): try: self.admin_flavors_client.delete_flavor(flavor_id) self.admin_flavors_client.wait_for_resource_deletion(flavor_id) except exceptions.NotFound: pass @decorators.idempotent_id('33f1fec3-ba18-4470-8e4e-1d888e7c3593') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') def test_resize_server_revert_deleted_flavor(self): # Tests that we can revert the resize on an instance whose original # flavor has been deleted. # First we have to create a flavor that we can delete so make a copy # of the normal flavor from which we'd create a server. flavor = self.admin_flavors_client.show_flavor( self.flavor_ref)['flavor'] flavor = self.admin_flavors_client.create_flavor( name=data_utils.rand_name('test_resize_flavor_'), ram=flavor['ram'], disk=flavor['disk'], vcpus=flavor['vcpus'] )['flavor'] self.addCleanup(self._flavor_clean_up, flavor['id']) # Now boot a server with the copied flavor. server = self.create_test_server( wait_until='ACTIVE', flavor=flavor['id']) # Delete the flavor we used to boot the instance. self._flavor_clean_up(flavor['id']) # Now resize the server and wait for it to go into verify state. self.servers_client.resize_server(server['id'], self.flavor_ref_alt) waiters.wait_for_server_status(self.servers_client, server['id'], 'VERIFY_RESIZE') # Now revert the resize, it should be OK even though the original # flavor used to boot the server was deleted. self.servers_client.revert_resize_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') server = self.servers_client.show_server(server['id'])['server'] self.assertEqual(flavor['id'], server['flavor']['id']) def _test_cold_migrate_server(self, revert=False): if CONF.compute.min_compute_nodes < 2: msg = "Less than 2 compute nodes, skipping multinode tests." raise self.skipException(msg) server = self.create_test_server(wait_until="ACTIVE") src_host = self.admin_servers_client.show_server( server['id'])['server']['OS-EXT-SRV-ATTR:host'] self.admin_servers_client.migrate_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'VERIFY_RESIZE') if revert: self.servers_client.revert_resize_server(server['id']) assert_func = self.assertEqual else: self.servers_client.confirm_resize_server(server['id']) assert_func = self.assertNotEqual waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') dst_host = self.admin_servers_client.show_server( server['id'])['server']['OS-EXT-SRV-ATTR:host'] assert_func(src_host, dst_host) @decorators.idempotent_id('4bf0be52-3b6f-4746-9a27-3143636fe30d') @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, 'Cold migration not available.') def test_cold_migration(self): self._test_cold_migrate_server(revert=False) @decorators.idempotent_id('caa1aa8b-f4ef-4374-be0d-95f001c2ac2d') @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, 'Cold migration not available.') def test_revert_cold_migration(self): self._test_cold_migrate_server(revert=True) tempest-17.2.0/tempest/api/compute/admin/test_volume_swap.py0000666000175100017510000001403013207044712024256 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 time from tempest.api.compute import base from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class TestVolumeSwap(base.BaseV2ComputeAdminTest): """The test suite for swapping of volume with admin user. The following is the scenario outline: 1. Create a volume "volume1" with non-admin. 2. Create a volume "volume2" with non-admin. 3. Boot an instance "instance1" with non-admin. 4. Attach "volume1" to "instance1" with non-admin. 5. Swap volume from "volume1" to "volume2" as admin. 6. Check the swap volume is successful and "volume2" is attached to "instance1" and "volume1" is in available state. 7. Swap volume from "volume2" to "volume1" as admin. 8. Check the swap volume is successful and "volume1" is attached to "instance1" and "volume2" is in available state. """ @classmethod def skip_checks(cls): super(TestVolumeSwap, cls).skip_checks() if not CONF.compute_feature_enabled.swap_volume: raise cls.skipException("Swapping volumes is not supported.") def _wait_for_server_volume_swap(self, server_id, old_volume_id, new_volume_id): """Waits for a server to swap the old volume to a new one.""" volume_attachments = self.servers_client.list_volume_attachments( server_id)['volumeAttachments'] attached_volume_ids = [attachment['volumeId'] for attachment in volume_attachments] start = int(time.time()) while (old_volume_id in attached_volume_ids) \ or (new_volume_id not in attached_volume_ids): time.sleep(self.servers_client.build_interval) volume_attachments = self.servers_client.list_volume_attachments( server_id)['volumeAttachments'] attached_volume_ids = [attachment['volumeId'] for attachment in volume_attachments] if int(time.time()) - start >= self.servers_client.build_timeout: old_vol_bdm_status = 'in BDM' \ if old_volume_id in attached_volume_ids else 'not in BDM' new_vol_bdm_status = 'in BDM' \ if new_volume_id in attached_volume_ids else 'not in BDM' message = ('Failed to swap old volume %(old_volume_id)s ' '(current %(old_vol_bdm_status)s) to new volume ' '%(new_volume_id)s (current %(new_vol_bdm_status)s)' ' on server %(server_id)s within the required time ' '(%(timeout)s s)' % {'old_volume_id': old_volume_id, 'old_vol_bdm_status': old_vol_bdm_status, 'new_volume_id': new_volume_id, 'new_vol_bdm_status': new_vol_bdm_status, 'server_id': server_id, 'timeout': self.servers_client.build_timeout}) raise lib_exc.TimeoutException(message) @decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813') @utils.services('volume') def test_volume_swap(self): # Create two volumes. # NOTE(gmann): Volumes are created before server creation so that # volumes cleanup can happen successfully irrespective of which volume # is attached to server. volume1 = self.create_volume() volume2 = self.create_volume() # Boot server server = self.create_test_server(wait_until='ACTIVE') # Attach "volume1" to server self.attach_volume(server, volume1) # Swap volume from "volume1" to "volume2" self.admin_servers_client.update_attached_volume( server['id'], volume1['id'], volumeId=volume2['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume1['id'], 'available') waiters.wait_for_volume_resource_status(self.volumes_client, volume2['id'], 'in-use') self._wait_for_server_volume_swap(server['id'], volume1['id'], volume2['id']) # Verify "volume2" is attached to the server vol_attachments = self.servers_client.list_volume_attachments( server['id'])['volumeAttachments'] self.assertEqual(1, len(vol_attachments)) self.assertIn(volume2['id'], vol_attachments[0]['volumeId']) # Swap volume from "volume2" to "volume1" self.admin_servers_client.update_attached_volume( server['id'], volume2['id'], volumeId=volume1['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume2['id'], 'available') waiters.wait_for_volume_resource_status(self.volumes_client, volume1['id'], 'in-use') self._wait_for_server_volume_swap(server['id'], volume2['id'], volume1['id']) # Verify "volume1" is attached to the server vol_attachments = self.servers_client.list_volume_attachments( server['id'])['volumeAttachments'] self.assertEqual(1, len(vol_attachments)) self.assertIn(volume1['id'], vol_attachments[0]['volumeId']) tempest-17.2.0/tempest/api/compute/admin/test_flavors_access_negative.py0000666000175100017510000001165413207044712026605 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ credentials = ['primary', 'admin', 'alt'] @classmethod def skip_checks(cls): super(FlavorsAccessNegativeTestJSON, cls).skip_checks() if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'): msg = "OS-FLV-EXT-DATA extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(FlavorsAccessNegativeTestJSON, cls).resource_setup() cls.tenant_id = cls.flavors_client.tenant_id cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @decorators.attr(type=['negative']) @decorators.idempotent_id('0621c53e-d45d-40e7-951d-43e5e257b272') def test_flavor_access_list_with_public_flavor(self): # Test to list flavor access with exceptions by querying public flavor flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='True') self.assertRaises(lib_exc.NotFound, self.admin_flavors_client.list_flavor_access, flavor['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('41eaaade-6d37-4f28-9c74-f21b46ca67bd') def test_flavor_non_admin_add(self): # Test to add flavor access as a user without admin privileges. flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='False') self.assertRaises(lib_exc.Forbidden, self.flavors_client.add_flavor_access, flavor['id'], self.tenant_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('073e79a6-c311-4525-82dc-6083d919cb3a') def test_flavor_non_admin_remove(self): # Test to remove flavor access as a user without admin privileges. flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='False') # Add flavor access to a tenant. self.admin_flavors_client.add_flavor_access(flavor['id'], self.tenant_id) self.addCleanup(self.admin_flavors_client.remove_flavor_access, flavor['id'], self.tenant_id) self.assertRaises(lib_exc.Forbidden, self.flavors_client.remove_flavor_access, flavor['id'], self.tenant_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('f3592cc0-0306-483c-b210-9a7b5346eddc') def test_add_flavor_access_duplicate(self): # Create a new flavor. flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='False') # Add flavor access to a tenant. self.admin_flavors_client.add_flavor_access(flavor['id'], self.tenant_id) self.addCleanup(self.admin_flavors_client.remove_flavor_access, flavor['id'], self.tenant_id) # An exception should be raised when adding flavor access to the same # tenant self.assertRaises(lib_exc.Conflict, self.admin_flavors_client.add_flavor_access, flavor['id'], self.tenant_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('1f710927-3bc7-4381-9f82-0ca6e42644b7') def test_remove_flavor_access_not_found(self): # Create a new flavor. flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public='False') # An exception should be raised when flavor access is not found self.assertRaises(lib_exc.NotFound, self.admin_flavors_client.remove_flavor_access, flavor['id'], self.os_alt.servers_client.tenant_id) tempest-17.2.0/tempest/api/compute/admin/test_security_groups.py0000666000175100017510000000707413207044712025175 0ustar zuulzuul00000000000000# Copyright 2013 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 tempest.api.compute import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest): @classmethod def setup_clients(cls): super(SecurityGroupsTestAdminJSON, cls).setup_clients() cls.adm_client = cls.os_admin.compute_security_groups_client cls.client = cls.security_groups_client def _delete_security_group(self, securitygroup_id, admin=True): if admin: self.adm_client.delete_security_group(securitygroup_id) else: self.client.delete_security_group(securitygroup_id) @decorators.idempotent_id('49667619-5af9-4c63-ab5d-2cfdd1c8f7f1') @utils.services('network') def test_list_security_groups_list_all_tenants_filter(self): # Admin can list security groups of all tenants # List of all security groups created security_group_list = [] # Create two security groups for a non-admin tenant for _ in range(2): name = data_utils.rand_name('securitygroup') description = data_utils.rand_name('description') securitygroup = self.client.create_security_group( name=name, description=description)['security_group'] self.addCleanup(self._delete_security_group, securitygroup['id'], admin=False) security_group_list.append(securitygroup) client_tenant_id = securitygroup['tenant_id'] # Create two security groups for admin tenant for _ in range(2): name = data_utils.rand_name('securitygroup') description = data_utils.rand_name('description') adm_securitygroup = self.adm_client.create_security_group( name=name, description=description)['security_group'] self.addCleanup(self._delete_security_group, adm_securitygroup['id']) security_group_list.append(adm_securitygroup) # Fetch all security groups based on 'all_tenants' search filter fetched_list = self.adm_client.list_security_groups( all_tenants='true')['security_groups'] sec_group_id_list = [sg['id'] for sg in fetched_list] # Now check if all created Security Groups are present in fetched list for sec_group in security_group_list: self.assertIn(sec_group['id'], sec_group_id_list) # Fetch all security groups for non-admin user with 'all_tenants' # search filter fetched_list = (self.client.list_security_groups(all_tenants='true') ['security_groups']) # Now check if all created Security Groups are present in fetched list for sec_group in fetched_list: self.assertEqual(sec_group['tenant_id'], client_tenant_id, "Failed to get all security groups for " "non admin user.") tempest-17.2.0/tempest/api/compute/admin/test_quotas.py0000666000175100017510000002074013207044712023236 0ustar zuulzuul00000000000000# 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_log import log as logging from testtools import matchers from tempest.api.compute import base from tempest.common import identity from tempest.common import tempest_fixtures as fixtures from tempest.lib.common.utils import data_utils from tempest.lib import decorators LOG = logging.getLogger(__name__) class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest): force_tenant_isolation = True def setUp(self): # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests. self.useFixture(fixtures.LockFixture('compute_quotas')) super(QuotasAdminTestJSON, self).setUp() @classmethod def setup_clients(cls): super(QuotasAdminTestJSON, cls).setup_clients() cls.adm_client = cls.os_admin.quotas_client @classmethod def resource_setup(cls): super(QuotasAdminTestJSON, cls).resource_setup() # NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that cls.demo_tenant_id = cls.quotas_client.tenant_id cls.default_quota_set = set(('injected_file_content_bytes', 'metadata_items', 'injected_files', 'ram', 'floating_ips', 'fixed_ips', 'key_pairs', 'injected_file_path_bytes', 'instances', 'security_group_rules', 'cores', 'security_groups')) @decorators.idempotent_id('3b0a7c8f-cf58-46b8-a60c-715a32a8ba7d') def test_get_default_quotas(self): # Admin can get the default resource quota set for a tenant expected_quota_set = self.default_quota_set | set(['id']) quota_set = self.adm_client.show_default_quota_set( self.demo_tenant_id)['quota_set'] self.assertEqual(quota_set['id'], self.demo_tenant_id) for quota in expected_quota_set: self.assertIn(quota, quota_set.keys()) @decorators.idempotent_id('55fbe2bf-21a9-435b-bbd2-4162b0ed799a') def test_update_all_quota_resources_for_tenant(self): # Admin can update all the resource quota limits for a tenant default_quota_set = self.adm_client.show_default_quota_set( self.demo_tenant_id)['quota_set'] new_quota_set = {'injected_file_content_bytes': 20480, 'metadata_items': 256, 'injected_files': 10, 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10, 'key_pairs': 200, 'injected_file_path_bytes': 512, 'instances': 20, 'security_group_rules': 20, 'cores': 2, 'security_groups': 20, 'server_groups': 20, 'server_group_members': 20} # Update limits for all quota resources quota_set = self.adm_client.update_quota_set( self.demo_tenant_id, force=True, **new_quota_set)['quota_set'] default_quota_set.pop('id') self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, **default_quota_set) for quota in new_quota_set: self.assertIn(quota, quota_set.keys()) # TODO(afazekas): merge these test cases @decorators.idempotent_id('ce9e0815-8091-4abd-8345-7fe5b85faa1d') def test_get_updated_quotas(self): # Verify that GET shows the updated quota set of project project_name = data_utils.rand_name('cpu_quota_project') project_desc = project_name + '-desc' project = identity.identity_utils(self.os_admin).create_project( name=project_name, description=project_desc) project_id = project['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_project, project_id) self.adm_client.update_quota_set(project_id, ram='5120') quota_set = self.adm_client.show_quota_set(project_id)['quota_set'] self.assertEqual(5120, quota_set['ram']) # Verify that GET shows the updated quota set of user user_name = data_utils.rand_name('cpu_quota_user') password = data_utils.rand_password() email = user_name + '@testmail.tm' user = identity.identity_utils(self.os_admin).create_user( username=user_name, password=password, project=project, email=email) user_id = user['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_user, user_id) self.adm_client.update_quota_set(project_id, user_id=user_id, ram='2048') quota_set = self.adm_client.show_quota_set( project_id, user_id=user_id)['quota_set'] self.assertEqual(2048, quota_set['ram']) @decorators.idempotent_id('389d04f0-3a41-405f-9317-e5f86e3c44f0') def test_delete_quota(self): # Admin can delete the resource quota set for a project project_name = data_utils.rand_name('ram_quota_project') project_desc = project_name + '-desc' project = identity.identity_utils(self.os_admin).create_project( name=project_name, description=project_desc) project_id = project['id'] self.addCleanup(identity.identity_utils(self.os_admin).delete_project, project_id) quota_set_default = (self.adm_client.show_quota_set(project_id) ['quota_set']) ram_default = quota_set_default['ram'] self.adm_client.update_quota_set(project_id, ram='5120') self.adm_client.delete_quota_set(project_id) quota_set_new = self.adm_client.show_quota_set(project_id)['quota_set'] self.assertEqual(ram_default, quota_set_new['ram']) class QuotaClassesAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests the os-quota-class-sets API to update default quotas.""" def setUp(self): # All test cases in this class need to externally lock on doing # anything with default quota values. self.useFixture(fixtures.LockFixture('compute_quotas')) super(QuotaClassesAdminTestJSON, self).setUp() @classmethod def resource_setup(cls): super(QuotaClassesAdminTestJSON, cls).resource_setup() cls.adm_client = cls.os_admin.quota_classes_client def _restore_default_quotas(self, original_defaults): LOG.debug("restoring quota class defaults") self.adm_client.update_quota_class_set('default', **original_defaults) # NOTE(sdague): this test is problematic as it changes # global state, and possibly needs to be part of a set of # tests that get run all by themselves at the end under a # 'danger' flag. @decorators.idempotent_id('7932ab0f-5136-4075-b201-c0e2338df51a') def test_update_default_quotas(self): LOG.debug("get the current 'default' quota class values") body = (self.adm_client.show_quota_class_set('default') ['quota_class_set']) self.assertEqual('default', body.pop('id')) # restore the defaults when the test is done self.addCleanup(self._restore_default_quotas, body.copy()) # increment all of the values for updating the default quota class for quota, default in body.items(): # NOTE(sdague): we need to increment a lot, otherwise # there is a real chance that we go from -1 (unlimited) # to a very small number which causes issues. body[quota] = default + 100 LOG.debug("update limits for the default quota class set") update_body = self.adm_client.update_quota_class_set( 'default', **body)['quota_class_set'] LOG.debug("assert that the response has all of the changed values") self.assertThat(update_body.items(), matchers.ContainsAll(body.items())) tempest-17.2.0/tempest/api/compute/admin/test_flavors_extra_specs.py0000666000175100017510000001175513207044712026004 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest): """Tests Flavor Extra Spec API extension. SET, UNSET, UPDATE Flavor Extra specs require admin privileges. GET Flavor Extra specs can be performed even by without admin privileges. """ @classmethod def skip_checks(cls): super(FlavorsExtraSpecsTestJSON, cls).skip_checks() if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'): msg = "OS-FLV-EXT-DATA extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(FlavorsExtraSpecsTestJSON, cls).resource_setup() flavor_name = data_utils.rand_name('test_flavor') ram = 512 vcpus = 1 disk = 10 ephemeral = 10 new_flavor_id = data_utils.rand_int_id(start=1000) swap = 1024 rxtx = 1 # Create a flavor so as to set/get/unset extra specs cls.flavor = cls.admin_flavors_client.create_flavor( name=flavor_name, ram=ram, vcpus=vcpus, disk=disk, id=new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx_factor=rxtx)['flavor'] cls.addClassResourceCleanup( cls.admin_flavors_client.wait_for_resource_deletion, cls.flavor['id']) cls.addClassResourceCleanup(cls.admin_flavors_client.delete_flavor, cls.flavor['id']) @decorators.idempotent_id('0b2f9d4b-1ca2-4b99-bb40-165d4bb94208') def test_flavor_set_get_update_show_unset_keys(self): # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra # spec as a user with admin privileges. # Assigning extra specs values that are to be set specs = {"key1": "value1", "key2": "value2"} # SET extra specs to the flavor created in setUp set_body = self.admin_flavors_client.set_flavor_extra_spec( self.flavor['id'], **specs)['extra_specs'] self.assertEqual(set_body, specs) # GET extra specs and verify get_body = (self.admin_flavors_client.list_flavor_extra_specs( self.flavor['id'])['extra_specs']) self.assertEqual(get_body, specs) # UPDATE the value of the extra specs key1 update_body = \ self.admin_flavors_client.update_flavor_extra_spec( self.flavor['id'], "key1", key1="value") self.assertEqual({"key1": "value"}, update_body) # GET extra specs and verify the value of the key2 # is the same as before get_body = self.admin_flavors_client.list_flavor_extra_specs( self.flavor['id'])['extra_specs'] self.assertEqual(get_body, {"key1": "value", "key2": "value2"}) # UNSET extra specs that were set in this test self.admin_flavors_client.unset_flavor_extra_spec(self.flavor['id'], "key1") self.admin_flavors_client.unset_flavor_extra_spec(self.flavor['id'], "key2") get_body = self.admin_flavors_client.list_flavor_extra_specs( self.flavor['id'])['extra_specs'] self.assertEmpty(get_body) @decorators.idempotent_id('a99dad88-ae1c-4fba-aeb4-32f898218bd0') def test_flavor_non_admin_get_all_keys(self): specs = {"key1": "value1", "key2": "value2"} self.admin_flavors_client.set_flavor_extra_spec(self.flavor['id'], **specs) body = (self.flavors_client.list_flavor_extra_specs( self.flavor['id'])['extra_specs']) for key in specs: self.assertEqual(body[key], specs[key]) @decorators.idempotent_id('12805a7f-39a3-4042-b989-701d5cad9c90') def test_flavor_non_admin_get_specific_key(self): body = self.admin_flavors_client.set_flavor_extra_spec( self.flavor['id'], key1="value1", key2="value2")['extra_specs'] self.assertEqual(body['key1'], 'value1') self.assertIn('key2', body) body = self.flavors_client.show_flavor_extra_spec( self.flavor['id'], 'key1') self.assertEqual(body['key1'], 'value1') self.assertNotIn('key2', body) tempest-17.2.0/tempest/api/compute/admin/test_quotas_negative.py0000666000175100017510000001373613207044712025127 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class QuotasAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): force_tenant_isolation = True @classmethod def setup_clients(cls): super(QuotasAdminNegativeTestJSON, cls).setup_clients() cls.client = cls.os_primary.quotas_client cls.adm_client = cls.os_admin.quotas_client cls.sg_client = cls.security_groups_client cls.sgr_client = cls.security_group_rules_client @classmethod def resource_setup(cls): super(QuotasAdminNegativeTestJSON, cls).resource_setup() # NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that cls.demo_tenant_id = cls.client.tenant_id def _update_quota(self, quota_item, quota_value): quota_set = (self.adm_client.show_quota_set(self.demo_tenant_id) ['quota_set']) default_quota_value = quota_set[quota_item] self.adm_client.update_quota_set(self.demo_tenant_id, force=True, **{quota_item: quota_value}) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, **{quota_item: default_quota_value}) @decorators.attr(type=['negative']) @decorators.idempotent_id('733abfe8-166e-47bb-8363-23dbd7ff3476') def test_update_quota_normal_user(self): self.assertRaises(lib_exc.Forbidden, self.client.update_quota_set, self.demo_tenant_id, ram=0) # TODO(afazekas): Add dedicated tenant to the skipped quota tests. # It can be moved into the setUpClass as well. @decorators.attr(type=['negative']) @decorators.idempotent_id('91058876-9947-4807-9f22-f6eb17140d9b') def test_create_server_when_cpu_quota_is_full(self): # Disallow server creation when tenant's vcpu quota is full self._update_quota('cores', 0) self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.create_test_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('6fdd7012-584d-4327-a61c-49122e0d5864') def test_create_server_when_memory_quota_is_full(self): # Disallow server creation when tenant's memory quota is full self._update_quota('ram', 0) self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.create_test_server) @decorators.attr(type=['negative']) @decorators.idempotent_id('7c6be468-0274-449a-81c3-ac1c32ee0161') def test_create_server_when_instances_quota_is_full(self): # Once instances quota limit is reached, disallow server creation self._update_quota('instances', 0) self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.create_test_server) @decorators.skip_because(bug="1186354", condition=CONF.service_available.neutron) @decorators.attr(type=['negative']) @decorators.idempotent_id('7c6c8f3b-2bf6-4918-b240-57b136a66aa0') @utils.services('network') def test_security_groups_exceed_limit(self): # Negative test: Creation Security Groups over limit should FAIL # Set the quota to number of used security groups sg_quota = self.limits_client.show_limits()['limits']['absolute'][ 'totalSecurityGroupsUsed'] self._update_quota('security_groups', sg_quota) # Check we cannot create anymore # A 403 Forbidden or 413 Overlimit (old behaviour) exception # will be raised when out of quota self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.sg_client.create_security_group, name="sg-overlimit", description="sg-desc") @decorators.skip_because(bug="1186354", condition=CONF.service_available.neutron) @decorators.attr(type=['negative']) @decorators.idempotent_id('6e9f436d-f1ed-4f8e-a493-7275dfaa4b4d') @utils.services('network') def test_security_groups_rules_exceed_limit(self): # Negative test: Creation of Security Group Rules should FAIL # when we reach limit maxSecurityGroupRules self._update_quota('security_group_rules', 0) s_name = data_utils.rand_name('securitygroup') s_description = data_utils.rand_name('description') securitygroup = self.sg_client.create_security_group( name=s_name, description=s_description)['security_group'] self.addCleanup(self.sg_client.delete_security_group, securitygroup['id']) secgroup_id = securitygroup['id'] ip_protocol = 'tcp' # Check we cannot create SG rule anymore # A 403 Forbidden or 413 Overlimit (old behaviour) exception # will be raised when out of quota self.assertRaises((lib_exc.OverLimit, lib_exc.Forbidden), self.sgr_client.create_security_group_rule, parent_group_id=secgroup_id, ip_protocol=ip_protocol, from_port=1025, to_port=1025) tempest-17.2.0/tempest/api/compute/admin/test_keypairs_v210.py0000666000175100017510000000653513207044712024327 0ustar zuulzuul00000000000000# 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.api.compute.keypairs import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class KeyPairsV210TestJSON(base.BaseKeypairTest): credentials = ['primary', 'admin'] min_microversion = '2.10' @classmethod def setup_clients(cls): super(KeyPairsV210TestJSON, cls).setup_clients() cls.client = cls.os_admin.keypairs_client cls.non_admin_client = cls.os_primary.keypairs_client def _create_and_check_keypairs(self, user_id): key_list = list() for _ in range(2): k_name = data_utils.rand_name('keypair') keypair = self.create_keypair(k_name, keypair_type='ssh', user_id=user_id) self.assertEqual(k_name, keypair['name'], "The created keypair name is not equal " "to the requested name!") self.assertEqual(user_id, keypair['user_id'], "The created keypair is not for requested user!") keypair.pop('private_key', None) keypair.pop('user_id') key_list.append(keypair) return key_list @decorators.idempotent_id('3c8484af-cfb3-48f6-b8ba-d5d58bbf3eac') def test_admin_manage_keypairs_for_other_users(self): user_id = self.non_admin_client.user_id key_list = self._create_and_check_keypairs(user_id) first_keyname = key_list[0]['name'] keypair_detail = self.client.show_keypair(first_keyname, user_id=user_id)['keypair'] self.assertEqual(first_keyname, keypair_detail['name']) self.assertEqual(user_id, keypair_detail['user_id'], "The fetched keypair is not for requested user!") # Create a admin keypair admin_keypair = self.create_keypair(keypair_type='ssh') admin_keypair.pop('private_key', None) admin_keypair.pop('user_id') # Admin fetch keypairs list of non admin user keypairs = self.client.list_keypairs(user_id=user_id)['keypairs'] fetched_list = [keypair['keypair'] for keypair in keypairs] # Check admin keypair is not present in non admin user keypairs list self.assertNotIn(admin_keypair, fetched_list, "The fetched user keypairs has admin keypair!") # Now check if all the created keypairs are in the fetched list missing_kps = [kp for kp in key_list if kp not in fetched_list] self.assertFalse(missing_kps, "Failed to find keypairs %s in fetched list" % ', '.join(m_key['name'] for m_key in missing_kps)) tempest-17.2.0/tempest/api/compute/admin/test_hypervisor_negative.py0000666000175100017510000001227413207044712026021 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Hypervisors API that require admin privileges""" @classmethod def setup_clients(cls): super(HypervisorAdminNegativeTestJSON, cls).setup_clients() cls.client = cls.os_admin.hypervisor_client cls.non_adm_client = cls.hypervisor_client def _list_hypervisors(self): # List of hypervisors hypers = self.client.list_hypervisors()['hypervisors'] return hypers @decorators.attr(type=['negative']) @decorators.idempotent_id('c136086a-0f67-4b2b-bc61-8482bd68989f') def test_show_nonexistent_hypervisor(self): nonexistent_hypervisor_id = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.client.show_hypervisor, nonexistent_hypervisor_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('51e663d0-6b89-4817-a465-20aca0667d03') def test_show_hypervisor_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertNotEmpty(hypers) self.assertRaises( lib_exc.Forbidden, self.non_adm_client.show_hypervisor, hypers[0]['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('2a0a3938-832e-4859-95bf-1c57c236b924') def test_show_servers_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertNotEmpty(hypers) self.assertRaises( lib_exc.Forbidden, self.non_adm_client.list_servers_on_hypervisor, hypers[0]['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('02463d69-0ace-4d33-a4a8-93d7883a2bba') def test_show_servers_with_nonexistent_hypervisor(self): nonexistent_hypervisor_id = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.client.list_servers_on_hypervisor, nonexistent_hypervisor_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('e2b061bb-13f9-40d8-9d6e-d5bf17595849') def test_get_hypervisor_stats_with_non_admin_user(self): self.assertRaises( lib_exc.Forbidden, self.non_adm_client.show_hypervisor_statistics) @decorators.attr(type=['negative']) @decorators.idempotent_id('f60aa680-9a3a-4c7d-90e1-fae3a4891303') def test_get_nonexistent_hypervisor_uptime(self): nonexistent_hypervisor_id = data_utils.rand_uuid() self.assertRaises( lib_exc.NotFound, self.client.show_hypervisor_uptime, nonexistent_hypervisor_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('6c3461f9-c04c-4e2a-bebb-71dc9cb47df2') def test_get_hypervisor_uptime_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertNotEmpty(hypers) self.assertRaises( lib_exc.Forbidden, self.non_adm_client.show_hypervisor_uptime, hypers[0]['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('51b3d536-9b14-409c-9bce-c6f7c794994e') def test_get_hypervisor_list_with_non_admin_user(self): # List of hypervisor and available services with non admin user self.assertRaises( lib_exc.Forbidden, self.non_adm_client.list_hypervisors) @decorators.attr(type=['negative']) @decorators.idempotent_id('dc02db05-e801-4c5f-bc8e-d915290ab345') def test_get_hypervisor_list_details_with_non_admin_user(self): # List of hypervisor details and available services with non admin user self.assertRaises( lib_exc.Forbidden, self.non_adm_client.list_hypervisors, detail=True) @decorators.attr(type=['negative']) @decorators.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa') def test_search_nonexistent_hypervisor(self): self.assertRaises( lib_exc.NotFound, self.client.search_hypervisor, 'nonexistent_hypervisor_name') @decorators.attr(type=['negative']) @decorators.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74') def test_search_hypervisor_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertNotEmpty(hypers) self.assertRaises( lib_exc.Forbidden, self.non_adm_client.search_hypervisor, hypers[0]['hypervisor_hostname']) tempest-17.2.0/tempest/api/compute/admin/test_aggregates_negative.py0000666000175100017510000001774313207044712025726 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class AggregatesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Aggregates API that require admin privileges""" @classmethod def setup_clients(cls): super(AggregatesAdminNegativeTestJSON, cls).setup_clients() cls.client = cls.os_admin.aggregates_client @classmethod def resource_setup(cls): super(AggregatesAdminNegativeTestJSON, cls).resource_setup() cls.aggregate_name_prefix = 'test_aggregate' hosts_all = cls.os_admin.hosts_client.list_hosts()['hosts'] hosts = ([host['host_name'] for host in hosts_all if host['service'] == 'compute']) cls.host = hosts[0] def _create_test_aggregate(self): aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) aggregate = (self.client.create_aggregate(name=aggregate_name) ['aggregate']) self.addCleanup(self.client.delete_aggregate, aggregate['id']) return aggregate @decorators.attr(type=['negative']) @decorators.idempotent_id('86a1cb14-da37-4a70-b056-903fd56dfe29') def test_aggregate_create_as_user(self): # Regular user is not allowed to create an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) self.assertRaises(lib_exc.Forbidden, self.aggregates_client.create_aggregate, name=aggregate_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('3b8a1929-3793-4e92-bcb4-dfa572ee6c1d') def test_aggregate_create_aggregate_name_length_less_than_1(self): # the length of aggregate name should >= 1 and <=255 self.assertRaises(lib_exc.BadRequest, self.client.create_aggregate, name='') @decorators.attr(type=['negative']) @decorators.idempotent_id('4c194563-543b-4e70-a719-557bbe947fac') def test_aggregate_create_aggregate_name_length_exceeds_255(self): # the length of aggregate name should >= 1 and <=255 aggregate_name = 'a' * 256 self.assertRaises(lib_exc.BadRequest, self.client.create_aggregate, name=aggregate_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('9c23a291-b0b1-487b-b464-132e061151b3') def test_aggregate_create_with_existent_aggregate_name(self): # creating an aggregate with existent aggregate name is forbidden aggregate = self._create_test_aggregate() self.assertRaises(lib_exc.Conflict, self.client.create_aggregate, name=aggregate['name']) @decorators.attr(type=['negative']) @decorators.idempotent_id('cd6de795-c15d-45f1-8d9e-813c6bb72a3d') def test_aggregate_delete_as_user(self): # Regular user is not allowed to delete an aggregate. aggregate = self._create_test_aggregate() self.assertRaises(lib_exc.Forbidden, self.aggregates_client.delete_aggregate, aggregate['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('b7d475a6-5dcd-4ff4-b70a-cd9de66a6672') def test_aggregate_list_as_user(self): # Regular user is not allowed to list aggregates. self.assertRaises(lib_exc.Forbidden, self.aggregates_client.list_aggregates) @decorators.attr(type=['negative']) @decorators.idempotent_id('557cad12-34c9-4ff4-95f0-22f0dfbaf7dc') def test_aggregate_get_details_as_user(self): # Regular user is not allowed to get aggregate details. aggregate = self._create_test_aggregate() self.assertRaises(lib_exc.Forbidden, self.aggregates_client.show_aggregate, aggregate['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('c74f4bf1-4708-4ff2-95a0-f49eaca951bd') def test_aggregate_delete_with_invalid_id(self): # Delete an aggregate with invalid id should raise exceptions. self.assertRaises(lib_exc.NotFound, self.client.delete_aggregate, -1) @decorators.attr(type=['negative']) @decorators.idempotent_id('3c916244-2c46-49a4-9b55-b20bb0ae512c') def test_aggregate_get_details_with_invalid_id(self): # Get aggregate details with invalid id should raise exceptions. self.assertRaises(lib_exc.NotFound, self.client.show_aggregate, -1) @decorators.attr(type=['negative']) @decorators.idempotent_id('0ef07828-12b4-45ba-87cc-41425faf5711') def test_aggregate_add_non_exist_host(self): # Adding a non-exist host to an aggregate should raise exceptions. hosts_all = self.os_admin.hosts_client.list_hosts()['hosts'] hosts = map(lambda x: x['host_name'], hosts_all) while True: non_exist_host = data_utils.rand_name('nonexist_host') if non_exist_host not in hosts: break aggregate = self._create_test_aggregate() self.assertRaises(lib_exc.NotFound, self.client.add_host, aggregate['id'], host=non_exist_host) @decorators.attr(type=['negative']) @decorators.idempotent_id('7324c334-bd13-4c93-8521-5877322c3d51') def test_aggregate_add_host_as_user(self): # Regular user is not allowed to add a host to an aggregate. aggregate = self._create_test_aggregate() self.assertRaises(lib_exc.Forbidden, self.aggregates_client.add_host, aggregate['id'], host=self.host) @decorators.attr(type=['negative']) @decorators.idempotent_id('19dd44e1-c435-4ee1-a402-88c4f90b5950') def test_aggregate_add_existent_host(self): self.useFixture(fixtures.LockFixture('availability_zone')) aggregate = self._create_test_aggregate() self.client.add_host(aggregate['id'], host=self.host) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.host) self.assertRaises(lib_exc.Conflict, self.client.add_host, aggregate['id'], host=self.host) @decorators.attr(type=['negative']) @decorators.idempotent_id('7a53af20-137a-4e44-a4ae-e19260e626d9') def test_aggregate_remove_host_as_user(self): # Regular user is not allowed to remove a host from an aggregate. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate = self._create_test_aggregate() self.client.add_host(aggregate['id'], host=self.host) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.host) self.assertRaises(lib_exc.Forbidden, self.aggregates_client.remove_host, aggregate['id'], host=self.host) @decorators.attr(type=['negative']) @decorators.idempotent_id('95d6a6fa-8da9-4426-84d0-eec0329f2e4d') def test_aggregate_remove_nonexistent_host(self): aggregate = self._create_test_aggregate() self.assertRaises(lib_exc.NotFound, self.client.remove_host, aggregate['id'], host='nonexist_host') tempest-17.2.0/tempest/api/compute/admin/test_services.py0000666000175100017510000000557613207044712023557 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Services API. List and Enable/Disable require admin privileges.""" @classmethod def setup_clients(cls): super(ServicesAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.services_client @decorators.idempotent_id('5be41ef4-53d1-41cc-8839-5c2a48a1b283') def test_list_services(self): services = self.client.list_services()['services'] self.assertNotEmpty(services) @decorators.idempotent_id('f345b1ec-bc6e-4c38-a527-3ca2bc00bef5') def test_get_service_by_service_binary_name(self): binary_name = 'nova-compute' services = self.client.list_services(binary=binary_name)['services'] self.assertNotEmpty(services) for service in services: self.assertEqual(binary_name, service['binary']) @decorators.idempotent_id('affb42d5-5b4b-43c8-8b0b-6dca054abcca') def test_get_service_by_host_name(self): services = self.client.list_services()['services'] host_name = services[0]['host'] services_on_host = [service for service in services if service['host'] == host_name] services = self.client.list_services(host=host_name)['services'] # we could have a periodic job checkin between the 2 service # lookups, so only compare binary lists. s1 = map(lambda x: x['binary'], services) s2 = map(lambda x: x['binary'], services_on_host) # sort the lists before comparing, to take out dependency # on order. self.assertEqual(sorted(s1), sorted(s2)) @decorators.idempotent_id('39397f6f-37b8-4234-8671-281e44c74025') def test_get_service_by_service_and_host_name(self): services = self.client.list_services()['services'] host_name = services[0]['host'] binary_name = services[0]['binary'] services = self.client.list_services(host=host_name, binary=binary_name)['services'] self.assertEqual(1, len(services)) self.assertEqual(host_name, services[0]['host']) self.assertEqual(binary_name, services[0]['binary']) tempest-17.2.0/tempest/api/compute/admin/test_servers.py0000666000175100017510000002161613207044712023416 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import waiters from tempest.lib.common.utils import data_utils from tempest.lib import decorators class ServersAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Servers API using admin privileges""" @classmethod def setup_clients(cls): super(ServersAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.servers_client cls.non_admin_client = cls.servers_client @classmethod def resource_setup(cls): super(ServersAdminTestJSON, cls).resource_setup() cls.s1_name = data_utils.rand_name(cls.__name__ + '-server') server = cls.create_test_server(name=cls.s1_name) cls.s1_id = server['id'] cls.s2_name = data_utils.rand_name(cls.__name__ + '-server') server = cls.create_test_server(name=cls.s2_name, wait_until='ACTIVE') cls.s2_id = server['id'] waiters.wait_for_server_status(cls.non_admin_client, cls.s1_id, 'ACTIVE') @decorators.idempotent_id('06f960bb-15bb-48dc-873d-f96e89be7870') def test_list_servers_filter_by_error_status(self): # Filter the list of servers by server error status params = {'status': 'error'} self.client.reset_state(self.s1_id, state='error') body = self.non_admin_client.list_servers(**params) # Reset server's state to 'active' self.client.reset_state(self.s1_id, state='active') # Verify server's state server = self.client.show_server(self.s1_id)['server'] self.assertEqual(server['status'], 'ACTIVE') servers = body['servers'] # Verify error server in list result self.assertIn(self.s1_id, map(lambda x: x['id'], servers)) self.assertNotIn(self.s2_id, map(lambda x: x['id'], servers)) @decorators.idempotent_id('d56e9540-73ed-45e0-9b88-98fc419087eb') def test_list_servers_detailed_filter_by_invalid_status(self): params = {'status': 'invalid_status'} body = self.client.list_servers(detail=True, **params) servers = body['servers'] self.assertEmpty(servers) @decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f') def test_list_servers_by_admin(self): # Listing servers by admin user returns a list which doesn't # contain the other tenants' server by default body = self.client.list_servers(detail=True) servers = body['servers'] # This case is for the test environments which contain # the existing servers before testing servers_name = [server['name'] for server in servers] self.assertNotIn(self.s1_name, servers_name) self.assertNotIn(self.s2_name, servers_name) @decorators.idempotent_id('9f5579ae-19b4-4985-a091-2a5d56106580') def test_list_servers_by_admin_with_all_tenants(self): # Listing servers by admin user with all tenants parameter # Here should be listed all servers params = {'all_tenants': ''} body = self.client.list_servers(detail=True, **params) servers = body['servers'] servers_name = [server['name'] for server in servers] self.assertIn(self.s1_name, servers_name) self.assertIn(self.s2_name, servers_name) @decorators.related_bug('1659811') @decorators.idempotent_id('7e5d6b8f-454a-4ba1-8ae2-da857af8338b') def test_list_servers_by_admin_with_specified_tenant(self): # In nova v2, tenant_id is ignored unless all_tenants is specified # List the primary tenant but get nothing due to odd specified behavior tenant_id = self.non_admin_client.tenant_id params = {'tenant_id': tenant_id} body = self.client.list_servers(detail=True, **params) servers = body['servers'] servers_name = [x['name'] for x in servers] self.assertNotIn(self.s1_name, servers_name) self.assertNotIn(self.s2_name, servers_name) # List the primary tenant with all_tenants is specified params = {'all_tenants': '', 'tenant_id': tenant_id} body = self.client.list_servers(detail=True, **params) servers = body['servers'] servers_name = [x['name'] for x in servers] self.assertIn(self.s1_name, servers_name) self.assertIn(self.s2_name, servers_name) # List the admin tenant shouldn't get servers created by other tenants admin_tenant_id = self.client.tenant_id params = {'all_tenants': '', 'tenant_id': admin_tenant_id} body = self.client.list_servers(detail=True, **params) servers = body['servers'] servers_name = [x['name'] for x in servers] self.assertNotIn(self.s1_name, servers_name) self.assertNotIn(self.s2_name, servers_name) @decorators.idempotent_id('86c7a8f7-50cf-43a9-9bac-5b985317134f') def test_list_servers_filter_by_exist_host(self): # Filter the list of servers by existent host server = self.client.show_server(self.s1_id)['server'] hostname = server['OS-EXT-SRV-ATTR:host'] params = {'host': hostname, 'all_tenants': '1'} servers = self.client.list_servers(**params)['servers'] self.assertIn(server['id'], map(lambda x: x['id'], servers)) nonexistent_params = {'host': 'nonexistent_host', 'all_tenants': '1'} nonexistent_body = self.client.list_servers(**nonexistent_params) nonexistent_servers = nonexistent_body['servers'] self.assertNotIn(server['id'], map(lambda x: x['id'], nonexistent_servers)) @decorators.idempotent_id('ee8ae470-db70-474d-b752-690b7892cab1') def test_reset_state_server(self): # Reset server's state to 'error' self.client.reset_state(self.s1_id, state='error') # Verify server's state server = self.client.show_server(self.s1_id)['server'] self.assertEqual(server['status'], 'ERROR') # Reset server's state to 'active' self.client.reset_state(self.s1_id, state='active') # Verify server's state server = self.client.show_server(self.s1_id)['server'] self.assertEqual(server['status'], 'ACTIVE') @decorators.idempotent_id('682cb127-e5bb-4f53-87ce-cb9003604442') def test_rebuild_server_in_error_state(self): # The server in error state should be rebuilt using the provided # image and changed to ACTIVE state # resetting vm state require admin privilege self.client.reset_state(self.s1_id, state='error') rebuilt_server = self.non_admin_client.rebuild_server( self.s1_id, self.image_ref_alt)['server'] self.addCleanup(waiters.wait_for_server_status, self.non_admin_client, self.s1_id, 'ACTIVE') self.addCleanup(self.non_admin_client.rebuild_server, self.s1_id, self.image_ref) # Verify the properties in the initial response are correct self.assertEqual(self.s1_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertEqual(self.image_ref_alt, rebuilt_image_id) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) waiters.wait_for_server_status(self.non_admin_client, rebuilt_server['id'], 'ACTIVE', raise_on_error=False) # Verify the server properties after rebuilding server = (self.non_admin_client.show_server(rebuilt_server['id']) ['server']) rebuilt_image_id = server['image']['id'] self.assertEqual(self.image_ref_alt, rebuilt_image_id) @decorators.idempotent_id('7a1323b4-a6a2-497a-96cb-76c07b945c71') def test_reset_network_inject_network_info(self): # Reset Network of a Server server = self.create_test_server(wait_until='ACTIVE') self.client.reset_network(server['id']) # Inject the Network Info into Server self.client.inject_network_info(server['id']) @decorators.idempotent_id('fdcd9b33-0903-4e00-a1f7-b5f6543068d6') def test_create_server_with_scheduling_hint(self): # Create a server with scheduler hints. hints = { 'same_host': self.s1_id } self.create_test_server(scheduler_hints=hints, wait_until='ACTIVE') tempest-17.2.0/tempest/api/compute/admin/test_server_diagnostics.py0000666000175100017510000000647413207044712025627 0ustar zuulzuul00000000000000# Copyright 2017 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 time from tempest.api.compute import base from tempest.lib import decorators class ServerDiagnosticsTest(base.BaseV2ComputeAdminTest): min_microversion = None max_microversion = '2.47' @classmethod def setup_clients(cls): super(ServerDiagnosticsTest, cls).setup_clients() cls.client = cls.os_admin.servers_client @decorators.idempotent_id('31ff3486-b8a0-4f56-a6c0-aab460531db3') def test_get_server_diagnostics(self): server_id = self.create_test_server(wait_until='ACTIVE')['id'] diagnostics = self.client.show_server_diagnostics(server_id) # NOTE(snikitin): Before microversion 2.48 response data from each # hypervisor (libvirt, xen, vmware) was different. None of the fields # were equal. As this test is common for libvirt, xen and vmware CI # jobs we can't check any field in the response because all fields are # different. self.assertNotEmpty(diagnostics) class ServerDiagnosticsV248Test(base.BaseV2ComputeAdminTest): min_microversion = '2.48' max_microversion = 'latest' @classmethod def setup_clients(cls): super(ServerDiagnosticsV248Test, cls).setup_clients() cls.client = cls.os_admin.servers_client @decorators.idempotent_id('64d0d48c-dff1-11e6-bf01-fe55135034f3') def test_get_server_diagnostics(self): server_id = self.create_test_server(wait_until='ACTIVE')['id'] # Response status and filed types will be checked by json schema self.client.show_server_diagnostics(server_id) # NOTE(snikitin): This is a special case for Xen hypervisor. In Xen # case we're getting diagnostics stats from the RRDs which are updated # every 5 seconds. It means that diagnostics information may be # incomplete during first 5 seconds of VM life. In such cases methods # which get diagnostics stats from Xen may raise exceptions or # return `NaN` values. Such behavior must be handled correctly. # Response must contain all diagnostics fields (may be with `None` # values) and response status must be 200. Line above checks it by # json schema. time.sleep(10) diagnostics = self.client.show_server_diagnostics(server_id) # NOTE(snikitin): After 10 seconds diagnostics fields must contain # not None values. But we will check only "memory_details.maximum" # field because only this field meets all the following conditions: # 1) This field may be unset because of Xen 5 seconds timeout. # 2) This field is present in responses from all three supported # hypervisors (libvirt, xen, vmware). self.assertIsNotNone(diagnostics['memory_details']['maximum']) tempest-17.2.0/tempest/api/compute/admin/test_simple_tenant_usage.py0000666000175100017510000000703313207044712025750 0ustar zuulzuul00000000000000# 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. import datetime from tempest.api.compute import base from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions as e # Time that waits for until returning valid response # TODO(takmatsu): Ideally this value would come from configuration. VALID_WAIT = 30 class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setup_clients(cls): super(TenantUsagesTestJSON, cls).setup_clients() cls.adm_client = cls.os_admin.tenant_usages_client cls.client = cls.os_primary.tenant_usages_client @classmethod def resource_setup(cls): super(TenantUsagesTestJSON, cls).resource_setup() cls.tenant_id = cls.client.tenant_id # Create a server in the demo tenant cls.create_test_server(wait_until='ACTIVE') now = datetime.datetime.now() cls.start = cls._parse_strtime(now - datetime.timedelta(days=1)) cls.end = cls._parse_strtime(now + datetime.timedelta(days=1)) @classmethod def _parse_strtime(cls, at): # Returns formatted datetime return at.strftime('%Y-%m-%dT%H:%M:%S.%f') def call_until_valid(self, func, duration, *args, **kwargs): # Call until get valid response for "duration" # because tenant usage doesn't become available immediately # after create VM. def is_valid(): try: self.resp = func(*args, **kwargs) return True except e.InvalidHTTPResponseBody: return False self.assertEqual(test_utils.call_until_true(is_valid, duration, 1), True, "%s not return valid response in %s secs" % ( func.__name__, duration)) return self.resp @decorators.idempotent_id('062c8ae9-9912-4249-8b51-e38d664e926e') def test_list_usage_all_tenants(self): # Get usage for all tenants tenant_usage = self.call_until_valid( self.adm_client.list_tenant_usages, VALID_WAIT, start=self.start, end=self.end, detailed="1")['tenant_usages'][0] self.assertEqual(len(tenant_usage), 8) @decorators.idempotent_id('94135049-a4c5-4934-ad39-08fa7da4f22e') def test_get_usage_tenant(self): # Get usage for a specific tenant tenant_usage = self.call_until_valid( self.adm_client.show_tenant_usage, VALID_WAIT, self.tenant_id, start=self.start, end=self.end)['tenant_usage'] self.assertEqual(len(tenant_usage), 8) @decorators.idempotent_id('9d00a412-b40e-4fd9-8eba-97b496316116') def test_get_usage_tenant_with_non_admin_user(self): # Get usage for a specific tenant with non admin user tenant_usage = self.call_until_valid( self.client.show_tenant_usage, VALID_WAIT, self.tenant_id, start=self.start, end=self.end)['tenant_usage'] self.assertEqual(len(tenant_usage), 8) tempest-17.2.0/tempest/api/compute/admin/test_servers_negative.py0000666000175100017510000001314113207044712025272 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Servers API using admin privileges""" @classmethod def setup_clients(cls): super(ServersAdminNegativeTestJSON, cls).setup_clients() cls.client = cls.os_admin.servers_client cls.quotas_client = cls.os_admin.quotas_client @classmethod def resource_setup(cls): super(ServersAdminNegativeTestJSON, cls).resource_setup() cls.tenant_id = cls.client.tenant_id server = cls.create_test_server(wait_until='ACTIVE') cls.s1_id = server['id'] @decorators.idempotent_id('28dcec23-f807-49da-822c-56a92ea3c687') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @decorators.attr(type=['negative']) def test_resize_server_using_overlimit_ram(self): # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests. self.useFixture(fixtures.LockFixture('compute_quotas')) quota_set = self.quotas_client.show_quota_set( self.tenant_id)['quota_set'] ram = quota_set['ram'] if ram == -1: raise self.skipException("ram quota set is -1," " cannot test overlimit") ram += 1 vcpus = 1 disk = 5 flavor_ref = self.create_flavor(ram=ram, vcpus=vcpus, disk=disk) self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.client.resize_server, self.s1_id, flavor_ref['id']) @decorators.idempotent_id('7368a427-2f26-4ad9-9ba9-911a0ec2b0db') @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @decorators.attr(type=['negative']) def test_resize_server_using_overlimit_vcpus(self): # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests. self.useFixture(fixtures.LockFixture('compute_quotas')) quota_set = self.quotas_client.show_quota_set( self.tenant_id)['quota_set'] vcpus = quota_set['cores'] if vcpus == -1: raise self.skipException("cores quota set is -1," " cannot test overlimit") vcpus += 1 ram = 512 disk = 5 flavor_ref = self.create_flavor(ram=ram, vcpus=vcpus, disk=disk) self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.client.resize_server, self.s1_id, flavor_ref['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('b0b4d8af-1256-41ef-9ee7-25f1c19dde80') def test_reset_state_server_invalid_state(self): self.assertRaises(lib_exc.BadRequest, self.client.reset_state, self.s1_id, state='invalid') @decorators.attr(type=['negative']) @decorators.idempotent_id('4cdcc984-fab0-4577-9a9d-6d558527ee9d') def test_reset_state_server_invalid_type(self): self.assertRaises(lib_exc.BadRequest, self.client.reset_state, self.s1_id, state=1) @decorators.attr(type=['negative']) @decorators.idempotent_id('e741298b-8df2-46f0-81cb-8f814ff2504c') def test_reset_state_server_nonexistent_server(self): self.assertRaises(lib_exc.NotFound, self.client.reset_state, '999', state='error') @decorators.attr(type=['negative']) @decorators.idempotent_id('46a4e1ca-87ae-4d28-987a-1b6b136a0221') def test_migrate_non_existent_server(self): # migrate a non existent server self.assertRaises(lib_exc.NotFound, self.client.migrate_server, data_utils.rand_uuid()) @decorators.idempotent_id('b0b17f83-d14e-4fc4-8f31-bcc9f3cfa629') @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, 'Cold migration not available.') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @decorators.attr(type=['negative']) def test_migrate_server_invalid_state(self): # create server. server = self.create_test_server(wait_until='ACTIVE') server_id = server['id'] # suspend the server. self.client.suspend_server(server_id) waiters.wait_for_server_status(self.client, server_id, 'SUSPENDED') # migrate a suspended server should fail self.assertRaises(lib_exc.Conflict, self.client.migrate_server, server_id) tempest-17.2.0/tempest/api/compute/admin/test_live_migration_negative.py0000666000175100017510000000515213207044712026614 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class LiveMigrationNegativeTest(base.BaseV2ComputeAdminTest): @classmethod def skip_checks(cls): super(LiveMigrationNegativeTest, cls).skip_checks() if not CONF.compute_feature_enabled.live_migration: raise cls.skipException("Live migration is not enabled") def _migrate_server_to(self, server_id, dest_host): bmflm = CONF.compute_feature_enabled.block_migration_for_live_migration self.admin_servers_client.live_migrate_server( server_id, host=dest_host, block_migration=bmflm, disk_over_commit=False) @decorators.attr(type=['negative']) @decorators.idempotent_id('7fb7856e-ae92-44c9-861a-af62d7830bcb') def test_invalid_host_for_migration(self): # Migrating to an invalid host should not change the status target_host = data_utils.rand_name('host') server = self.create_test_server(wait_until="ACTIVE") self.assertRaises(lib_exc.BadRequest, self._migrate_server_to, server['id'], target_host) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') @decorators.attr(type=['negative']) @decorators.idempotent_id('6e2f94f5-2ee8-4830-bef5-5bc95bb0795b') def test_live_block_migration_suspended(self): server = self.create_test_server(wait_until="ACTIVE") self.admin_servers_client.suspend_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SUSPENDED') destination_host = self.get_host_other_than(server['id']) self.assertRaises(lib_exc.Conflict, self._migrate_server_to, server['id'], destination_host) tempest-17.2.0/tempest/api/compute/admin/test_flavors.py0000666000175100017510000002351313207044712023377 0ustar zuulzuul00000000000000# 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 uuid from tempest.api.compute import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class FlavorsAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Flavors API Create and Delete that require admin privileges""" @classmethod def skip_checks(cls): super(FlavorsAdminTestJSON, cls).skip_checks() if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'): msg = "OS-FLV-EXT-DATA extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(FlavorsAdminTestJSON, cls).resource_setup() cls.flavor_name_prefix = 'test_flavor_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 cls.ephemeral = 10 cls.swap = 1024 cls.rxtx = 2 @decorators.idempotent_id('8b4330e1-12c4-4554-9390-e6639971f086') def test_create_flavor_with_int_id(self): flavor_id = data_utils.rand_int_id(start=1000) new_flavor_id = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, id=flavor_id)['id'] self.assertEqual(new_flavor_id, str(flavor_id)) @decorators.idempotent_id('94c9bb4e-2c2a-4f3c-bb1f-5f0daf918e6d') def test_create_flavor_with_uuid_id(self): flavor_id = data_utils.rand_uuid() new_flavor_id = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, id=flavor_id)['id'] self.assertEqual(new_flavor_id, flavor_id) @decorators.idempotent_id('f83fe669-6758-448a-a85e-32d351f36fe0') def test_create_flavor_with_none_id(self): # If nova receives a request with None as flavor_id, # nova generates flavor_id of uuid. flavor_id = None new_flavor_id = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, id=flavor_id)['id'] self.assertEqual(new_flavor_id, str(uuid.UUID(new_flavor_id))) @decorators.idempotent_id('8261d7b0-be58-43ec-a2e5-300573c3f6c5') def test_create_flavor_verify_entry_in_list_details(self): # Create a flavor and ensure it's details are listed # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) # Create the flavor self.create_flavor(name=flavor_name, ram=self.ram, vcpus=self.vcpus, disk=self.disk, ephemeral=self.ephemeral, swap=self.swap, rxtx_factor=self.rxtx) # Check if flavor is present in list flavors_list = self.admin_flavors_client.list_flavors( detail=True)['flavors'] self.assertIn(flavor_name, [f['name'] for f in flavors_list]) @decorators.idempotent_id('63dc64e6-2e79-4fdf-868f-85500d308d66') def test_create_list_flavor_without_extra_data(self): # Create a flavor and ensure it is listed # This operation requires the user to have 'admin' role def verify_flavor_response_extension(flavor): # check some extensions for the flavor create/show/detail response self.assertEqual(flavor['swap'], '') self.assertEqual(int(flavor['rxtx_factor']), 1) self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'], 0) self.assertEqual(flavor['os-flavor-access:is_public'], True) flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor flavor = self.create_flavor(name=flavor_name, ram=self.ram, vcpus=self.vcpus, disk=self.disk, id=new_flavor_id) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(int(flavor['id']), new_flavor_id) verify_flavor_response_extension(flavor) # Verify flavor is retrieved flavor = self.admin_flavors_client.show_flavor(new_flavor_id)['flavor'] self.assertEqual(flavor['name'], flavor_name) verify_flavor_response_extension(flavor) # Check if flavor is present in list flavors_list = [ f for f in self.flavors_client.list_flavors(detail=True)['flavors'] if f['name'] == flavor_name ] self.assertNotEmpty(flavors_list) verify_flavor_response_extension(flavors_list[0]) @decorators.idempotent_id('be6cc18c-7c5d-48c0-ac16-17eaf03c54eb') def test_list_non_public_flavor(self): # Create a flavor with os-flavor-access:is_public false. # The flavor should not be present in list_details as the # tenant is not automatically added access list. # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) # Create the flavor self.create_flavor(name=flavor_name, ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public="False") # Verify flavor is not retrieved flavors_list = self.admin_flavors_client.list_flavors( detail=True)['flavors'] self.assertNotIn(flavor_name, [f['name'] for f in flavors_list]) # Verify flavor is not retrieved with other user flavors_list = self.flavors_client.list_flavors(detail=True)['flavors'] self.assertNotIn(flavor_name, [f['name'] for f in flavors_list]) @decorators.idempotent_id('bcc418ef-799b-47cc-baa1-ce01368b8987') def test_create_server_with_non_public_flavor(self): # Create a flavor with os-flavor-access:is_public false flavor = self.create_flavor(ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public="False") # Verify flavor is not used by other user self.assertRaises(lib_exc.BadRequest, self.os_primary.servers_client.create_server, name='test', imageRef=self.image_ref, flavorRef=flavor['id']) @decorators.idempotent_id('b345b196-bfbd-4231-8ac1-6d7fe15ff3a3') def test_list_public_flavor_with_other_user(self): # Create a Flavor with public access. # Try to List/Get flavor with another user flavor_name = data_utils.rand_name(self.flavor_name_prefix) # Create the flavor self.create_flavor(name=flavor_name, ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public="True") # Verify flavor is retrieved with new user flavors_list = self.flavors_client.list_flavors(detail=True)['flavors'] self.assertIn(flavor_name, [f['name'] for f in flavors_list]) @decorators.idempotent_id('fb9cbde6-3a0e-41f2-a983-bdb0a823c44e') def test_is_public_string_variations(self): flavor_name_not_public = data_utils.rand_name(self.flavor_name_prefix) flavor_name_public = data_utils.rand_name(self.flavor_name_prefix) # Create a non public flavor self.create_flavor(name=flavor_name_not_public, ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public="False") # Create a public flavor self.create_flavor(name=flavor_name_public, ram=self.ram, vcpus=self.vcpus, disk=self.disk, is_public="True") def _test_string_variations(variations, flavor_name): for string in variations: params = {'is_public': string} flavors = (self.admin_flavors_client.list_flavors(detail=True, **params) ['flavors']) self.assertIn(flavor_name, [f['name'] for f in flavors]) _test_string_variations(['f', 'false', 'no', '0'], flavor_name_not_public) _test_string_variations(['t', 'true', 'yes', '1'], flavor_name_public) @decorators.idempotent_id('3b541a2e-2ac2-4b42-8b8d-ba6e22fcd4da') def test_create_flavor_using_string_ram(self): new_flavor_id = data_utils.rand_int_id(start=1000) ram = "1024" flavor = self.create_flavor(ram=ram, vcpus=self.vcpus, disk=self.disk, id=new_flavor_id) self.assertEqual(flavor['ram'], int(ram)) self.assertEqual(int(flavor['id']), new_flavor_id) tempest-17.2.0/tempest/api/compute/admin/test_agents.py0000666000175100017510000001171513207044712023205 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class AgentsAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Agents API""" @classmethod def setup_clients(cls): super(AgentsAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.agents_client @classmethod def resource_setup(cls): super(AgentsAdminTestJSON, cls).resource_setup() cls.params_agent = cls._param_helper( hypervisor='common', os='linux', architecture='x86_64', version='7.0', url='xxx://xxxx/xxx/xxx', md5hash='add6bb58e139be103324d04d82d8f545') @staticmethod def _param_helper(**kwargs): rand_key = 'architecture' if rand_key in kwargs: # NOTE: The rand_name is for avoiding agent conflicts. # If you try to create an agent with the same hypervisor, # os and architecture as an existing agent, Nova will return # an HTTPConflict or HTTPServerError. kwargs[rand_key] = data_utils.rand_name(kwargs[rand_key]) return kwargs @decorators.idempotent_id('1fc6bdc8-0b6d-4cc7-9f30-9b04fabe5b90') def test_create_agent(self): # Create an agent. params = self._param_helper( hypervisor='kvm', os='win', architecture='x86', version='7.0', url='xxx://xxxx/xxx/xxx', md5hash='add6bb58e139be103324d04d82d8f545') body = self.client.create_agent(**params)['agent'] self.addCleanup(self.client.delete_agent, body['agent_id']) for expected_item, value in params.items(): self.assertEqual(value, body[expected_item]) @decorators.idempotent_id('dc9ffd51-1c50-4f0e-a820-ae6d2a568a9e') def test_update_agent(self): # Create and update an agent. body = self.client.create_agent(**self.params_agent)['agent'] self.addCleanup(self.client.delete_agent, body['agent_id']) agent_id = body['agent_id'] params = self._param_helper( version='8.0', url='xxx://xxxx/xxx/xxx2', md5hash='add6bb58e139be103324d04d82d8f547') body = self.client.update_agent(agent_id, **params)['agent'] for expected_item, value in params.items(): self.assertEqual(value, body[expected_item]) @decorators.idempotent_id('470e0b89-386f-407b-91fd-819737d0b335') def test_delete_agent(self): # Create an agent and delete it. body = self.client.create_agent(**self.params_agent)['agent'] self.client.delete_agent(body['agent_id']) # Verify the list doesn't contain the deleted agent. agents = self.client.list_agents()['agents'] self.assertNotIn(body['agent_id'], map(lambda x: x['agent_id'], agents)) @decorators.idempotent_id('6a326c69-654b-438a-80a3-34bcc454e138') def test_list_agents(self): # Create an agent and list all agents. body = self.client.create_agent(**self.params_agent)['agent'] self.addCleanup(self.client.delete_agent, body['agent_id']) agents = self.client.list_agents()['agents'] self.assertNotEmpty(agents, 'Cannot get any agents.') self.assertIn(body['agent_id'], map(lambda x: x['agent_id'], agents)) @decorators.idempotent_id('eabadde4-3cd7-4ec4-a4b5-5a936d2d4408') def test_list_agents_with_filter(self): # Create agents and list the agent builds by the filter. body = self.client.create_agent(**self.params_agent)['agent'] self.addCleanup(self.client.delete_agent, body['agent_id']) params = self._param_helper( hypervisor='xen', os='linux', architecture='x86', version='7.0', url='xxx://xxxx/xxx/xxx1', md5hash='add6bb58e139be103324d04d82d8f546') agent_xen = self.client.create_agent(**params)['agent'] self.addCleanup(self.client.delete_agent, agent_xen['agent_id']) agent_id_xen = agent_xen['agent_id'] agents = (self.client.list_agents(hypervisor=agent_xen['hypervisor']) ['agents']) self.assertNotEmpty(agents, 'Cannot get any agents.') self.assertIn(agent_id_xen, map(lambda x: x['agent_id'], agents)) self.assertNotIn(body['agent_id'], map(lambda x: x['agent_id'], agents)) tempest-17.2.0/tempest/api/compute/admin/test_availability_zone.py0000666000175100017510000000305613207044712025430 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.lib import decorators class AZAdminV2TestJSON(base.BaseV2ComputeAdminTest): """Tests Availability Zone API List""" @classmethod def setup_clients(cls): super(AZAdminV2TestJSON, cls).setup_clients() cls.client = cls.availability_zone_admin_client @decorators.idempotent_id('d3431479-8a09-4f76-aa2d-26dc580cb27c') def test_get_availability_zone_list(self): # List of availability zone availability_zone = self.client.list_availability_zones() self.assertNotEmpty(availability_zone['availabilityZoneInfo']) @decorators.idempotent_id('ef726c58-530f-44c2-968c-c7bed22d5b8c') def test_get_availability_zone_list_detail(self): # List of availability zones and available services availability_zone = self.client.list_availability_zones(detail=True) self.assertNotEmpty(availability_zone['availabilityZoneInfo']) tempest-17.2.0/tempest/api/compute/admin/__init__.py0000666000175100017510000000000013207044712022405 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/admin/test_instance_usage_audit_log.py0000666000175100017510000000460213207044712026740 0ustar zuulzuul00000000000000# 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. import datetime from six.moves.urllib import parse as urllib from tempest.api.compute import base from tempest.lib import decorators class InstanceUsageAuditLogTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setup_clients(cls): super(InstanceUsageAuditLogTestJSON, cls).setup_clients() cls.adm_client = cls.os_admin.instance_usages_audit_log_client @decorators.idempotent_id('25319919-33d9-424f-9f99-2c203ee48b9d') def test_list_instance_usage_audit_logs(self): # list instance usage audit logs body = (self.adm_client.list_instance_usage_audit_logs() ["instance_usage_audit_logs"]) expected_items = ['total_errors', 'total_instances', 'log', 'num_hosts_running', 'num_hosts_done', 'num_hosts', 'hosts_not_run', 'overall_status', 'period_ending', 'period_beginning', 'num_hosts_not_run'] for item in expected_items: self.assertIn(item, body) @decorators.idempotent_id('6e40459d-7c5f-400b-9e83-449fbc8e7feb') def test_get_instance_usage_audit_log(self): # Get instance usage audit log before specified time now = datetime.datetime.now() body = (self.adm_client.show_instance_usage_audit_log( urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S"))) ["instance_usage_audit_log"]) expected_items = ['total_errors', 'total_instances', 'log', 'num_hosts_running', 'num_hosts_done', 'num_hosts', 'hosts_not_run', 'overall_status', 'period_ending', 'period_beginning', 'num_hosts_not_run'] for item in expected_items: self.assertIn(item, body) tempest-17.2.0/tempest/api/compute/admin/test_floating_ips_bulk.py0000666000175100017510000000700113207044712025410 0ustar zuulzuul00000000000000# Copyright 2014 NEC Technologies India Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from tempest.api.compute import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import test_utils from tempest.lib import decorators from tempest.lib import exceptions CONF = config.CONF class FloatingIPsBulkAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Floating IPs Bulk APIs that require admin privileges. API documentation - http://docs.openstack.org/api/openstack-compute/2/ content/ext-os-floating-ips-bulk.html """ @classmethod def setup_clients(cls): super(FloatingIPsBulkAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.floating_ips_bulk_client @classmethod def resource_setup(cls): super(FloatingIPsBulkAdminTestJSON, cls).resource_setup() cls.ip_range = CONF.validation.floating_ip_range cls.verify_unallocated_floating_ip_range(cls.ip_range) @classmethod def verify_unallocated_floating_ip_range(cls, ip_range): # Verify whether configure floating IP range is not already allocated. body = cls.client.list_floating_ips_bulk()['floating_ip_info'] allocated_ips_list = map(lambda x: x['address'], body) for ip_addr in netaddr.IPNetwork(ip_range).iter_hosts(): if str(ip_addr) in allocated_ips_list: msg = ("Configured unallocated floating IP range is already " "allocated. Configure the correct unallocated range " "as 'floating_ip_range'") raise exceptions.InvalidConfiguration(msg) return @decorators.idempotent_id('2c8f145f-8012-4cb8-ac7e-95a587f0e4ab') @utils.services('network') def test_create_list_delete_floating_ips_bulk(self): # Create, List and delete the Floating IPs Bulk pool = 'test_pool' # NOTE(GMann): Reserving the IP range but those are not attached # anywhere. Using the below mentioned interface which is not ever # expected to be used. Clean Up has been done for created IP range interface = 'eth0' body = (self.client.create_floating_ips_bulk(self.ip_range, pool, interface) ['floating_ips_bulk_create']) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_floating_ips_bulk, self.ip_range) self.assertEqual(self.ip_range, body['ip_range']) ips_list = self.client.list_floating_ips_bulk()['floating_ip_info'] self.assertNotEmpty(ips_list) for ip in netaddr.IPNetwork(self.ip_range).iter_hosts(): self.assertIn(str(ip), map(lambda x: x['address'], ips_list)) body = (self.client.delete_floating_ips_bulk(self.ip_range) ['floating_ips_bulk_delete']) self.assertEqual(self.ip_range, body) tempest-17.2.0/tempest/api/compute/admin/test_availability_zone_negative.py0000666000175100017510000000262513207044712027313 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class AZAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Availability Zone API List""" @classmethod def setup_clients(cls): super(AZAdminNegativeTestJSON, cls).setup_clients() cls.non_adm_client = cls.availability_zone_client @decorators.attr(type=['negative']) @decorators.idempotent_id('bf34dca2-fdc3-4073-9c02-7648d9eae0d7') def test_get_availability_zone_list_detail_with_non_admin_user(self): # List of availability zones and available services with # non-administrator user self.assertRaises( lib_exc.Forbidden, self.non_adm_client.list_availability_zones, detail=True) tempest-17.2.0/tempest/api/compute/admin/test_delete_server.py0000666000175100017510000000434013207044712024550 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest): # NOTE: Server creations of each test class should be under 10 # for preventing "Quota exceeded for instances". @classmethod def setup_clients(cls): super(DeleteServersAdminTestJSON, cls).setup_clients() cls.non_admin_client = cls.servers_client cls.admin_client = cls.os_admin.servers_client @decorators.idempotent_id('99774678-e072-49d1-9d2a-49a59bc56063') def test_delete_server_while_in_error_state(self): # Delete a server while it's VM state is error server = self.create_test_server(wait_until='ACTIVE') self.admin_client.reset_state(server['id'], state='error') # Verify server's state server = self.non_admin_client.show_server(server['id'])['server'] self.assertEqual(server['status'], 'ERROR') self.non_admin_client.delete_server(server['id']) waiters.wait_for_server_termination(self.servers_client, server['id'], ignore_error=True) @decorators.idempotent_id('73177903-6737-4f27-a60c-379e8ae8cf48') def test_admin_delete_servers_of_others(self): # Administrator can delete servers of others server = self.create_test_server(wait_until='ACTIVE') self.admin_client.delete_server(server['id']) waiters.wait_for_server_termination(self.servers_client, server['id']) tempest-17.2.0/tempest/api/compute/admin/test_live_migration.py0000666000175100017510000002553113207044712024735 0ustar zuulzuul00000000000000# 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 time from oslo_log import log as logging import testtools from tempest.api.compute import base from tempest.common import compute from tempest.common import utils from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF LOG = logging.getLogger(__name__) class LiveMigrationTest(base.BaseV2ComputeAdminTest): max_microversion = '2.24' block_migration = None @classmethod def skip_checks(cls): super(LiveMigrationTest, cls).skip_checks() if not CONF.compute_feature_enabled.live_migration: skip_msg = ("%s skipped as live-migration is " "not available" % cls.__name__) raise cls.skipException(skip_msg) if CONF.compute.min_compute_nodes < 2: raise cls.skipException( "Less than 2 compute nodes, skipping migration test.") @classmethod def setup_credentials(cls): # These tests don't attempt any SSH validation nor do they use # floating IPs on the instance, so all we need is a network and # a subnet so the instance being migrated has a single port, but # we need that to make sure we are properly updating the port # host bindings during the live migration. # TODO(mriedem): SSH validation before and after the instance is # live migrated would be a nice test wrinkle addition. cls.set_network_resources(network=True, subnet=True) super(LiveMigrationTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(LiveMigrationTest, cls).setup_clients() cls.admin_migration_client = cls.os_admin.migrations_client def _migrate_server_to(self, server_id, dest_host, volume_backed=False): kwargs = dict() block_migration = getattr(self, 'block_migration', None) if self.block_migration is None: kwargs['disk_over_commit'] = False block_migration = (CONF.compute_feature_enabled. block_migration_for_live_migration and not volume_backed) self.admin_servers_client.live_migrate_server( server_id, host=dest_host, block_migration=block_migration, **kwargs) def _live_migrate(self, server_id, target_host, state, volume_backed=False): self._migrate_server_to(server_id, target_host, volume_backed) waiters.wait_for_server_status(self.servers_client, server_id, state) migration_list = (self.admin_migration_client.list_migrations() ['migrations']) msg = ("Live Migration failed. Migrations list for Instance " "%s: [" % server_id) for live_migration in migration_list: if (live_migration['instance_uuid'] == server_id): msg += "\n%s" % live_migration msg += "]" self.assertEqual(target_host, self.get_host_for_server(server_id), msg) def _test_live_migration(self, state='ACTIVE', volume_backed=False): """Tests live migration between two hosts. Requires CONF.compute_feature_enabled.live_migration to be True. :param state: The vm_state the migrated server should be in before and after the live migration. Supported values are 'ACTIVE' and 'PAUSED'. :param volume_backed: If the instance is volume backed or not. If volume_backed, *block* migration is not used. """ # Live migrate an instance to another host server_id = self.create_test_server(wait_until="ACTIVE", volume_backed=volume_backed)['id'] source_host = self.get_host_for_server(server_id) destination_host = self.get_host_other_than(server_id) if state == 'PAUSED': self.admin_servers_client.pause_server(server_id) waiters.wait_for_server_status(self.admin_servers_client, server_id, state) LOG.info("Live migrate from source %s to destination %s", source_host, destination_host) self._live_migrate(server_id, destination_host, state, volume_backed) if CONF.compute_feature_enabled.live_migrate_back_and_forth: # If live_migrate_back_and_forth is enabled it is a grenade job. # Therefore test should validate whether LM is compatible in both # ways, so live migrate VM back to the source host LOG.info("Live migrate back to source %s", source_host) self._live_migrate(server_id, source_host, state, volume_backed) @decorators.idempotent_id('1dce86b8-eb04-4c03-a9d8-9c1dc3ee0c7b') def test_live_block_migration(self): self._test_live_migration() @decorators.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') def test_live_block_migration_paused(self): self._test_live_migration(state='PAUSED') @decorators.skip_because(bug="1524898") @decorators.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd') @utils.services('volume') def test_volume_backed_live_migration(self): self._test_live_migration(volume_backed=True) @decorators.idempotent_id('e19c0cc6-6720-4ed8-be83-b6603ed5c812') @testtools.skipIf(not CONF.compute_feature_enabled. block_migration_for_live_migration, 'Block Live migration not available') @testtools.skipIf(not CONF.compute_feature_enabled. block_migrate_cinder_iscsi, 'Block Live migration not configured for iSCSI') def test_iscsi_volume(self): server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] target_host = self.get_host_other_than(server_id) volume = self.create_volume() # Attach the volume to the server self.attach_volume(server, volume, device='/dev/xvdb') server = self.admin_servers_client.show_server(server_id)['server'] volume_id1 = server["os-extended-volumes:volumes_attached"][0]["id"] self._migrate_server_to(server_id, target_host) waiters.wait_for_server_status(self.servers_client, server_id, 'ACTIVE') server = self.admin_servers_client.show_server(server_id)['server'] volume_id2 = server["os-extended-volumes:volumes_attached"][0]["id"] self.assertEqual(target_host, self.get_host_for_server(server_id)) self.assertEqual(volume_id1, volume_id2) class LiveMigrationRemoteConsolesV26Test(LiveMigrationTest): min_microversion = '2.6' max_microversion = 'latest' @decorators.idempotent_id('6190af80-513e-4f0f-90f2-9714e84955d7') @testtools.skipUnless(CONF.compute_feature_enabled.serial_console, 'Serial console not supported.') @testtools.skipUnless( compute.is_scheduler_filter_enabled("DifferentHostFilter"), 'DifferentHostFilter is not available.') def test_live_migration_serial_console(self): """Test the live-migration of an instance which has a serial console The serial console feature of an instance uses ports on the host. These ports need to be updated when they are already in use by another instance on the target host. This test checks if this update behavior is correctly done, by connecting to the serial consoles of the instances before and after the live migration. """ server01_id = self.create_test_server(wait_until='ACTIVE')['id'] hints = {'different_host': server01_id} server02_id = self.create_test_server(scheduler_hints=hints, wait_until='ACTIVE')['id'] host01_id = self.get_host_for_server(server01_id) host02_id = self.get_host_for_server(server02_id) self.assertNotEqual(host01_id, host02_id) # At this step we have 2 instances on different hosts, both with # serial consoles, both with port 10000 (the default value). # https://bugs.launchpad.net/nova/+bug/1455252 describes the issue # when live-migrating in such a scenario. self._verify_console_interaction(server01_id) self._verify_console_interaction(server02_id) self._migrate_server_to(server01_id, host02_id) waiters.wait_for_server_status(self.servers_client, server01_id, 'ACTIVE') self.assertEqual(host02_id, self.get_host_for_server(server01_id)) self._verify_console_interaction(server01_id) # At this point, both instances have a valid serial console # connection, which means the ports got updated. def _verify_console_interaction(self, server_id): body = self.servers_client.get_remote_console(server_id, console_type='serial', protocol='serial') console_url = body['remote_console']['url'] data = "test_live_migration_serial_console" console_output = '' t = 0.0 interval = 0.1 ws = compute.create_websocket(console_url) try: # NOTE (markus_z): It can take a long time until the terminal # of the instance is available for interaction. Hence the # long timeout value. while data not in console_output and t <= 120.0: try: ws.send_frame(data) recieved = ws.receive_frame() console_output += recieved except Exception: # In case we had an issue with send/receive on the # websocket connection, we create a new one. ws = compute.create_websocket(console_url) time.sleep(interval) t += interval finally: ws.close() self.assertIn(data, console_output) class LiveAutoBlockMigrationV225Test(LiveMigrationTest): min_microversion = '2.25' max_microversion = 'latest' block_migration = 'auto' tempest-17.2.0/tempest/api/compute/admin/test_networks.py0000666000175100017510000000536413207044712023603 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest import config from tempest.lib import decorators CONF = config.CONF class NetworksTest(base.BaseV2ComputeAdminTest): """Tests Nova Networks API that usually requires admin privileges. API docs: http://developer.openstack.org/api-ref-compute-v2-ext.html#ext-os-networks """ @classmethod def setup_clients(cls): super(NetworksTest, cls).setup_clients() cls.client = cls.os_admin.compute_networks_client @decorators.idempotent_id('d206d211-8912-486f-86e2-a9d090d1f416') def test_get_network(self): networks = self.client.list_networks()['networks'] if CONF.compute.fixed_network_name: configured_network = [x for x in networks if x['label'] == CONF.compute.fixed_network_name] self.assertEqual(1, len(configured_network), "{0} networks with label {1}".format( len(configured_network), CONF.compute.fixed_network_name)) elif CONF.network.public_network_id: configured_network = [x for x in networks if x['id'] == CONF.network.public_network_id] else: raise self.skipException( "Environment has no known-for-sure existing network.") configured_network = configured_network[0] network = (self.client.show_network(configured_network['id']) ['network']) self.assertEqual(configured_network['label'], network['label']) @decorators.idempotent_id('df3d1046-6fa5-4b2c-ad0c-cfa46a351cb9') def test_list_all_networks(self): networks = self.client.list_networks()['networks'] # Check the configured network is in the list if CONF.compute.fixed_network_name: configured_network = CONF.compute.fixed_network_name self.assertIn(configured_network, [x['label'] for x in networks]) else: network_labels = [x['label'] for x in networks] self.assertNotEmpty(network_labels) tempest-17.2.0/tempest/api/compute/admin/test_security_group_default_rules.py0000666000175100017510000001331013207044712027716 0ustar zuulzuul00000000000000# 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 testtools from tempest.api.compute import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class SecurityGroupDefaultRulesTest(base.BaseV2ComputeAdminTest): @classmethod # TODO(GMann): Once Bug# 1311500 is fixed, these test can run # for Neutron also. @testtools.skipIf(CONF.service_available.neutron, "Skip as this functionality is not yet " "implemented in Neutron. Related Bug#1311500") def setup_credentials(cls): # A network and a subnet will be created for these tests cls.set_network_resources(network=True, subnet=True) super(SecurityGroupDefaultRulesTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(SecurityGroupDefaultRulesTest, cls).setup_clients() cls.adm_client = cls.os_admin.security_group_default_rules_client def _create_security_group_default_rules(self, ip_protocol='tcp', from_port=22, to_port=22, cidr='10.10.0.0/24'): # Create Security Group default rule rule = self.adm_client.create_security_default_group_rule( ip_protocol=ip_protocol, from_port=from_port, to_port=to_port, cidr=cidr)['security_group_default_rule'] self.assertEqual(ip_protocol, rule['ip_protocol']) self.assertEqual(from_port, rule['from_port']) self.assertEqual(to_port, rule['to_port']) self.assertEqual(cidr, rule['ip_range']['cidr']) return rule @decorators.idempotent_id('6d880615-eec3-4d29-97c5-7a074dde239d') def test_create_delete_security_group_default_rules(self): # Create and delete Security Group default rule ip_protocols = ['tcp', 'udp', 'icmp'] for ip_protocol in ip_protocols: rule = self._create_security_group_default_rules(ip_protocol) # Delete Security Group default rule self.adm_client.delete_security_group_default_rule(rule['id']) self.assertRaises(lib_exc.NotFound, self.adm_client.show_security_group_default_rule, rule['id']) @decorators.idempotent_id('4d752e0a-33a1-4c3a-b498-ff8667ca22e5') def test_create_security_group_default_rule_without_cidr(self): ip_protocol = 'udp' from_port = 80 to_port = 80 rule = self.adm_client.create_security_default_group_rule( ip_protocol=ip_protocol, from_port=from_port, to_port=to_port)['security_group_default_rule'] self.addCleanup(self.adm_client.delete_security_group_default_rule, rule['id']) self.assertNotEqual(0, rule['id']) self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr']) @decorators.idempotent_id('29f2d218-69b0-4a95-8f3d-6bd0ef732b3a') def test_create_security_group_default_rule_with_blank_cidr(self): ip_protocol = 'icmp' from_port = 10 to_port = 10 cidr = '' rule = self.adm_client.create_security_default_group_rule( ip_protocol=ip_protocol, from_port=from_port, to_port=to_port, cidr=cidr)['security_group_default_rule'] self.addCleanup(self.adm_client.delete_security_group_default_rule, rule['id']) self.assertNotEqual(0, rule['id']) self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr']) @decorators.idempotent_id('6e6de55e-9146-4ae0-89f2-3569586e0b9b') def test_security_group_default_rules_list(self): ip_protocol = 'tcp' from_port = 22 to_port = 22 cidr = '10.10.0.0/24' rule = self._create_security_group_default_rules(ip_protocol, from_port, to_port, cidr) self.addCleanup(self.adm_client.delete_security_group_default_rule, rule['id']) rules = (self.adm_client.list_security_group_default_rules() ['security_group_default_rules']) self.assertNotEmpty(rules) self.assertIn(rule, rules) @decorators.idempotent_id('15cbb349-86b4-4f71-a048-04b7ef3f150b') def test_default_security_group_default_rule_show(self): ip_protocol = 'tcp' from_port = 22 to_port = 22 cidr = '10.10.0.0/24' rule = self._create_security_group_default_rules(ip_protocol, from_port, to_port, cidr) self.addCleanup(self.adm_client.delete_security_group_default_rule, rule['id']) fetched_rule = self.adm_client.show_security_group_default_rule( rule['id'])['security_group_default_rule'] self.assertEqual(rule, fetched_rule) tempest-17.2.0/tempest/api/compute/admin/test_aggregates.py0000666000175100017510000002313013207044712024027 0ustar zuulzuul00000000000000# 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. import testtools from tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import decorators CONF = config.CONF class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Aggregates API that require admin privileges""" @classmethod def setup_clients(cls): super(AggregatesAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.aggregates_client @classmethod def resource_setup(cls): super(AggregatesAdminTestJSON, cls).resource_setup() cls.aggregate_name_prefix = 'test_aggregate' cls.az_name_prefix = 'test_az' cls.host = None hypers = cls.os_admin.hypervisor_client.list_hypervisors( detail=True)['hypervisors'] if CONF.compute.hypervisor_type: hypers = [hyper for hyper in hypers if (hyper['hypervisor_type'] == CONF.compute.hypervisor_type)] hosts_available = [hyper['service']['host'] for hyper in hypers if (hyper['state'] == 'up' and hyper['status'] == 'enabled')] if hosts_available: cls.host = hosts_available[0] else: msg = "no available compute node found" if CONF.compute.hypervisor_type: msg += " for hypervisor_type %s" % CONF.compute.hypervisor_type raise testtools.TestCase.failureException(msg) def _create_test_aggregate(self, **kwargs): if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name(self.aggregate_name_prefix) aggregate = self.client.create_aggregate(**kwargs)['aggregate'] self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_aggregate, aggregate['id']) self.assertEqual(kwargs['name'], aggregate['name']) return aggregate @decorators.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20') def test_aggregate_create_delete(self): # Create and delete an aggregate. aggregate = self._create_test_aggregate() self.assertIsNone(aggregate['availability_zone']) self.client.delete_aggregate(aggregate['id']) self.client.wait_for_resource_deletion(aggregate['id']) @decorators.idempotent_id('5873a6f8-671a-43ff-8838-7ce430bb6d0b') def test_aggregate_create_delete_with_az(self): # Create and delete an aggregate. az_name = data_utils.rand_name(self.az_name_prefix) aggregate = self._create_test_aggregate(availability_zone=az_name) self.assertEqual(az_name, aggregate['availability_zone']) self.client.delete_aggregate(aggregate['id']) self.client.wait_for_resource_deletion(aggregate['id']) @decorators.idempotent_id('68089c38-04b1-4758-bdf0-cf0daec4defd') def test_aggregate_create_verify_entry_in_list(self): # Create an aggregate and ensure it is listed. aggregate = self._create_test_aggregate() aggregates = self.client.list_aggregates()['aggregates'] self.assertIn((aggregate['id'], aggregate['availability_zone']), map(lambda x: (x['id'], x['availability_zone']), aggregates)) @decorators.idempotent_id('36ec92ca-7a73-43bc-b920-7531809e8540') def test_aggregate_create_update_metadata_get_details(self): # Create an aggregate and ensure its details are returned. aggregate = self._create_test_aggregate() body = self.client.show_aggregate(aggregate['id'])['aggregate'] self.assertEqual(aggregate['name'], body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertEqual({}, body["metadata"]) # set the metadata of the aggregate meta = {"key": "value"} body = self.client.set_metadata(aggregate['id'], metadata=meta) self.assertEqual(meta, body['aggregate']["metadata"]) # verify the metadata has been set body = self.client.show_aggregate(aggregate['id'])['aggregate'] self.assertEqual(meta, body["metadata"]) @decorators.idempotent_id('4d2b2004-40fa-40a1-aab2-66f4dab81beb') def test_aggregate_create_update_with_az(self): # Update an aggregate and ensure properties are updated correctly aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) aggregate = self._create_test_aggregate( name=aggregate_name, availability_zone=az_name) self.assertEqual(az_name, aggregate['availability_zone']) aggregate_id = aggregate['id'] new_aggregate_name = aggregate_name + '_new' new_az_name = az_name + '_new' resp_aggregate = self.client.update_aggregate( aggregate_id, name=new_aggregate_name, availability_zone=new_az_name)['aggregate'] self.assertEqual(new_aggregate_name, resp_aggregate['name']) self.assertEqual(new_az_name, resp_aggregate['availability_zone']) aggregates = self.client.list_aggregates()['aggregates'] self.assertIn((aggregate_id, new_aggregate_name, new_az_name), map(lambda x: (x['id'], x['name'], x['availability_zone']), aggregates)) @decorators.idempotent_id('c8e85064-e79b-4906-9931-c11c24294d02') def test_aggregate_add_remove_host(self): # Add a host to the given aggregate and remove. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) aggregate = self._create_test_aggregate(name=aggregate_name) body = (self.client.add_host(aggregate['id'], host=self.host) ['aggregate']) self.assertEqual(aggregate_name, body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertIn(self.host, body['hosts']) body = (self.client.remove_host(aggregate['id'], host=self.host) ['aggregate']) self.assertEqual(aggregate_name, body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertNotIn(self.host, body['hosts']) @decorators.idempotent_id('7f6a1cc5-2446-4cdb-9baa-b6ae0a919b72') def test_aggregate_add_host_list(self): # Add a host to the given aggregate and list. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) aggregate = self._create_test_aggregate(name=aggregate_name) self.client.add_host(aggregate['id'], host=self.host) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.host) aggregates = self.client.list_aggregates()['aggregates'] aggs = [agg for agg in aggregates if agg['id'] == aggregate['id']] self.assertEqual(1, len(aggs)) agg = aggs[0] self.assertEqual(aggregate_name, agg['name']) self.assertIsNone(agg['availability_zone']) self.assertIn(self.host, agg['hosts']) @decorators.idempotent_id('eeef473c-7c52-494d-9f09-2ed7fc8fc036') def test_aggregate_add_host_get_details(self): # Add a host to the given aggregate and get details. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) aggregate = self._create_test_aggregate(name=aggregate_name) self.client.add_host(aggregate['id'], host=self.host) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.host) body = self.client.show_aggregate(aggregate['id'])['aggregate'] self.assertEqual(aggregate_name, body['name']) self.assertIsNone(body['availability_zone']) self.assertIn(self.host, body['hosts']) @decorators.idempotent_id('96be03c7-570d-409c-90f8-e4db3c646996') def test_aggregate_add_host_create_server_with_az(self): # Add a host to the given aggregate and create a server. self.useFixture(fixtures.LockFixture('availability_zone')) az_name = data_utils.rand_name(self.az_name_prefix) aggregate = self._create_test_aggregate(availability_zone=az_name) self.client.add_host(aggregate['id'], host=self.host) self.addCleanup(self.client.remove_host, aggregate['id'], host=self.host) admin_servers_client = self.os_admin.servers_client server = self.create_test_server(availability_zone=az_name, wait_until='ACTIVE') body = admin_servers_client.show_server(server['id'])['server'] self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host']) tempest-17.2.0/tempest/api/compute/admin/test_volumes_negative.py0000666000175100017510000000466213207044712025303 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumesAdminNegativeTest(base.BaseV2ComputeAdminTest): @classmethod def skip_checks(cls): super(VolumesAdminNegativeTest, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def resource_setup(cls): super(VolumesAdminNegativeTest, cls).resource_setup() cls.server = cls.create_test_server(wait_until='ACTIVE') @decorators.attr(type=['negative']) @decorators.idempotent_id('309b5ecd-0585-4a7e-a36f-d2b2bf55259d') def test_update_attached_volume_with_nonexistent_volume_in_uri(self): volume = self.create_volume() nonexistent_volume = data_utils.rand_uuid() self.assertRaises(lib_exc.NotFound, self.admin_servers_client.update_attached_volume, self.server['id'], nonexistent_volume, volumeId=volume['id']) @decorators.related_bug('1629110', status_code=400) @decorators.attr(type=['negative']) @decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a') def test_update_attached_volume_with_nonexistent_volume_in_body(self): volume = self.create_volume() self.attach_volume(self.server, volume) nonexistent_volume = data_utils.rand_uuid() self.assertRaises(lib_exc.BadRequest, self.admin_servers_client.update_attached_volume, self.server['id'], volume['id'], volumeId=nonexistent_volume) tempest-17.2.0/tempest/api/compute/admin/test_servers_on_multinodes.py0000666000175100017510000001130213207044712026344 0ustar zuulzuul00000000000000# 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 testtools from tempest.api.compute import base from tempest.common import compute from tempest import config from tempest.lib import decorators CONF = config.CONF class ServersOnMultiNodesTest(base.BaseV2ComputeAdminTest): @classmethod def resource_setup(cls): super(ServersOnMultiNodesTest, cls).resource_setup() cls.server01 = cls.create_test_server(wait_until='ACTIVE')['id'] cls.host01 = cls._get_host(cls.server01) @classmethod def skip_checks(cls): super(ServersOnMultiNodesTest, cls).skip_checks() if CONF.compute.min_compute_nodes < 2: raise cls.skipException( "Less than 2 compute nodes, skipping multi-nodes test.") @classmethod def _get_host(cls, server_id): return cls.os_admin.servers_client.show_server( server_id)['server']['OS-EXT-SRV-ATTR:host'] @decorators.idempotent_id('26a9d5df-6890-45f2-abc4-a659290cb130') @testtools.skipUnless( compute.is_scheduler_filter_enabled("SameHostFilter"), 'SameHostFilter is not available.') def test_create_servers_on_same_host(self): hints = {'same_host': self.server01} server02 = self.create_test_server(scheduler_hints=hints, wait_until='ACTIVE')['id'] host02 = self._get_host(server02) self.assertEqual(self.host01, host02) @decorators.idempotent_id('cc7ca884-6e3e-42a3-a92f-c522fcf25e8e') @testtools.skipUnless( compute.is_scheduler_filter_enabled("DifferentHostFilter"), 'DifferentHostFilter is not available.') def test_create_servers_on_different_hosts(self): hints = {'different_host': self.server01} server02 = self.create_test_server(scheduler_hints=hints, wait_until='ACTIVE')['id'] host02 = self._get_host(server02) self.assertNotEqual(self.host01, host02) @decorators.idempotent_id('7869cc84-d661-4e14-9f00-c18cdc89cf57') @testtools.skipUnless( compute.is_scheduler_filter_enabled("DifferentHostFilter"), 'DifferentHostFilter is not available.') def test_create_servers_on_different_hosts_with_list_of_servers(self): # This scheduler-hint supports list of servers also. hints = {'different_host': [self.server01]} server02 = self.create_test_server(scheduler_hints=hints, wait_until='ACTIVE')['id'] host02 = self._get_host(server02) self.assertNotEqual(self.host01, host02) @decorators.idempotent_id('f8bd0867-e459-45f5-ba53-59134552fe04') @testtools.skipUnless( compute.is_scheduler_filter_enabled("ServerGroupAntiAffinityFilter"), 'ServerGroupAntiAffinityFilter is not available.') def test_create_server_with_scheduler_hint_group_anti_affinity(self): """Tests the ServerGroupAntiAffinityFilter Creates two servers in an anti-affinity server group and asserts the servers are in the group and on different hosts. """ group_id = self.create_test_server_group( policy=['anti-affinity'])['id'] hints = {'group': group_id} reservation_id = self.create_test_server( scheduler_hints=hints, wait_until='ACTIVE', min_count=2, return_reservation_id=True)['reservation_id'] # Get the servers using the reservation_id. servers = self.servers_client.list_servers( detail=True, reservation_id=reservation_id)['servers'] self.assertEqual(2, len(servers)) # Assert the servers are in the group. server_group = self.server_groups_client.show_server_group( group_id)['server_group'] hosts = {} for server in servers: self.assertIn(server['id'], server_group['members']) hosts[server['id']] = self._get_host(server['id']) # Assert the servers are on different hosts. hostnames = list(hosts.values()) self.assertNotEqual(hostnames[0], hostnames[1], 'Servers are on the same host: %s' % hosts) tempest-17.2.0/tempest/api/compute/admin/test_simple_tenant_usage_negative.py0000666000175100017510000000542513207044712027635 0ustar zuulzuul00000000000000# 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. import datetime from tempest.api.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class TenantUsagesNegativeTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setup_clients(cls): super(TenantUsagesNegativeTestJSON, cls).setup_clients() cls.adm_client = cls.os_admin.tenant_usages_client cls.client = cls.os_primary.tenant_usages_client @classmethod def resource_setup(cls): super(TenantUsagesNegativeTestJSON, cls).resource_setup() now = datetime.datetime.now() cls.start = cls._parse_strtime(now - datetime.timedelta(days=1)) cls.end = cls._parse_strtime(now + datetime.timedelta(days=1)) @classmethod def _parse_strtime(cls, at): # Returns formatted datetime return at.strftime('%Y-%m-%dT%H:%M:%S.%f') @decorators.attr(type=['negative']) @decorators.idempotent_id('8b21e135-d94b-4991-b6e9-87059609c8ed') def test_get_usage_tenant_with_empty_tenant_id(self): # Get usage for a specific tenant empty params = {'start': self.start, 'end': self.end} self.assertRaises(lib_exc.NotFound, self.adm_client.show_tenant_usage, '', **params) @decorators.attr(type=['negative']) @decorators.idempotent_id('4079dd2a-9e8d-479f-869d-6fa985ce45b6') def test_get_usage_tenant_with_invalid_date(self): # Get usage for tenant with invalid date params = {'start': self.end, 'end': self.start} self.assertRaises(lib_exc.BadRequest, self.adm_client.show_tenant_usage, self.client.tenant_id, **params) @decorators.attr(type=['negative']) @decorators.idempotent_id('bbe6fe2c-15d8-404c-a0a2-44fad0ad5cc7') def test_list_usage_all_tenants_with_non_admin_user(self): # Get usage for all tenants with non admin user params = {'start': self.start, 'end': self.end, 'detailed': "1"} self.assertRaises(lib_exc.Forbidden, self.client.list_tenant_usages, **params) tempest-17.2.0/tempest/api/compute/admin/test_hosts_negative.py0000666000175100017510000001400013207044712024734 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class HostsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests hosts API using admin privileges.""" @classmethod def setup_clients(cls): super(HostsAdminNegativeTestJSON, cls).setup_clients() cls.client = cls.os_admin.hosts_client cls.non_admin_client = cls.os_primary.hosts_client @classmethod def resource_setup(cls): super(HostsAdminNegativeTestJSON, cls).resource_setup() hosts = cls.client.list_hosts()['hosts'] if not hosts: raise lib_exc.NotFound("no host found") cls.hostname = hosts[0]['host_name'] @decorators.attr(type=['negative']) @decorators.idempotent_id('dd032027-0210-4d9c-860e-69b1b8deed5f') def test_list_hosts_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.list_hosts) @decorators.attr(type=['negative']) @decorators.idempotent_id('e75b0a1a-041f-47a1-8b4a-b72a6ff36d3f') def test_show_host_detail_with_nonexistent_hostname(self): self.assertRaises(lib_exc.NotFound, self.client.show_host, 'nonexistent_hostname') @decorators.attr(type=['negative']) @decorators.idempotent_id('19ebe09c-bfd4-4b7c-81a2-e2e0710f59cc') def test_show_host_detail_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.show_host, self.hostname) @decorators.attr(type=['negative']) @decorators.idempotent_id('e40c72b1-0239-4ed6-ba21-81a184df1f7c') def test_update_host_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.update_host, self.hostname, status='enable', maintenance_mode='enable') @decorators.attr(type=['negative']) @decorators.idempotent_id('fbe2bf3e-3246-4a95-a59f-94e4e298ec77') def test_update_host_with_invalid_status(self): # 'status' can only be 'enable' or 'disable' self.assertRaises(lib_exc.BadRequest, self.client.update_host, self.hostname, status='invalid', maintenance_mode='enable') @decorators.attr(type=['negative']) @decorators.idempotent_id('ab1e230e-5e22-41a9-8699-82b9947915d4') def test_update_host_with_invalid_maintenance_mode(self): # 'maintenance_mode' can only be 'enable' or 'disable' self.assertRaises(lib_exc.BadRequest, self.client.update_host, self.hostname, status='enable', maintenance_mode='invalid') @decorators.attr(type=['negative']) @decorators.idempotent_id('0cd85f75-6992-4a4a-b1bd-d11e37fd0eee') def test_update_host_without_param(self): # 'status' or 'maintenance_mode' needed for host update self.assertRaises(lib_exc.BadRequest, self.client.update_host, self.hostname) @decorators.attr(type=['negative']) @decorators.idempotent_id('23c92146-2100-4d68-b2d6-c7ade970c9c1') def test_update_nonexistent_host(self): self.assertRaises(lib_exc.NotFound, self.client.update_host, 'nonexistent_hostname', status='enable', maintenance_mode='enable') @decorators.attr(type=['negative']) @decorators.idempotent_id('0d981ac3-4320-4898-b674-82b61fbb60e4') def test_startup_nonexistent_host(self): self.assertRaises(lib_exc.NotFound, self.client.startup_host, 'nonexistent_hostname') @decorators.attr(type=['negative']) @decorators.idempotent_id('9f4ebb7e-b2ae-4e5b-a38f-0fd1bb0ddfca') def test_startup_host_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.startup_host, self.hostname) @decorators.attr(type=['negative']) @decorators.idempotent_id('9e637444-29cf-4244-88c8-831ae82c31b6') def test_shutdown_nonexistent_host(self): self.assertRaises(lib_exc.NotFound, self.client.shutdown_host, 'nonexistent_hostname') @decorators.attr(type=['negative']) @decorators.idempotent_id('a803529c-7e3f-4d3c-a7d6-8e1c203d27f6') def test_shutdown_host_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.shutdown_host, self.hostname) @decorators.attr(type=['negative']) @decorators.idempotent_id('f86bfd7b-0b13-4849-ae29-0322e83ee58b') def test_reboot_nonexistent_host(self): self.assertRaises(lib_exc.NotFound, self.client.reboot_host, 'nonexistent_hostname') @decorators.attr(type=['negative']) @decorators.idempotent_id('02d79bb9-eb57-4612-abf6-2cb38897d2f8') def test_reboot_host_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.reboot_host, self.hostname) tempest-17.2.0/tempest/api/compute/admin/test_flavors_extra_specs_negative.py0000666000175100017510000001271213207044712027660 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # 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 tempest.api.compute import base from tempest.common import utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest): """Negative Tests Flavor Extra Spec API extension. SET, UNSET, UPDATE Flavor Extra specs require admin privileges. """ @classmethod def skip_checks(cls): super(FlavorsExtraSpecsNegativeTestJSON, cls).skip_checks() if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'): msg = "OS-FLV-EXT-DATA extension not enabled." raise cls.skipException(msg) @classmethod def resource_setup(cls): super(FlavorsExtraSpecsNegativeTestJSON, cls).resource_setup() flavor_name = data_utils.rand_name('test_flavor') ram = 512 vcpus = 1 disk = 10 ephemeral = 10 new_flavor_id = data_utils.rand_int_id(start=1000) swap = 1024 rxtx = 1 # Create a flavor cls.flavor = cls.admin_flavors_client.create_flavor( name=flavor_name, ram=ram, vcpus=vcpus, disk=disk, id=new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx_factor=rxtx)['flavor'] cls.addClassResourceCleanup( cls.admin_flavors_client.wait_for_resource_deletion, cls.flavor['id']) cls.addClassResourceCleanup(cls.admin_flavors_client.delete_flavor, cls.flavor['id']) @decorators.attr(type=['negative']) @decorators.idempotent_id('a00a3b81-5641-45a8-ab2b-4a8ec41e1d7d') def test_flavor_non_admin_set_keys(self): # Test to SET flavor extra spec as a user without admin privileges. self.assertRaises(lib_exc.Forbidden, self.flavors_client.set_flavor_extra_spec, self.flavor['id'], key1="value1", key2="value2") @decorators.attr(type=['negative']) @decorators.idempotent_id('1ebf4ef8-759e-48fe-a801-d451d80476fb') def test_flavor_non_admin_update_specific_key(self): # non admin user is not allowed to update flavor extra spec body = self.admin_flavors_client.set_flavor_extra_spec( self.flavor['id'], key1="value1", key2="value2")['extra_specs'] self.assertEqual(body['key1'], 'value1') self.assertRaises(lib_exc.Forbidden, self.flavors_client. update_flavor_extra_spec, self.flavor['id'], 'key1', key1='value1_new') @decorators.attr(type=['negative']) @decorators.idempotent_id('28f12249-27c7-44c1-8810-1f382f316b11') def test_flavor_non_admin_unset_keys(self): self.admin_flavors_client.set_flavor_extra_spec( self.flavor['id'], key1="value1", key2="value2") self.assertRaises(lib_exc.Forbidden, self.flavors_client.unset_flavor_extra_spec, self.flavor['id'], 'key1') @decorators.attr(type=['negative']) @decorators.idempotent_id('440b9f3f-3c7f-4293-a106-0ceda350f8de') def test_flavor_unset_nonexistent_key(self): self.assertRaises(lib_exc.NotFound, self.admin_flavors_client.unset_flavor_extra_spec, self.flavor['id'], 'nonexistent_key') @decorators.attr(type=['negative']) @decorators.idempotent_id('329a7be3-54b2-48be-8052-bf2ce4afd898') def test_flavor_get_nonexistent_key(self): self.assertRaises(lib_exc.NotFound, self.flavors_client.show_flavor_extra_spec, self.flavor['id'], "nonexistent_key") @decorators.attr(type=['negative']) @decorators.idempotent_id('25b822b8-9f49-44f6-80de-d99f0482e5cb') def test_flavor_update_mismatch_key(self): # the key will be updated should be match the key in the body self.assertRaises(lib_exc.BadRequest, self.admin_flavors_client.update_flavor_extra_spec, self.flavor['id'], "key2", key1="value") @decorators.attr(type=['negative']) @decorators.idempotent_id('f5889590-bf66-41cc-b4b1-6e6370cfd93f') def test_flavor_update_more_key(self): # there should be just one item in the request body self.assertRaises(lib_exc.BadRequest, self.admin_flavors_client.update_flavor_extra_spec, self.flavor['id'], "key1", key1="value", key2="value") tempest-17.2.0/tempest/api/compute/admin/test_server_diagnostics_negative.py0000666000175100017510000000304313207044712027476 0ustar zuulzuul00000000000000# Copyright 2017 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. from tempest.api.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServerDiagnosticsNegativeTest(base.BaseV2ComputeAdminTest): min_microversion = None max_microversion = '2.47' @classmethod def setup_clients(cls): super(ServerDiagnosticsNegativeTest, cls).setup_clients() cls.client = cls.servers_client @decorators.attr(type=['negative']) @decorators.idempotent_id('e84e2234-60d2-42fa-8b30-e2d3049724ac') def test_get_server_diagnostics_by_non_admin(self): # Non-admin user cannot view server diagnostics according to policy server_id = self.create_test_server(wait_until='ACTIVE')['id'] self.assertRaises(lib_exc.Forbidden, self.client.show_server_diagnostics, server_id) class ServerDiagnosticsNegativeV248Test(ServerDiagnosticsNegativeTest): min_microversion = '2.48' max_microversion = 'latest' tempest-17.2.0/tempest/api/compute/admin/test_fixed_ips.py0000666000175100017510000000503013207044712023667 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF class FixedIPsTestJson(base.BaseV2ComputeAdminTest): @classmethod def skip_checks(cls): super(FixedIPsTestJson, cls).skip_checks() if CONF.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg) if not utils.get_service_list()['network']: raise cls.skipException("network service not enabled.") @classmethod def setup_clients(cls): super(FixedIPsTestJson, cls).setup_clients() cls.client = cls.os_admin.fixed_ips_client @classmethod def resource_setup(cls): super(FixedIPsTestJson, cls).resource_setup() server = cls.create_test_server(wait_until='ACTIVE') server = cls.servers_client.show_server(server['id'])['server'] cls.ip = None for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break if cls.ip is None: raise cls.skipException("No fixed ip found for server: %s" % server['id']) @decorators.idempotent_id('16b7d848-2f7c-4709-85a3-2dfb4576cc52') def test_list_fixed_ip_details(self): fixed_ip = self.client.show_fixed_ip(self.ip) self.assertEqual(fixed_ip['fixed_ip']['address'], self.ip) @decorators.idempotent_id('5485077b-7e46-4cec-b402-91dc3173433b') def test_set_reserve(self): self.client.reserve_fixed_ip(self.ip, reserve="None") @decorators.idempotent_id('7476e322-b9ff-4710-bf82-49d51bac6e2e') def test_set_unreserve(self): self.client.reserve_fixed_ip(self.ip, unreserve="None") tempest-17.2.0/tempest/api/compute/admin/test_services_negative.py0000666000175100017510000000526113207044712025430 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Services API. List and Enable/Disable require admin privileges.""" @classmethod def setup_clients(cls): super(ServicesAdminNegativeTestJSON, cls).setup_clients() cls.client = cls.os_admin.services_client cls.non_admin_client = cls.services_client @decorators.attr(type=['negative']) @decorators.idempotent_id('1126d1f8-266e-485f-a687-adc547492646') def test_list_services_with_non_admin_user(self): self.assertRaises(lib_exc.Forbidden, self.non_admin_client.list_services) @decorators.attr(type=['negative']) @decorators.idempotent_id('d0884a69-f693-4e79-a9af-232d15643bf7') def test_get_service_by_invalid_params(self): # return all services if send the request with invalid parameter services = self.client.list_services()['services'] services_xxx = (self.client.list_services(xxx='nova-compute') ['services']) self.assertEqual(len(services), len(services_xxx)) @decorators.attr(type=['negative']) @decorators.idempotent_id('1e966d4a-226e-47c7-b601-0b18a27add54') def test_get_service_by_invalid_service_and_valid_host(self): services = self.client.list_services()['services'] host_name = services[0]['host'] services = self.client.list_services(host=host_name, binary='xxx')['services'] self.assertEmpty(services) @decorators.attr(type=['negative']) @decorators.idempotent_id('64e7e7fb-69e8-4cb6-a71d-8d5eb0c98655') def test_get_service_with_valid_service_and_invalid_host(self): services = self.client.list_services()['services'] binary_name = services[0]['binary'] services = self.client.list_services(host='xxx', binary=binary_name)['services'] self.assertEmpty(services) tempest-17.2.0/tempest/api/compute/admin/test_hypervisor.py0000666000175100017510000001157713207044712024144 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest): """Tests Hypervisors API that require admin privileges""" @classmethod def setup_clients(cls): super(HypervisorAdminTestJSON, cls).setup_clients() cls.client = cls.os_admin.hypervisor_client def _list_hypervisors(self): # List of hypervisors hypers = self.client.list_hypervisors()['hypervisors'] return hypers @decorators.idempotent_id('7f0ceacd-c64d-4e96-b8ee-d02943142cc5') def test_get_hypervisor_list(self): # List of hypervisor and available hypervisors hostname hypers = self._list_hypervisors() self.assertNotEmpty(hypers, "No hypervisors found.") @decorators.idempotent_id('1e7fdac2-b672-4ad1-97a4-bad0e3030118') def test_get_hypervisor_list_details(self): # Display the details of the all hypervisor hypers = self.client.list_hypervisors(detail=True)['hypervisors'] self.assertNotEmpty(hypers, "No hypervisors found.") @decorators.idempotent_id('94ff9eae-a183-428e-9cdb-79fde71211cc') def test_get_hypervisor_show_details(self): # Display the details of the specified hypervisor hypers = self._list_hypervisors() self.assertNotEmpty(hypers, "No hypervisors found.") details = self.client.show_hypervisor(hypers[0]['id'])['hypervisor'] self.assertNotEmpty(details) self.assertEqual(details['hypervisor_hostname'], hypers[0]['hypervisor_hostname']) @decorators.idempotent_id('e81bba3f-6215-4e39-a286-d52d2f906862') def test_get_hypervisor_show_servers(self): # Show instances about the specific hypervisors hypers = self._list_hypervisors() self.assertNotEmpty(hypers, "No hypervisors found.") hostname = hypers[0]['hypervisor_hostname'] hypervisors = (self.client.list_servers_on_hypervisor(hostname) ['hypervisors']) self.assertNotEmpty(hypervisors) @decorators.idempotent_id('797e4f28-b6e0-454d-a548-80cc77c00816') def test_get_hypervisor_stats(self): # Verify the stats of the all hypervisor stats = (self.client.show_hypervisor_statistics() ['hypervisor_statistics']) self.assertNotEmpty(stats) @decorators.idempotent_id('91a50d7d-1c2b-4f24-b55a-a1fe20efca70') def test_get_hypervisor_uptime(self): # Verify that GET shows the specified hypervisor uptime hypers = self._list_hypervisors() # Ironic will register each baremetal node as a 'hypervisor', # so the hypervisor list can contain many hypervisors of type # 'ironic'. If they are ALL ironic, skip this test since ironic # doesn't support hypervisor uptime. Otherwise, remove them # from the list of hypervisors to test. ironic_only = True hypers_without_ironic = [] for hyper in hypers: details = (self.client.show_hypervisor(hyper['id']) ['hypervisor']) if details['hypervisor_type'] != 'ironic': hypers_without_ironic.append(hyper) ironic_only = False if ironic_only: raise self.skipException( "Ironic does not support hypervisor uptime") has_valid_uptime = False for hyper in hypers_without_ironic: # because hypervisors might be disabled, this loops looking # for any good hit. try: uptime = (self.client.show_hypervisor_uptime(hyper['id']) ['hypervisor']) if uptime: has_valid_uptime = True break except Exception: pass self.assertTrue( has_valid_uptime, "None of the hypervisors had a valid uptime: %s" % hypers) @decorators.idempotent_id('d7e1805b-3b14-4a3b-b6fd-50ec6d9f361f') def test_search_hypervisor(self): hypers = self._list_hypervisors() self.assertNotEmpty(hypers, "No hypervisors found.") hypers = self.client.search_hypervisor( hypers[0]['hypervisor_hostname'])['hypervisors'] self.assertNotEmpty(hypers, "No hypervisors found.") tempest-17.2.0/tempest/api/compute/test_tenant_networks.py0000666000175100017510000000340513207044712024056 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest.common import utils from tempest.lib import decorators class ComputeTenantNetworksTest(base.BaseV2ComputeTest): @classmethod def resource_setup(cls): super(ComputeTenantNetworksTest, cls).resource_setup() cls.client = cls.os_primary.tenant_networks_client cls.network = cls.get_tenant_network() @classmethod def setup_credentials(cls): cls.set_network_resources(network=True) super(ComputeTenantNetworksTest, cls).setup_credentials() @decorators.idempotent_id('edfea98e-bbe3-4c7a-9739-87b986baff26') @utils.services('network') def test_list_show_tenant_networks(self): # Fetch all networks that are visible to the tenant: this may include # shared and external networks tenant_networks = [ n['id'] for n in self.client.list_tenant_networks()['networks'] ] self.assertIn(self.network['id'], tenant_networks, "No tenant networks found.") net = self.client.show_tenant_network(self.network['id']) self.assertEqual(self.network['id'], net['network']['id']) tempest-17.2.0/tempest/api/compute/keypairs/0000775000175100017510000000000013207045130021036 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/keypairs/test_keypairs_v22.py0000666000175100017510000000445213207044712025003 0ustar zuulzuul00000000000000# 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.api.compute.keypairs import test_keypairs from tempest.lib.common.utils import data_utils from tempest.lib import decorators class KeyPairsV22TestJSON(test_keypairs.KeyPairsV2TestJSON): min_microversion = '2.2' max_microversion = 'latest' def _check_keypair_type(self, keypair, keypair_type): if keypair_type is None: keypair_type = 'ssh' self.assertEqual(keypair_type, keypair['type']) def _test_keypairs_create_list_show(self, keypair_type=None): k_name = data_utils.rand_name('keypair') keypair = self.create_keypair(k_name, keypair_type=keypair_type) # Verify whether 'type' is present in keypair create response of # version 2.2 and it is with default value 'ssh'. self._check_keypair_type(keypair, keypair_type) keypair_detail = self.client.show_keypair(k_name)['keypair'] self._check_keypair_type(keypair_detail, keypair_type) fetched_list = self.client.list_keypairs()['keypairs'] for keypair in fetched_list: # Verify whether 'type' is present in keypair list response of # version 2.2 and it is with default value 'ssh'. if keypair['keypair']['name'] == k_name: self._check_keypair_type(keypair['keypair'], keypair_type) @decorators.idempotent_id('8726fa85-7f98-4b20-af9e-f710a4f3391c') def test_keypairsv22_create_list_show(self): self._test_keypairs_create_list_show() @decorators.idempotent_id('89d59d43-f735-441a-abcf-0601727f47b6') def test_keypairsv22_create_list_show_with_type(self): keypair_type = 'x509' self._test_keypairs_create_list_show(keypair_type=keypair_type) tempest-17.2.0/tempest/api/compute/keypairs/__init__.py0000666000175100017510000000000013207044712023144 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/keypairs/base.py0000666000175100017510000000347013207044712022335 0ustar zuulzuul00000000000000# 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. from tempest.api.compute import base from tempest.lib.common.utils import data_utils class BaseKeypairTest(base.BaseV2ComputeTest): """Base test case class for all keypair API tests.""" @classmethod def setup_clients(cls): super(BaseKeypairTest, cls).setup_clients() cls.client = cls.keypairs_client def _delete_keypair(self, keypair_name, **params): self.client.delete_keypair(keypair_name, **params) def create_keypair(self, keypair_name=None, pub_key=None, keypair_type=None, user_id=None): if keypair_name is None: keypair_name = data_utils.rand_name( self.__class__.__name__ + '-keypair') kwargs = {'name': keypair_name} delete_params = {} if pub_key: kwargs.update({'public_key': pub_key}) if keypair_type: kwargs.update({'type': keypair_type}) if user_id: kwargs.update({'user_id': user_id}) delete_params['user_id'] = user_id body = self.client.create_keypair(**kwargs)['keypair'] self.addCleanup(self._delete_keypair, keypair_name, **delete_params) return body tempest-17.2.0/tempest/api/compute/keypairs/test_keypairs_negative.py0000666000175100017510000000775613207044712026206 0ustar zuulzuul00000000000000# 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. from tempest.api.compute.keypairs import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class KeyPairsNegativeTestJSON(base.BaseKeypairTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('29cca892-46ae-4d48-bc32-8fe7e731eb81') def test_keypair_create_with_invalid_pub_key(self): # Keypair should not be created with a non RSA public key pub_key = "ssh-rsa JUNK nova@ubuntu" self.assertRaises(lib_exc.BadRequest, self.create_keypair, pub_key=pub_key) @decorators.attr(type=['negative']) @decorators.idempotent_id('7cc32e47-4c42-489d-9623-c5e2cb5a2fa5') def test_keypair_delete_nonexistent_key(self): # Non-existent key deletion should throw a proper error k_name = data_utils.rand_name("keypair-non-existent") self.assertRaises(lib_exc.NotFound, self.client.delete_keypair, k_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('dade320e-69ca-42a9-ba4a-345300f127e0') def test_create_keypair_with_empty_public_key(self): # Keypair should not be created with an empty public key pub_key = ' ' self.assertRaises(lib_exc.BadRequest, self.create_keypair, pub_key=pub_key) @decorators.attr(type=['negative']) @decorators.idempotent_id('fc100c19-2926-4b9c-8fdc-d0589ee2f9ff') def test_create_keypair_when_public_key_bits_exceeds_maximum(self): # Keypair should not be created when public key bits are too long pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu' self.assertRaises(lib_exc.BadRequest, self.create_keypair, pub_key=pub_key) @decorators.attr(type=['negative']) @decorators.idempotent_id('0359a7f1-f002-4682-8073-0c91e4011b7c') def test_create_keypair_with_duplicate_name(self): # Keypairs with duplicate names should not be created k_name = data_utils.rand_name('keypair') self.client.create_keypair(name=k_name) # Now try the same keyname to create another key self.assertRaises(lib_exc.Conflict, self.create_keypair, k_name) self.client.delete_keypair(k_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('1398abe1-4a84-45fb-9294-89f514daff00') def test_create_keypair_with_empty_name_string(self): # Keypairs with name being an empty string should not be created self.assertRaises(lib_exc.BadRequest, self.create_keypair, '') @decorators.attr(type=['negative']) @decorators.idempotent_id('3faa916f-779f-4103-aca7-dc3538eee1b7') def test_create_keypair_with_long_keynames(self): # Keypairs with name longer than 255 chars should not be created k_name = 'keypair-'.ljust(260, '0') self.assertRaises(lib_exc.BadRequest, self.create_keypair, k_name) @decorators.attr(type=['negative']) @decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91') def test_create_keypair_invalid_name(self): # Keypairs with name being an invalid name should not be created k_name = 'key_/.\@:' self.assertRaises(lib_exc.BadRequest, self.create_keypair, k_name) tempest-17.2.0/tempest/api/compute/keypairs/test_keypairs.py0000666000175100017510000001011413207044712024302 0ustar zuulzuul00000000000000# 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 tempest.api.compute.keypairs import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators class KeyPairsV2TestJSON(base.BaseKeypairTest): max_microversion = '2.1' @decorators.idempotent_id('1d1dbedb-d7a0-432a-9d09-83f543c3c19b') def test_keypairs_create_list_delete(self): # Keypairs created should be available in the response list # Create 3 keypairs key_list = list() for _ in range(3): keypair = self.create_keypair() # Need to pop these keys so that our compare doesn't fail later, # as the keypair dicts from list API doesn't have them. keypair.pop('private_key') keypair.pop('user_id') key_list.append(keypair) # Fetch all keypairs and verify the list # has all created keypairs fetched_list = self.client.list_keypairs()['keypairs'] new_list = list() for keypair in fetched_list: new_list.append(keypair['keypair']) fetched_list = new_list # Now check if all the created keypairs are in the fetched list missing_kps = [kp for kp in key_list if kp not in fetched_list] self.assertFalse(missing_kps, "Failed to find keypairs %s in fetched list" % ', '.join(m_key['name'] for m_key in missing_kps)) @decorators.idempotent_id('6c1d3123-4519-4742-9194-622cb1714b7d') def test_keypair_create_delete(self): # Keypair should be created, verified and deleted k_name = data_utils.rand_name('keypair') keypair = self.create_keypair(k_name) key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name") @decorators.idempotent_id('a4233d5d-52d8-47cc-9a25-e1864527e3df') def test_get_keypair_detail(self): # Keypair should be created, Got details by name and deleted k_name = data_utils.rand_name('keypair') self.create_keypair(k_name) keypair_detail = self.client.show_keypair(k_name)['keypair'] self.assertEqual(keypair_detail['name'], k_name, "The created keypair name is not equal " "to requested name") @decorators.idempotent_id('39c90c6a-304a-49dd-95ec-2366129def05') def test_keypair_create_with_pub_key(self): # Keypair should be created with a given public key k_name = data_utils.rand_name('keypair') pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs" "Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd" "aZq7KZEwO0jhglaFjU1mpqq4Gz5RX156sCTNM9vRbw" "KAxfsdF9laBYVsex3m3Wmui3uYrKyumsoJn2g9GNnG1P" "I1mrVjZ61i0GY3khna+wzlTpCCmy5HNlrmbj3XLqBUpip" "TOXmsnr4sChzC53KCd8LXuwc1i/CZPvF+3XipvAgFSE53pCt" "LOeB1kYMOBaiUPLQTWXR3JpckqFIQwhIH0zoHlJvZE8hh90" "XcPojYN56tI0OlrGqojbediJYD0rUsJu4weZpbn8vilb3JuDY+jws" "snSA8wzBx3A/8y9Pp1B nova@ubuntu") keypair = self.create_keypair(k_name, pub_key) self.assertNotIn('private_key', keypair, "Field private_key is not empty!") key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name!") tempest-17.2.0/tempest/api/compute/test_extensions.py0000666000175100017510000000406713207044712023035 0ustar zuulzuul00000000000000# 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_log import log as logging from tempest.api.compute import base from tempest.common import utils from tempest import config from tempest.lib import decorators CONF = config.CONF LOG = logging.getLogger(__name__) class ExtensionsTest(base.BaseV2ComputeTest): @decorators.idempotent_id('3bb27738-b759-4e0d-a5fa-37d7a6df07d1') def test_list_extensions(self): # List of all extensions if not CONF.compute_feature_enabled.api_extensions: raise self.skipException('There are not any extensions configured') extensions = self.extensions_client.list_extensions()['extensions'] ext = CONF.compute_feature_enabled.api_extensions[0] # Log extensions list extension_list = map(lambda x: x['alias'], extensions) LOG.debug("Nova extensions: %s", ','.join(extension_list)) if ext == 'all': self.assertIn('Hosts', map(lambda x: x['name'], extensions)) elif ext: self.assertIn(ext, extension_list) else: raise self.skipException('There are not any extensions configured') @decorators.idempotent_id('05762f39-bdfa-4cdb-9b46-b78f8e78e2fd') @utils.requires_ext(extension='os-consoles', service='compute') def test_get_extension(self): # get the specified extensions extension = self.extensions_client.show_extension('os-consoles') self.assertEqual('os-consoles', extension['extension']['alias']) tempest-17.2.0/tempest/api/compute/test_versions.py0000666000175100017510000000624013207044712022501 0ustar zuulzuul00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.lib import decorators class TestVersions(base.BaseV2ComputeTest): @decorators.idempotent_id('6c0a0990-43b6-4529-9b61-5fd8daf7c55c') @decorators.attr(type='smoke') def test_list_api_versions(self): """Test that a get of the unversioned url returns the choices doc. A key feature in OpenStack services is the idea that you can GET / on the service and get a list of the versioned endpoints that you can access. This comes back as a status 300 request. It's important that this is available to API consumers to discover the API they can use. """ result = self.versions_client.list_versions() versions = result['versions'] # NOTE(sdague): at a later point we may want to loosen this # up, but for now this should be true of all Novas deployed. self.assertEqual(versions[0]['id'], 'v2.0', "The first listed version should be v2.0") @decorators.idempotent_id('b953a29e-929c-4a8e-81be-ec3a7e03cb76') @decorators.attr(type='smoke') def test_get_version_details(self): """Test individual version endpoints info works. In addition to the GET / version request, there is also a version info document stored at the top of the versioned endpoints. This provides access to details about that endpoint, including min / max version if that implements microversions. This test starts with the version list, iterates all the returned endpoints, and fetches them. This will also ensure that all the version links are followable constructs which will help detect configuration issues when SSL termination isn't done completely for a site. """ result = self.versions_client.list_versions() versions = result['versions'] # iterate through all the versions that are returned and # attempt to get their version documents. for version in versions: links = [x for x in version['links'] if x['rel'] == 'self'] self.assertEqual( len(links), 1, "There should only be 1 self link in %s" % version) link = links[0] # this is schema validated result = self.versions_client.get_version_by_url(link['href']) # ensure the new self link matches the old one newlinks = [x for x in result['version']['links'] if x['rel'] == 'self'] self.assertEqual(links, newlinks) tempest-17.2.0/tempest/api/compute/__init__.py0000666000175100017510000000000013207044712021315 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/base.py0000666000175100017510000005505613207044725020521 0ustar zuulzuul00000000000000# 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 time from oslo_log import log as logging from tempest.api.compute import api_microversion_fixture from tempest.common import compute from tempest.common import waiters from tempest import config from tempest import exceptions from tempest.lib.common import api_version_request from tempest.lib.common import api_version_utils from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import exceptions as lib_exc import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseV2ComputeTest(api_version_utils.BaseMicroversionTest, tempest.test.BaseTestCase): """Base test case class for all Compute API tests.""" force_tenant_isolation = False # TODO(andreaf) We should care also for the alt_manager here # but only once client lazy load in the manager is done credentials = ['primary'] @classmethod def skip_checks(cls): super(BaseV2ComputeTest, cls).skip_checks() if not CONF.service_available.nova: raise cls.skipException("Nova is not available") cfg_min_version = CONF.compute.min_microversion cfg_max_version = CONF.compute.max_microversion api_version_utils.check_skip_with_microversion(cls.min_microversion, cls.max_microversion, cfg_min_version, cfg_max_version) @classmethod def setup_credentials(cls): cls.set_network_resources() super(BaseV2ComputeTest, cls).setup_credentials() @classmethod def setup_clients(cls): super(BaseV2ComputeTest, cls).setup_clients() cls.servers_client = cls.os_primary.servers_client cls.server_groups_client = cls.os_primary.server_groups_client cls.flavors_client = cls.os_primary.flavors_client cls.compute_images_client = cls.os_primary.compute_images_client cls.extensions_client = cls.os_primary.extensions_client cls.floating_ip_pools_client = cls.os_primary.floating_ip_pools_client cls.floating_ips_client = cls.os_primary.compute_floating_ips_client cls.keypairs_client = cls.os_primary.keypairs_client cls.security_group_rules_client = ( cls.os_primary.compute_security_group_rules_client) cls.security_groups_client =\ cls.os_primary.compute_security_groups_client cls.quotas_client = cls.os_primary.quotas_client cls.compute_networks_client = cls.os_primary.compute_networks_client cls.limits_client = cls.os_primary.limits_client cls.volumes_extensions_client =\ cls.os_primary.volumes_extensions_client cls.snapshots_extensions_client =\ cls.os_primary.snapshots_extensions_client cls.interfaces_client = cls.os_primary.interfaces_client cls.fixed_ips_client = cls.os_primary.fixed_ips_client cls.availability_zone_client = cls.os_primary.availability_zone_client cls.agents_client = cls.os_primary.agents_client cls.aggregates_client = cls.os_primary.aggregates_client cls.services_client = cls.os_primary.services_client cls.instance_usages_audit_log_client = ( cls.os_primary.instance_usages_audit_log_client) cls.hypervisor_client = cls.os_primary.hypervisor_client cls.certificates_client = cls.os_primary.certificates_client cls.migrations_client = cls.os_primary.migrations_client cls.security_group_default_rules_client = ( cls.os_primary.security_group_default_rules_client) cls.versions_client = cls.os_primary.compute_versions_client if CONF.service_available.cinder: cls.volumes_client = cls.os_primary.volumes_client_latest @classmethod def resource_setup(cls): super(BaseV2ComputeTest, cls).resource_setup() cls.request_microversion = ( api_version_utils.select_request_microversion( cls.min_microversion, CONF.compute.min_microversion)) cls.build_interval = CONF.compute.build_interval cls.build_timeout = CONF.compute.build_timeout cls.image_ref = CONF.compute.image_ref cls.image_ref_alt = CONF.compute.image_ref_alt cls.flavor_ref = CONF.compute.flavor_ref cls.flavor_ref_alt = CONF.compute.flavor_ref_alt cls.ssh_user = CONF.validation.image_ssh_user cls.image_ssh_user = CONF.validation.image_ssh_user cls.image_ssh_password = CONF.validation.image_ssh_password @classmethod def server_check_teardown(cls): """Checks is the shared server clean enough for subsequent test. Method will delete the server when it's dirty. The setUp method is responsible for creating a new server. Exceptions raised in tearDown class are fails the test case, This method supposed to use only by tearDown methods, when the shared server_id is stored in the server_id of the class. """ if getattr(cls, 'server_id', None) is not None: try: waiters.wait_for_server_status(cls.servers_client, cls.server_id, 'ACTIVE') except Exception as exc: LOG.exception(exc) cls.servers_client.delete_server(cls.server_id) waiters.wait_for_server_termination(cls.servers_client, cls.server_id) cls.server_id = None raise @classmethod def clear_resources(cls, resource_name, resources, resource_del_func): LOG.debug('Clearing %s: %s', resource_name, ','.join(map(str, resources))) for res_id in resources: try: test_utils.call_and_ignore_notfound_exc( resource_del_func, res_id) except Exception as exc: LOG.exception('Exception raised deleting %s: %s', resource_name, res_id) LOG.exception(exc) @classmethod def create_test_server(cls, validatable=False, volume_backed=False, validation_resources=None, **kwargs): """Wrapper utility that returns a test server. This wrapper utility calls the common create test server and returns a test server. The purpose of this wrapper is to minimize the impact on the code of the tests already using this function. :param validatable: Whether the server will be pingable or sshable. :param volume_backed: Whether the instance is volume backed or not. :param validation_resources: Dictionary of validation resources as returned by `get_class_validation_resources`. :param kwargs: Extra arguments are passed down to the `compute.create_test_server` call. """ if 'name' not in kwargs: kwargs['name'] = data_utils.rand_name(cls.__name__ + "-server") request_version = api_version_request.APIVersionRequest( cls.request_microversion) v2_37_version = api_version_request.APIVersionRequest('2.37') # NOTE(snikitin): since microversion v2.37 'networks' field is required if request_version >= v2_37_version and 'networks' not in kwargs: kwargs['networks'] = 'none' tenant_network = cls.get_tenant_network() body, servers = compute.create_test_server( cls.os_primary, validatable, validation_resources=validation_resources, tenant_network=tenant_network, volume_backed=volume_backed, **kwargs) # For each server schedule wait and delete, so we first delete all # and then wait for all for server in servers: cls.addClassResourceCleanup(waiters.wait_for_server_termination, cls.servers_client, server['id']) for server in servers: cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.servers_client.delete_server, server['id']) return body @classmethod def create_security_group(cls, name=None, description=None): if name is None: name = data_utils.rand_name(cls.__name__ + "-securitygroup") if description is None: description = data_utils.rand_name('description') body = cls.security_groups_client.create_security_group( name=name, description=description)['security_group'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.security_groups_client.delete_security_group, body['id']) return body @classmethod def create_test_server_group(cls, name="", policy=None): if not name: name = data_utils.rand_name(cls.__name__ + "-Server-Group") if policy is None: policy = ['affinity'] body = cls.server_groups_client.create_server_group( name=name, policies=policy)['server_group'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.server_groups_client.delete_server_group, body['id']) return body def wait_for(self, condition): """Repeatedly calls condition() until a timeout.""" start_time = int(time.time()) while True: try: condition() except Exception: pass else: return if int(time.time()) - start_time >= self.build_timeout: condition() return time.sleep(self.build_interval) @classmethod def prepare_instance_network(cls): if (CONF.validation.auth_method != 'disabled' and CONF.validation.connect_method == 'floating'): cls.set_network_resources(network=True, subnet=True, router=True, dhcp=True) @classmethod def create_image_from_server(cls, server_id, **kwargs): """Wrapper utility that returns an image created from the server.""" name = kwargs.pop('name', data_utils.rand_name(cls.__name__ + "-image")) wait_until = kwargs.pop('wait_until', None) wait_for_server = kwargs.pop('wait_for_server', True) image = cls.compute_images_client.create_image(server_id, name=name, **kwargs) if api_version_utils.compare_version_header_to_response( "OpenStack-API-Version", "compute 2.45", image.response, "lt"): image_id = image['image_id'] else: image_id = data_utils.parse_image_id(image.response['location']) cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.compute_images_client.delete_image, image_id) if wait_until is not None: try: waiters.wait_for_image_status(cls.compute_images_client, image_id, wait_until) except lib_exc.NotFound: if wait_until.upper() == 'ACTIVE': # If the image is not found after create_image returned # that means the snapshot failed in nova-compute and nova # deleted the image. There should be a compute fault # recorded with the server in that case, so get the server # and dump some details. server = ( cls.servers_client.show_server(server_id)['server']) if 'fault' in server: raise exceptions.SnapshotNotFoundException( server['fault'], image_id=image_id) else: raise exceptions.SnapshotNotFoundException( image_id=image_id) else: raise image = cls.compute_images_client.show_image(image_id)['image'] if wait_until.upper() == 'ACTIVE': if wait_for_server: waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE') return image @classmethod def recreate_server(cls, server_id, validatable=False, **kwargs): """Destroy an existing class level server and creates a new one Some test classes use a test server that can be used by multiple tests. This is done to optimise runtime and test load. If something goes wrong with the test server, it can be rebuilt using this helper. This helper can also be used for the initial provisioning if no server_id is specified. :param server_id: UUID of the server to be rebuilt. If None is specified, a new server is provisioned. :param validatable: whether to the server needs to be validatable. When True, validation resources are acquired via the `get_class_validation_resources` helper. :param kwargs: extra paramaters are passed through to the `create_test_server` call. :return: the UUID of the created server. """ if server_id: cls.delete_server(server_id) cls.password = data_utils.rand_password() server = cls.create_test_server( validatable, validation_resources=cls.get_class_validation_resources( cls.os_primary), wait_until='ACTIVE', adminPass=cls.password, **kwargs) return server['id'] @classmethod def delete_server(cls, server_id): """Deletes an existing server and waits for it to be gone.""" try: cls.servers_client.delete_server(server_id) waiters.wait_for_server_termination(cls.servers_client, server_id) except Exception: LOG.exception('Failed to delete server %s', server_id) @classmethod def resize_server(cls, server_id, new_flavor_id, **kwargs): """resize and confirm_resize an server, waits for it to be ACTIVE.""" cls.servers_client.resize_server(server_id, new_flavor_id, **kwargs) waiters.wait_for_server_status(cls.servers_client, server_id, 'VERIFY_RESIZE') cls.servers_client.confirm_resize_server(server_id) waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE') @classmethod def delete_volume(cls, volume_id): """Deletes the given volume and waits for it to be gone.""" try: cls.volumes_client.delete_volume(volume_id) # TODO(mriedem): We should move the wait_for_resource_deletion # into the delete_volume method as a convenience to the caller. cls.volumes_client.wait_for_resource_deletion(volume_id) except lib_exc.NotFound: LOG.warning("Unable to delete volume '%s' since it was not found. " "Maybe it was already deleted?", volume_id) @classmethod def get_server_ip(cls, server, validation_resources=None): """Get the server fixed or floating IP. Based on the configuration we're in, return a correct ip address for validating that a guest is up. :param server: The server dict as returned by the API :param validation_resources: The dict of validation resources provisioned for the server. """ if CONF.validation.connect_method == 'floating': if validation_resources: return validation_resources['floating_ip']['ip'] else: msg = ('When validation.connect_method equals floating, ' 'validation_resources cannot be None') raise exceptions.InvalidParam(invalid_param=msg) elif CONF.validation.connect_method == 'fixed': addresses = server['addresses'][CONF.validation.network_for_ssh] for address in addresses: if address['version'] == CONF.validation.ip_version_for_ssh: return address['addr'] raise exceptions.ServerUnreachable(server_id=server['id']) else: raise lib_exc.InvalidConfiguration() def setUp(self): super(BaseV2ComputeTest, self).setUp() self.useFixture(api_microversion_fixture.APIMicroversionFixture( self.request_microversion)) @classmethod def create_volume(cls, image_ref=None, **kwargs): """Create a volume and wait for it to become 'available'. :param image_ref: Specify an image id to create a bootable volume. :**kwargs: other parameters to create volume. :returns: The available volume. """ if 'size' not in kwargs: kwargs['size'] = CONF.volume.volume_size if 'display_name' not in kwargs: vol_name = data_utils.rand_name(cls.__name__ + '-volume') kwargs['display_name'] = vol_name if image_ref is not None: kwargs['imageRef'] = image_ref volume = cls.volumes_client.create_volume(**kwargs)['volume'] cls.addClassResourceCleanup( cls.volumes_client.wait_for_resource_deletion, volume['id']) cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc, cls.volumes_client.delete_volume, volume['id']) waiters.wait_for_volume_resource_status(cls.volumes_client, volume['id'], 'available') return volume def _detach_volume(self, server, volume): """Helper method to detach a volume. Ignores 404 responses if the volume or server do not exist, or the volume is already detached from the server. """ try: volume = self.volumes_client.show_volume(volume['id'])['volume'] # Check the status. You can only detach an in-use volume, otherwise # the compute API will return a 400 response. if volume['status'] == 'in-use': self.servers_client.detach_volume(server['id'], volume['id']) except exceptions.NotFound: # Ignore 404s on detach in case the server is deleted or the volume # is already detached. pass def attach_volume(self, server, volume, device=None, check_reserved=False): """Attaches volume to server and waits for 'in-use' volume status. The volume will be detached when the test tears down. :param server: The server to which the volume will be attached. :param volume: The volume to attach. :param device: Optional mountpoint for the attached volume. Note that this is not guaranteed for all hypervisors and is not recommended. :param check_reserved: Consider a status of reserved as valid for completion. This is to handle new Cinder attach where we more accurately use 'reserved' for things like attaching to a shelved server. """ attach_kwargs = dict(volumeId=volume['id']) if device: attach_kwargs['device'] = device attachment = self.servers_client.attach_volume( server['id'], **attach_kwargs)['volumeAttachment'] # On teardown detach the volume and wait for it to be available. This # is so we don't error out when trying to delete the volume during # teardown. self.addCleanup(waiters.wait_for_volume_resource_status, self.volumes_client, volume['id'], 'available') # Ignore 404s on detach in case the server is deleted or the volume # is already detached. self.addCleanup(self._detach_volume, server, volume) statuses = ['in-use'] if check_reserved: statuses.append('reserved') waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], statuses) return attachment class BaseV2ComputeAdminTest(BaseV2ComputeTest): """Base test case class for Compute Admin API tests.""" credentials = ['primary', 'admin'] @classmethod def setup_clients(cls): super(BaseV2ComputeAdminTest, cls).setup_clients() cls.availability_zone_admin_client = ( cls.os_admin.availability_zone_client) cls.admin_flavors_client = cls.os_admin.flavors_client cls.admin_servers_client = cls.os_admin.servers_client def create_flavor(self, ram, vcpus, disk, name=None, is_public='True', **kwargs): if name is None: name = data_utils.rand_name(self.__class__.__name__ + "-flavor") id = kwargs.pop('id', data_utils.rand_int_id(start=1000)) client = self.admin_flavors_client flavor = client.create_flavor( ram=ram, vcpus=vcpus, disk=disk, name=name, id=id, is_public=is_public, **kwargs)['flavor'] self.addCleanup(client.wait_for_resource_deletion, flavor['id']) self.addCleanup(client.delete_flavor, flavor['id']) return flavor def get_host_for_server(self, server_id): server_details = self.admin_servers_client.show_server(server_id) return server_details['server']['OS-EXT-SRV-ATTR:host'] def get_host_other_than(self, server_id): source_host = self.get_host_for_server(server_id) hypers = self.os_admin.hypervisor_client.list_hypervisors( )['hypervisors'] hosts = [hyper['hypervisor_hostname'] for hyper in hypers if hyper['state'] == 'up' and hyper['status'] == 'enabled'] for target_host in hosts: if source_host != target_host: return target_host tempest-17.2.0/tempest/api/compute/limits/0000775000175100017510000000000013207045130020510 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/limits/__init__.py0000666000175100017510000000000013207044712022616 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/limits/test_absolute_limits.py0000666000175100017510000000423513207044712025333 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.lib import decorators class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest): @classmethod def setup_clients(cls): super(AbsoluteLimitsTestJSON, cls).setup_clients() cls.client = cls.limits_client @decorators.idempotent_id('b54c66af-6ab6-4cf0-a9e5-a0cb58d75e0b') def test_absLimits_get(self): # To check if all limits are present in the response limits = self.client.show_limits()['limits'] absolute_limits = limits['absolute'] expected_elements = ['maxImageMeta', 'maxPersonality', 'maxPersonalitySize', 'maxServerMeta', 'maxTotalCores', 'maxTotalFloatingIps', 'maxSecurityGroups', 'maxSecurityGroupRules', 'maxTotalInstances', 'maxTotalKeypairs', 'maxTotalRAMSize', 'maxServerGroups', 'maxServerGroupMembers', 'totalCoresUsed', 'totalFloatingIpsUsed', 'totalSecurityGroupsUsed', 'totalInstancesUsed', 'totalRAMUsed', 'totalServerGroupsUsed'] # check whether all expected elements exist missing_elements =\ [ele for ele in expected_elements if ele not in absolute_limits] self.assertEmpty(missing_elements, "Failed to find element %s in absolute limits list" % ', '.join(ele for ele in missing_elements)) tempest-17.2.0/tempest/api/compute/limits/test_absolute_limits_negative.py0000666000175100017510000000430013207044712027206 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class AbsoluteLimitsNegativeTestJSON(base.BaseV2ComputeTest): def setUp(self): # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests. self.useFixture(fixtures.LockFixture('compute_quotas')) super(AbsoluteLimitsNegativeTestJSON, self).setUp() @classmethod def setup_clients(cls): super(AbsoluteLimitsNegativeTestJSON, cls).setup_clients() cls.client = cls.limits_client @decorators.attr(type=['negative']) @decorators.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8') def test_max_image_meta_exceed_limit(self): # We should not create vm with image meta over maxImageMeta limit # Get max limit value limits = self.client.show_limits()['limits'] max_meta = limits['absolute']['maxImageMeta'] # No point in running this test if there is no limit. if max_meta == -1: raise self.skipException('no limit for maxImageMeta') # Create server should fail, since we are passing > metadata Limit! max_meta_data = max_meta + 1 meta_data = {} for xx in range(max_meta_data): meta_data[str(xx)] = str(xx) # A 403 Forbidden or 413 Overlimit (old behaviour) exception # will be raised when out of quota self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit), self.create_test_server, metadata=meta_data) tempest-17.2.0/tempest/api/compute/volumes/0000775000175100017510000000000013207045130020701 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/volumes/test_volumes_get.py0000666000175100017510000000622513207044712024657 0ustar zuulzuul00000000000000# 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 testtools import matchers from tempest.api.compute import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumesGetTestJSON(base.BaseV2ComputeTest): # These tests will fail with a 404 starting from microversion 2.36. For # more information, see: # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated max_microversion = '2.35' @classmethod def skip_checks(cls): super(VolumesGetTestJSON, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(VolumesGetTestJSON, cls).setup_clients() cls.volumes_client = cls.volumes_extensions_client @decorators.idempotent_id('f10f25eb-9775-4d9d-9cbe-1cf54dae9d5f') def test_volume_create_get_delete(self): # CREATE, GET, DELETE Volume v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') metadata = {'Type': 'work'} # Create volume volume = self.create_volume(size=CONF.volume.volume_size, display_name=v_name, metadata=metadata) self.assertEqual(volume['displayName'], v_name, "The created volume name is not equal " "to the requested name") # GET Volume fetched_volume = self.volumes_client.show_volume( volume['id'])['volume'] # Verification of details of fetched Volume self.assertEqual(v_name, fetched_volume['displayName'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(CONF.volume.volume_size, fetched_volume['size'], 'The fetched volume size is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') self.assertThat(fetched_volume['metadata'].items(), matchers.ContainsAll(metadata.items()), 'The fetched Volume metadata misses data ' 'from the created Volume') tempest-17.2.0/tempest/api/compute/volumes/test_attach_volume_negative.py0000666000175100017510000000317613207044712027045 0ustar zuulzuul00000000000000# 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.api.compute import base from tempest import config from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class AttachVolumeNegativeTest(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(AttachVolumeNegativeTest, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @decorators.attr(type=['negative']) @decorators.related_bug('1630783', status_code=500) @decorators.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7') def test_delete_attached_volume(self): server = self.create_test_server(wait_until='ACTIVE') volume = self.create_volume() path = "/dev/%s" % CONF.compute.volume_device_name self.attach_volume(server, volume, device=path) self.assertRaises(lib_exc.BadRequest, self.delete_volume, volume['id']) tempest-17.2.0/tempest/api/compute/volumes/__init__.py0000666000175100017510000000000013207044712023007 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/volumes/test_volumes_negative.py0000666000175100017510000001166413207044712025705 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class VolumesNegativeTest(base.BaseV2ComputeTest): # These tests will fail with a 404 starting from microversion 2.36. For # more information, see: # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated max_microversion = '2.35' @classmethod def skip_checks(cls): super(VolumesNegativeTest, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(VolumesNegativeTest, cls).setup_clients() cls.client = cls.volumes_extensions_client @decorators.attr(type=['negative']) @decorators.idempotent_id('c03ea686-905b-41a2-8748-9635154b7c57') def test_volume_get_nonexistent_volume_id(self): # Negative: Should not be able to get details of nonexistent volume # Creating a nonexistent volume id # Trying to GET a non existent volume self.assertRaises(lib_exc.NotFound, self.client.show_volume, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('54a34226-d910-4b00-9ef8-8683e6c55846') def test_volume_delete_nonexistent_volume_id(self): # Negative: Should not be able to delete nonexistent Volume # Creating nonexistent volume id # Trying to DELETE a non existent volume self.assertRaises(lib_exc.NotFound, self.client.delete_volume, data_utils.rand_uuid()) @decorators.attr(type=['negative']) @decorators.idempotent_id('5125ae14-152b-40a7-b3c5-eae15e9022ef') def test_create_volume_with_invalid_size(self): # Negative: Should not be able to create volume with invalid size # in request v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') metadata = {'Type': 'work'} self.assertRaises(lib_exc.BadRequest, self.client.create_volume, size='#$%', display_name=v_name, metadata=metadata) @decorators.attr(type=['negative']) @decorators.idempotent_id('131cb3a1-75cc-4d40-b4c3-1317f64719b0') def test_create_volume_without_passing_size(self): # Negative: Should not be able to create volume without passing size # in request v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') metadata = {'Type': 'work'} self.assertRaises(lib_exc.BadRequest, self.client.create_volume, size='', display_name=v_name, metadata=metadata) @decorators.attr(type=['negative']) @decorators.idempotent_id('8cce995e-0a83-479a-b94d-e1e40b8a09d1') def test_create_volume_with_size_zero(self): # Negative: Should not be able to create volume with size zero v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume') metadata = {'Type': 'work'} self.assertRaises(lib_exc.BadRequest, self.client.create_volume, size='0', display_name=v_name, metadata=metadata) @decorators.attr(type=['negative']) @decorators.idempotent_id('62bab09a-4c03-4617-8cca-8572bc94af9b') def test_get_volume_without_passing_volume_id(self): # Negative: Should not be able to get volume when empty ID is passed self.assertRaises(lib_exc.NotFound, self.client.show_volume, '') @decorators.attr(type=['negative']) @decorators.idempotent_id('62972737-124b-4513-b6cf-2f019f178494') def test_delete_invalid_volume_id(self): # Negative: Should not be able to delete volume when invalid ID is # passed self.assertRaises(lib_exc.NotFound, self.client.delete_volume, data_utils.rand_name('invalid')) @decorators.attr(type=['negative']) @decorators.idempotent_id('0d1417c5-4ae8-4c2c-adc5-5f0b864253e5') def test_delete_volume_without_passing_volume_id(self): # Negative: Should not be able to delete volume when empty ID is passed self.assertRaises(lib_exc.NotFound, self.client.delete_volume, '') tempest-17.2.0/tempest/api/compute/volumes/test_volume_snapshots.py0000666000175100017510000000626413207044712025742 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import waiters from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators CONF = config.CONF class VolumesSnapshotsTestJSON(base.BaseV2ComputeTest): # These tests will fail with a 404 starting from microversion 2.36. For # more information, see: # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated max_microversion = '2.35' @classmethod def skip_checks(cls): super(VolumesSnapshotsTestJSON, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) if not CONF.volume_feature_enabled.snapshot: skip_msg = ("Cinder volume snapshots are disabled") raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(VolumesSnapshotsTestJSON, cls).setup_clients() cls.volumes_client = cls.volumes_extensions_client cls.snapshots_client = cls.snapshots_extensions_client @decorators.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f') def test_volume_snapshot_create_get_list_delete(self): volume = self.create_volume() self.addCleanup(self.delete_volume, volume['id']) s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot') # Create snapshot snapshot = self.snapshots_client.create_snapshot( volume_id=volume['id'], display_name=s_name)['snapshot'] def delete_snapshot(snapshot_id): waiters.wait_for_volume_resource_status(self.snapshots_client, snapshot_id, 'available') # Delete snapshot self.snapshots_client.delete_snapshot(snapshot_id) self.snapshots_client.wait_for_resource_deletion(snapshot_id) self.addCleanup(delete_snapshot, snapshot['id']) self.assertEqual(volume['id'], snapshot['volumeId']) # Get snapshot fetched_snapshot = self.snapshots_client.show_snapshot( snapshot['id'])['snapshot'] self.assertEqual(s_name, fetched_snapshot['displayName']) self.assertEqual(volume['id'], fetched_snapshot['volumeId']) # Fetch all snapshots snapshots = self.snapshots_client.list_snapshots()['snapshots'] self.assertIn(snapshot['id'], map(lambda x: x['id'], snapshots)) tempest-17.2.0/tempest/api/compute/volumes/test_attach_volume.py0000666000175100017510000002667413207044712025173 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import compute from tempest.common.utils.linux import remote_client from tempest.common import waiters from tempest import config from tempest.lib import decorators CONF = config.CONF class AttachVolumeTestJSON(base.BaseV2ComputeTest): max_microversion = '2.19' @classmethod def skip_checks(cls): super(AttachVolumeTestJSON, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_credentials(cls): cls.prepare_instance_network() super(AttachVolumeTestJSON, cls).setup_credentials() @classmethod def resource_setup(cls): super(AttachVolumeTestJSON, cls).resource_setup() cls.device = CONF.compute.volume_device_name def _create_server(self): # Start a server and wait for it to become ready validation_resources = self.get_test_validation_resources( self.os_primary) server = self.create_test_server( validatable=True, validation_resources=validation_resources, wait_until='ACTIVE', adminPass=self.image_ssh_password) self.addCleanup(self.delete_server, server['id']) # Record addresses so that we can ssh later server['addresses'] = self.servers_client.list_addresses( server['id'])['addresses'] return server, validation_resources @decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff') def test_attach_detach_volume(self): # Stop and Start a server with an attached volume, ensuring that # the volume remains attached. server, validation_resources = self._create_server() # NOTE(andreaf) Create one remote client used throughout the test. if CONF.validation.run_validation: linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.image_ssh_user, self.image_ssh_password, validation_resources['keypair']['private_key'], server=server, servers_client=self.servers_client) # NOTE(andreaf) We need to ensure the ssh key has been # injected in the guest before we power cycle linux_client.validate_authentication() volume = self.create_volume() attachment = self.attach_volume(server, volume, device=('/dev/%s' % self.device)) self.servers_client.stop_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SHUTOFF') self.servers_client.start_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') if CONF.validation.run_validation: disks = linux_client.get_disks() device_name_to_match = '\n' + self.device + ' ' self.assertIn(device_name_to_match, disks) self.servers_client.detach_volume(server['id'], attachment['volumeId']) waiters.wait_for_volume_resource_status( self.volumes_client, attachment['volumeId'], 'available') self.servers_client.stop_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'SHUTOFF') self.servers_client.start_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') if CONF.validation.run_validation: disks = linux_client.get_disks() self.assertNotIn(device_name_to_match, disks) @decorators.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513') def test_list_get_volume_attachments(self): # List volume attachment of the server server, _ = self._create_server() volume_1st = self.create_volume() attachment_1st = self.attach_volume(server, volume_1st, device=('/dev/%s' % self.device)) body = self.servers_client.list_volume_attachments( server['id'])['volumeAttachments'] self.assertEqual(1, len(body)) self.assertIn(attachment_1st, body) # Get volume attachment of the server body = self.servers_client.show_volume_attachment( server['id'], attachment_1st['id'])['volumeAttachment'] self.assertEqual(server['id'], body['serverId']) self.assertEqual(volume_1st['id'], body['volumeId']) self.assertEqual(attachment_1st['id'], body['id']) # attach one more volume to server volume_2nd = self.create_volume() attachment_2nd = self.attach_volume(server, volume_2nd) body = self.servers_client.list_volume_attachments( server['id'])['volumeAttachments'] self.assertEqual(2, len(body)) for attachment in [attachment_1st, attachment_2nd]: body = self.servers_client.show_volume_attachment( server['id'], attachment['id'])['volumeAttachment'] self.assertEqual(server['id'], body['serverId']) self.assertEqual(attachment['volumeId'], body['volumeId']) self.assertEqual(attachment['id'], body['id']) self.servers_client.detach_volume(server['id'], attachment['volumeId']) waiters.wait_for_volume_resource_status( self.volumes_client, attachment['volumeId'], 'available') class AttachVolumeShelveTestJSON(AttachVolumeTestJSON): """Testing volume with shelved instance. This test checks the attaching and detaching volumes from a shelved or shelved offload instance. """ min_microversion = '2.20' max_microversion = 'latest' @classmethod def skip_checks(cls): super(AttachVolumeShelveTestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.shelve: raise cls.skipException('Shelve is not available.') def _count_volumes(self, server, validation_resources): # Count number of volumes on an instance volumes = 0 if CONF.validation.run_validation: linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.image_ssh_user, self.image_ssh_password, validation_resources['keypair']['private_key'], server=server, servers_client=self.servers_client) command = 'grep -c -E [vs]d.$ /proc/partitions' volumes = int(linux_client.exec_command(command).strip()) return volumes def _shelve_server(self, server, validation_resources): # NOTE(andreaf) If we are going to shelve a server, we should # check first whether the server is ssh-able. Otherwise we # won't be able to distinguish failures introduced by shelve # from pre-existing ones. Also it's good to wait for cloud-init # to be done and sshd server to be running before shelving to # avoid breaking the VM if CONF.validation.run_validation: linux_client = remote_client.RemoteClient( self.get_server_ip(server, validation_resources), self.image_ssh_user, self.image_ssh_password, validation_resources['keypair']['private_key'], server=server, servers_client=self.servers_client) linux_client.validate_authentication() # If validation went ok, or it was skipped, shelve the server compute.shelve_server(self.servers_client, server['id']) def _unshelve_server_and_check_volumes(self, server, validation_resources, number_of_volumes): # Unshelve the instance and check that there are expected volumes self.servers_client.unshelve_server(server['id']) waiters.wait_for_server_status(self.servers_client, server['id'], 'ACTIVE') if CONF.validation.run_validation: counted_volumes = self._count_volumes( server, validation_resources) self.assertEqual(number_of_volumes, counted_volumes) @decorators.idempotent_id('13a940b6-3474-4c3c-b03f-29b89112bfee') def test_attach_volume_shelved_or_offload_server(self): # Create server, count number of volumes on it, shelve # server and attach pre-created volume to shelved server server, validation_resources = self._create_server() volume = self.create_volume() num_vol = self._count_volumes(server, validation_resources) self._shelve_server(server, validation_resources) attachment = self.attach_volume(server, volume, device=('/dev/%s' % self.device), check_reserved=True) # Unshelve the instance and check that attached volume exists self._unshelve_server_and_check_volumes( server, validation_resources, num_vol + 1) # Get volume attachment of the server volume_attachment = self.servers_client.show_volume_attachment( server['id'], attachment['id'])['volumeAttachment'] self.assertEqual(server['id'], volume_attachment['serverId']) self.assertEqual(attachment['id'], volume_attachment['id']) # Check the mountpoint is not None after unshelve server even in # case of shelved_offloaded. self.assertIsNotNone(volume_attachment['device']) @decorators.idempotent_id('b54e86dd-a070-49c4-9c07-59ae6dae15aa') def test_detach_volume_shelved_or_offload_server(self): # Count number of volumes on instance, shelve # server and attach pre-created volume to shelved server server, validation_resources = self._create_server() volume = self.create_volume() num_vol = self._count_volumes(server, validation_resources) self._shelve_server(server, validation_resources) # Attach and then detach the volume self.attach_volume(server, volume, device=('/dev/%s' % self.device), check_reserved=True) self.servers_client.detach_volume(server['id'], volume['id']) waiters.wait_for_volume_resource_status(self.volumes_client, volume['id'], 'available') # Unshelve the instance and check that we have the expected number of # volume(s) self._unshelve_server_and_check_volumes( server, validation_resources, num_vol) tempest-17.2.0/tempest/api/compute/volumes/test_volumes_list.py0000666000175100017510000001427113207044712025053 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest import config from tempest.lib import decorators CONF = config.CONF class VolumesTestJSON(base.BaseV2ComputeTest): # NOTE: This test creates a number of 1G volumes. To run successfully, # ensure that the backing file for the volume group that Nova uses # has space for at least 3 1G volumes! # If you are running a Devstack environment, ensure that the # VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc # These tests will fail with a 404 starting from microversion 2.36. For # more information, see: # https://developer.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated max_microversion = '2.35' @classmethod def skip_checks(cls): super(VolumesTestJSON, cls).skip_checks() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @classmethod def setup_clients(cls): super(VolumesTestJSON, cls).setup_clients() cls.client = cls.volumes_extensions_client @classmethod def resource_setup(cls): super(VolumesTestJSON, cls).resource_setup() # Create 3 Volumes cls.volume_list = [] for _ in range(3): metadata = {'Type': 'work'} volume = cls.create_volume(metadata=metadata) volume = cls.client.show_volume(volume['id'])['volume'] cls.volume_list.append(volume) @decorators.idempotent_id('bc2dd1a0-15af-48e5-9990-f2e75a48325d') def test_volume_list(self): # Should return the list of Volumes # Fetch all Volumes fetched_list = self.client.list_volumes()['volumes'] # Now check if all the Volumes created in setup are in fetched list missing_volumes = [ v for v in self.volume_list if v not in fetched_list ] self.assertFalse(missing_volumes, "Failed to find volume %s in fetched list" % ', '.join(m_vol['displayName'] for m_vol in missing_volumes)) @decorators.idempotent_id('bad0567a-5a4f-420b-851e-780b55bb867c') def test_volume_list_with_details(self): # Should return the list of Volumes with details # Fetch all Volumes fetched_list = self.client.list_volumes(detail=True)['volumes'] # Now check if all the Volumes created in setup are in fetched list missing_volumes = [ v for v in self.volume_list if v not in fetched_list ] self.assertFalse(missing_volumes, "Failed to find volume %s in fetched list" % ', '.join(m_vol['displayName'] for m_vol in missing_volumes)) @decorators.idempotent_id('1048ed81-2baf-487a-b284-c0622b86e7b8') def test_volume_list_param_limit(self): # Return the list of volumes based on limit set params = {'limit': 2} fetched_vol_list = self.client.list_volumes(**params)['volumes'] self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volumes by limit set") @decorators.idempotent_id('33985568-4965-49d5-9bcc-0aa007ca5b7a') def test_volume_list_with_detail_param_limit(self): # Return the list of volumes with details based on limit set. params = {'limit': 2} fetched_vol_list = self.client.list_volumes(detail=True, **params)['volumes'] self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volume details by limit set") @decorators.idempotent_id('51c22651-a074-4ea7-af0b-094f9331303e') def test_volume_list_param_offset_and_limit(self): # Return the list of volumes based on offset and limit set. # get all volumes list all_vol_list = self.client.list_volumes()['volumes'] params = {'offset': 1, 'limit': 1} fetched_vol_list = self.client.list_volumes(**params)['volumes'] # Validating length of the fetched volumes self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volumes by offset and limit") # Validating offset of fetched volume for index, volume in enumerate(fetched_vol_list): self.assertEqual(volume['id'], all_vol_list[index + params['offset']]['id'], "Failed to list volumes by offset and limit") @decorators.idempotent_id('06b6abc4-3f10-48e9-a7a1-3facc98f03e5') def test_volume_list_with_detail_param_offset_and_limit(self): # Return the list of volumes details based on offset and limit set. # get all volumes list all_vol_list = self.client.list_volumes(detail=True)['volumes'] params = {'offset': 1, 'limit': 1} fetched_vol_list = self.client.list_volumes(detail=True, **params)['volumes'] # Validating length of the fetched volumes self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volume details by offset and limit") # Validating offset of fetched volume for index, volume in enumerate(fetched_vol_list): self.assertEqual(volume['id'], all_vol_list[index + params['offset']]['id'], "Failed to list volume details by " "offset and limit") tempest-17.2.0/tempest/api/compute/test_networks.py0000666000175100017510000000253213207044712022505 0ustar zuulzuul00000000000000# Copyright 2014 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.api.compute import base from tempest import config from tempest.lib import decorators CONF = config.CONF class ComputeNetworksTest(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(ComputeNetworksTest, cls).skip_checks() if CONF.service_available.neutron: raise cls.skipException('nova-network is not available.') @classmethod def setup_clients(cls): super(ComputeNetworksTest, cls).setup_clients() cls.client = cls.os_primary.compute_networks_client @decorators.idempotent_id('3fe07175-312e-49a5-a623-5f52eeada4c2') def test_list_networks(self): networks = self.client.list_networks()['networks'] self.assertNotEmpty(networks, "No networks found.") tempest-17.2.0/tempest/api/compute/certificates/0000775000175100017510000000000013207045130021654 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/certificates/test_certificates.py0000666000175100017510000000263513207044712025747 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest import config from tempest.lib import decorators CONF = config.CONF class CertificatesV2TestJSON(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(CertificatesV2TestJSON, cls).skip_checks() if not CONF.compute_feature_enabled.nova_cert: raise cls.skipException("Nova cert is not available") @decorators.idempotent_id('c070a441-b08e-447e-a733-905909535b1b') def test_create_root_certificate(self): # create certificates self.certificates_client.create_certificate() @decorators.idempotent_id('3ac273d0-92d2-4632-bdfc-afbc21d4606c') def test_get_root_certificate(self): # get the root certificate self.certificates_client.show_certificate('root') tempest-17.2.0/tempest/api/compute/certificates/__init__.py0000666000175100017510000000000013207044712023762 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/security_groups/0000775000175100017510000000000013207045130022455 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/security_groups/test_security_groups.py0000666000175100017510000001760313207044712027352 0ustar zuulzuul00000000000000# 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 tempest.api.compute.security_groups import base from tempest.common import waiters from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class SecurityGroupsTestJSON(base.BaseSecurityGroupsTest): @classmethod def setup_clients(cls): super(SecurityGroupsTestJSON, cls).setup_clients() cls.client = cls.security_groups_client @decorators.attr(type='smoke') @decorators.idempotent_id('eb2b087d-633d-4d0d-a7bd-9e6ba35b32de') def test_security_groups_create_list_delete(self): # Positive test:Should return the list of Security Groups # Create 3 Security Groups security_group_list = [] for _ in range(3): body = self.create_security_group() security_group_list.append(body) # Fetch all Security Groups and verify the list # has all created Security Groups fetched_list = self.client.list_security_groups()['security_groups'] # Now check if all the created Security Groups are in fetched list missing_sgs = \ [sg for sg in security_group_list if sg not in fetched_list] self.assertFalse(missing_sgs, "Failed to find Security Group %s in fetched " "list" % ', '.join(m_group['name'] for m_group in missing_sgs)) # Delete all security groups for sg in security_group_list: self.client.delete_security_group(sg['id']) self.client.wait_for_resource_deletion(sg['id']) # Now check if all the created Security Groups are deleted fetched_list = self.client.list_security_groups()['security_groups'] deleted_sgs = [sg for sg in security_group_list if sg in fetched_list] self.assertFalse(deleted_sgs, "Failed to delete Security Group %s " "list" % ', '.join(m_group['name'] for m_group in deleted_sgs)) @decorators.idempotent_id('ecc0da4a-2117-48af-91af-993cca39a615') def test_security_group_create_get_delete(self): # Security Group should be created, fetched and deleted # with char space between name along with # leading and trailing spaces s_name = ' %s ' % data_utils.rand_name('securitygroup ') securitygroup = self.create_security_group(name=s_name) securitygroup_name = securitygroup['name'] self.assertEqual(securitygroup_name, s_name, "The created Security Group name is " "not equal to the requested name") # Now fetch the created Security Group by its 'id' fetched_group = (self.client.show_security_group(securitygroup['id']) ['security_group']) self.assertEqual(securitygroup, fetched_group, "The fetched Security Group is different " "from the created Group") self.client.delete_security_group(securitygroup['id']) self.client.wait_for_resource_deletion(securitygroup['id']) @decorators.idempotent_id('fe4abc0d-83f5-4c50-ad11-57a1127297a2') def test_server_security_groups(self): # Checks that security groups may be added and linked to a server # and not deleted if the server is active. # Create a couple security groups that we will use # for the server resource this test creates sg = self.create_security_group() sg2 = self.create_security_group() # Create server and add the security group created # above to the server we just created server = self.create_test_server(wait_until='ACTIVE') server_id = server['id'] self.servers_client.add_security_group(server_id, name=sg['name']) # Check that we are not able to delete the security # group since it is in use by an active server self.assertRaises(lib_exc.BadRequest, self.client.delete_security_group, sg['id']) # Reboot and add the other security group self.servers_client.reboot_server(server_id, type='HARD') waiters.wait_for_server_status(self.servers_client, server_id, 'ACTIVE') self.servers_client.add_security_group(server_id, name=sg2['name']) # Check that we are not able to delete the other security # group since it is in use by an active server self.assertRaises(lib_exc.BadRequest, self.client.delete_security_group, sg2['id']) # Shutdown the server and then verify we can destroy the # security groups, since no active server instance is using them self.servers_client.delete_server(server_id) waiters.wait_for_server_termination(self.servers_client, server_id) self.client.delete_security_group(sg['id']) self.client.delete_security_group(sg2['id']) @decorators.idempotent_id('7d4e1d3c-3209-4d6d-b020-986304ebad1f') def test_update_security_groups(self): # Update security group name and description # Create a security group securitygroup = self.create_security_group() securitygroup_id = securitygroup['id'] # Update the name and description s_new_name = data_utils.rand_name('sg-hth') s_new_des = data_utils.rand_name('description-hth') self.client.update_security_group(securitygroup_id, name=s_new_name, description=s_new_des) # get the security group fetched_group = (self.client.show_security_group(securitygroup_id) ['security_group']) self.assertEqual(s_new_name, fetched_group['name']) self.assertEqual(s_new_des, fetched_group['description']) @decorators.idempotent_id('79517d60-535a-438f-af3d-e6feab1cbea7') def test_list_security_groups_by_server(self): # Create a couple security groups that we will use # for the server resource this test creates sg = self.create_security_group() sg2 = self.create_security_group() assigned_security_groups_ids = [sg['id'], sg2['id']] # Create server and add the security group created # above to the server we just created server_id = self.create_test_server(wait_until='ACTIVE')['id'] # add security groups to server self.servers_client.add_security_group(server_id, name=sg['name']) self.servers_client.add_security_group(server_id, name=sg2['name']) # list security groups for a server fetched_groups = ( self.servers_client.list_security_groups_by_server( server_id)['security_groups']) fetched_security_groups_ids = [i['id'] for i in fetched_groups] # verifying the security groups ids in list missing_security_groups =\ [p for p in assigned_security_groups_ids if p not in fetched_security_groups_ids] self.assertEmpty(missing_security_groups, "Failed to find security_groups %s in fetched list" % ', '.join(missing_security_groups)) tempest-17.2.0/tempest/api/compute/security_groups/test_security_group_rules.py0000666000175100017510000001655213207044712030403 0ustar zuulzuul00000000000000# 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 tempest.api.compute.security_groups import base from tempest.lib import decorators class SecurityGroupRulesTestJSON(base.BaseSecurityGroupsTest): @classmethod def setup_clients(cls): super(SecurityGroupRulesTestJSON, cls).setup_clients() cls.client = cls.security_group_rules_client @classmethod def resource_setup(cls): super(SecurityGroupRulesTestJSON, cls).resource_setup() cls.ip_protocol = 'tcp' cls.from_port = 22 cls.to_port = 22 def setUp(cls): super(SecurityGroupRulesTestJSON, cls).setUp() from_port = cls.from_port to_port = cls.to_port group = {} ip_range = {} cls.expected = { 'parent_group_id': None, 'ip_protocol': cls.ip_protocol, 'from_port': from_port, 'to_port': to_port, 'ip_range': ip_range, 'group': group } def _check_expected_response(self, actual_rule): for key in self.expected: self.assertEqual(self.expected[key], actual_rule[key], "Miss-matched key is %s" % key) @decorators.attr(type='smoke') @decorators.idempotent_id('850795d7-d4d3-4e55-b527-a774c0123d3a') def test_security_group_rules_create(self): # Positive test: Creation of Security Group rule # should be successful # Creating a Security Group to add rules to it security_group = self.create_security_group() securitygroup_id = security_group['id'] # Adding rules to the created Security Group rule = self.client.create_security_group_rule( parent_group_id=securitygroup_id, ip_protocol=self.ip_protocol, from_port=self.from_port, to_port=self.to_port)['security_group_rule'] self.expected['parent_group_id'] = securitygroup_id self.expected['ip_range'] = {'cidr': '0.0.0.0/0'} self._check_expected_response(rule) @decorators.idempotent_id('7a01873e-3c38-4f30-80be-31a043cfe2fd') def test_security_group_rules_create_with_optional_cidr(self): # Positive test: Creation of Security Group rule # with optional argument cidr # should be successful # Creating a Security Group to add rules to it security_group = self.create_security_group() parent_group_id = security_group['id'] # Adding rules to the created Security Group with optional cidr cidr = '10.2.3.124/24' rule = self.client.create_security_group_rule( parent_group_id=parent_group_id, ip_protocol=self.ip_protocol, from_port=self.from_port, to_port=self.to_port, cidr=cidr)['security_group_rule'] self.expected['parent_group_id'] = parent_group_id self.expected['ip_range'] = {'cidr': cidr} self._check_expected_response(rule) @decorators.idempotent_id('7f5d2899-7705-4d4b-8458-4505188ffab6') def test_security_group_rules_create_with_optional_group_id(self): # Positive test: Creation of Security Group rule # with optional argument group_id # should be successful # Creating a Security Group to add rules to it security_group = self.create_security_group() parent_group_id = security_group['id'] # Creating a Security Group so as to assign group_id to the rule security_group = self.create_security_group() group_id = security_group['id'] group_name = security_group['name'] # Adding rules to the created Security Group with optional group_id rule = self.client.create_security_group_rule( parent_group_id=parent_group_id, ip_protocol=self.ip_protocol, from_port=self.from_port, to_port=self.to_port, group_id=group_id)['security_group_rule'] self.expected['parent_group_id'] = parent_group_id self.expected['group'] = {'tenant_id': self.client.tenant_id, 'name': group_name} self._check_expected_response(rule) @decorators.attr(type='smoke') @decorators.idempotent_id('a6154130-5a55-4850-8be4-5e9e796dbf17') def test_security_group_rules_list(self): # Positive test: Created Security Group rules should be # in the list of all rules # Creating a Security Group to add rules to it security_group = self.create_security_group() securitygroup_id = security_group['id'] # Add a first rule to the created Security Group rule = self.client.create_security_group_rule( parent_group_id=securitygroup_id, ip_protocol=self.ip_protocol, from_port=self.from_port, to_port=self.to_port)['security_group_rule'] rule1_id = rule['id'] # Add a second rule to the created Security Group ip_protocol2 = 'icmp' from_port2 = -1 to_port2 = -1 rule = self.client.create_security_group_rule( parent_group_id=securitygroup_id, ip_protocol=ip_protocol2, from_port=from_port2, to_port=to_port2)['security_group_rule'] rule2_id = rule['id'] # Delete the Security Group rule2 at the end of this method self.addCleanup( self.security_group_rules_client.delete_security_group_rule, rule2_id) # Get rules of the created Security Group rules = self.security_groups_client.show_security_group( securitygroup_id)['security_group']['rules'] self.assertNotEmpty([i for i in rules if i['id'] == rule1_id]) self.assertNotEmpty([i for i in rules if i['id'] == rule2_id]) @decorators.idempotent_id('fc5c5acf-2091-43a6-a6ae-e42760e9ffaf') def test_security_group_rules_delete_when_peer_group_deleted(self): # Positive test:rule will delete when peer group deleting # Creating a Security Group to add rules to it security_group = self.create_security_group() sg1_id = security_group['id'] # Creating other Security Group to access to group1 security_group = self.create_security_group() sg2_id = security_group['id'] # Adding rules to the Group1 self.client.create_security_group_rule( parent_group_id=sg1_id, ip_protocol=self.ip_protocol, from_port=self.from_port, to_port=self.to_port, group_id=sg2_id) # Delete group2 self.security_groups_client.delete_security_group(sg2_id) # Get rules of the Group1 rules = (self.security_groups_client.show_security_group(sg1_id) ['security_group']['rules']) # The group1 has no rules because group2 has deleted self.assertEmpty(rules) tempest-17.2.0/tempest/api/compute/security_groups/__init__.py0000666000175100017510000000000013207044712024563 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/api/compute/security_groups/test_security_groups_negative.py0000666000175100017510000002124313207044712031227 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute.security_groups import base from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF class SecurityGroupsNegativeTestJSON(base.BaseSecurityGroupsTest): @classmethod def setup_clients(cls): super(SecurityGroupsNegativeTestJSON, cls).setup_clients() cls.client = cls.security_groups_client @decorators.attr(type=['negative']) @decorators.idempotent_id('673eaec1-9b3e-48ed-bdf1-2786c1b9661c') def test_security_group_get_nonexistent_group(self): # Negative test:Should not be able to GET the details # of non-existent Security Group non_exist_id = self.generate_random_security_group_id() self.assertRaises(lib_exc.NotFound, self.client.show_security_group, non_exist_id) @decorators.skip_because(bug="1161411", condition=CONF.service_available.neutron) @decorators.attr(type=['negative']) @decorators.idempotent_id('1759c3cb-b0fc-44b7-86ce-c99236be911d') def test_security_group_create_with_invalid_group_name(self): # Negative test: Security Group should not be created with group name # as an empty string/with white spaces/chars more than 255 s_description = data_utils.rand_name('description') # Create Security Group with empty string as group name self.assertRaises(lib_exc.BadRequest, self.client.create_security_group, name="", description=s_description) # Create Security Group with white space in group name self.assertRaises(lib_exc.BadRequest, self.client.create_security_group, name=" ", description=s_description) # Create Security Group with group name longer than 255 chars s_name = 'securitygroup-'.ljust(260, '0') self.assertRaises(lib_exc.BadRequest, self.client.create_security_group, name=s_name, description=s_description) @decorators.skip_because(bug="1161411", condition=CONF.service_available.neutron) @decorators.attr(type=['negative']) @decorators.idempotent_id('777b6f14-aca9-4758-9e84-38783cfa58bc') def test_security_group_create_with_invalid_group_description(self): # Negative test: Security Group should not be created with description # longer than 255 chars. Empty description is allowed by the API # reference, however. s_name = data_utils.rand_name('securitygroup') # Create Security Group with group description longer than 255 chars s_description = 'description-'.ljust(260, '0') self.assertRaises(lib_exc.BadRequest, self.client.create_security_group, name=s_name, description=s_description) @decorators.idempotent_id('9fdb4abc-6b66-4b27-b89c-eb215a956168') @testtools.skipIf(CONF.service_available.neutron, "Neutron allows duplicate names for security groups") @decorators.attr(type=['negative']) def test_security_group_create_with_duplicate_name(self): # Negative test:Security Group with duplicate name should not # be created s_name = data_utils.rand_name('securitygroup') s_description = data_utils.rand_name('description') self.create_security_group(name=s_name, description=s_description) # Now try the Security Group with the same 'Name' self.assertRaises(lib_exc.BadRequest, self.client.create_security_group, name=s_name, description=s_description) @decorators.attr(type=['negative']) @decorators.idempotent_id('36a1629f-c6da-4a26-b8b8-55e7e5d5cd58') def test_delete_the_default_security_group(self): # Negative test:Deletion of the "default" Security Group should Fail default_security_group_id = None body = self.client.list_security_groups()['security_groups'] for i in range(len(body)): if body[i]['name'] == 'default': default_security_group_id = body[i]['id'] break # Deleting the "default" Security Group self.assertRaises(lib_exc.BadRequest, self.client.delete_security_group, default_security_group_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('6727c00b-214c-4f9e-9a52-017ac3e98411') def test_delete_nonexistent_security_group(self): # Negative test:Deletion of a non-existent Security Group should fail non_exist_id = self.generate_random_security_group_id() self.assertRaises(lib_exc.NotFound, self.client.delete_security_group, non_exist_id) @decorators.attr(type=['negative']) @decorators.idempotent_id('1438f330-8fa4-4aeb-8a94-37c250106d7f') def test_delete_security_group_without_passing_id(self): # Negative test:Deletion of a Security Group with out passing ID # should Fail self.assertRaises(lib_exc.NotFound, self.client.delete_security_group, '') @decorators.idempotent_id('00579617-fe04-4e1c-9d08-ca7467d2e34b') @testtools.skipIf(CONF.service_available.neutron, "Neutron does not check the security group ID") @decorators.attr(type=['negative']) def test_update_security_group_with_invalid_sg_id(self): # Update security_group with invalid sg_id should fail s_name = data_utils.rand_name('sg') s_description = data_utils.rand_name('description') # Create a non int sg_id sg_id_invalid = data_utils.rand_name('sg') self.assertRaises(lib_exc.BadRequest, self.client.update_security_group, sg_id_invalid, name=s_name, description=s_description) @decorators.idempotent_id('cda8d8b4-59f8-4087-821d-20cf5a03b3b1') @testtools.skipIf(CONF.service_available.neutron, "Neutron does not check the security group name") @decorators.attr(type=['negative']) def test_update_security_group_with_invalid_sg_name(self): # Update security_group with invalid sg_name should fail securitygroup = self.create_security_group() securitygroup_id = securitygroup['id'] # Update Security Group with group name longer than 255 chars s_new_name = 'securitygroup-'.ljust(260, '0') self.assertRaises(lib_exc.BadRequest, self.client.update_security_group, securitygroup_id, name=s_new_name) @decorators.idempotent_id('97d12b1c-a610-4194-93f1-ba859e718b45') @testtools.skipIf(CONF.service_available.neutron, "Neutron does not check the security group description") @decorators.attr(type=['negative']) def test_update_security_group_with_invalid_sg_des(self): # Update security_group with invalid sg_des should fail securitygroup = self.create_security_group() securitygroup_id = securitygroup['id'] # Update Security Group with group description longer than 255 chars s_new_des = 'des-'.ljust(260, '0') self.assertRaises(lib_exc.BadRequest, self.client.update_security_group, securitygroup_id, description=s_new_des) @decorators.attr(type=['negative']) @decorators.idempotent_id('27edee9c-873d-4da6-a68a-3c256efebe8f') def test_update_non_existent_security_group(self): # Update a non-existent Security Group should Fail non_exist_id = self.generate_random_security_group_id() s_name = data_utils.rand_name('sg') s_description = data_utils.rand_name('description') self.assertRaises(lib_exc.NotFound, self.client.update_security_group, non_exist_id, name=s_name, description=s_description) tempest-17.2.0/tempest/api/compute/security_groups/base.py0000666000175100017510000000310713207044712023751 0ustar zuulzuul00000000000000# 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 tempest.api.compute import base from tempest.common import utils from tempest import config from tempest.lib.common.utils import data_utils CONF = config.CONF class BaseSecurityGroupsTest(base.BaseV2ComputeTest): @classmethod def skip_checks(cls): super(BaseSecurityGroupsTest, cls).skip_checks() if not utils.get_service_list()['network']: raise cls.skipException("network service not enabled.") @classmethod def setup_credentials(cls): # A network and a subnet will be created for these tests cls.set_network_resources(network=True, subnet=True) super(BaseSecurityGroupsTest, cls).setup_credentials() @staticmethod def generate_random_security_group_id(): if (CONF.service_available.neutron and utils.is_extension_enabled('security-group', 'network')): return data_utils.rand_uuid() else: return data_utils.rand_int_id(start=999) tempest-17.2.0/tempest/api/compute/security_groups/test_security_group_rules_negative.py0000666000175100017510000001704313207044712032261 0ustar zuulzuul00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute.security_groups import base from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class SecurityGroupRulesNegativeTestJSON(base.BaseSecurityGroupsTest): @classmethod def setup_clients(cls): super(SecurityGroupRulesNegativeTestJSON, cls).setup_clients() cls.rules_client = cls.security_group_rules_client @decorators.attr(type=['negative']) @decorators.idempotent_id('1d507e98-7951-469b-82c3-23f1e6b8c254') def test_create_security_group_rule_with_non_existent_id(self): # Negative test: Creation of Security Group rule should FAIL # with non existent Parent group id # Adding rules to the non existent Security Group id parent_group_id = self.generate_random_security_group_id() ip_protocol = 'tcp' from_port = 22 to_port = 22 self.assertRaises(lib_exc.NotFound, self.rules_client.create_security_group_rule, parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('2244d7e4-adb7-4ecb-9930-2d77e123ce4f') def test_create_security_group_rule_with_invalid_id(self): # Negative test: Creation of Security Group rule should FAIL # with Parent group id which is not integer # Adding rules to the non int Security Group id parent_group_id = data_utils.rand_name('non_int_id') ip_protocol = 'tcp' from_port = 22 to_port = 22 self.assertRaises(lib_exc.BadRequest, self.rules_client.create_security_group_rule, parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('8bd56d02-3ffa-4d67-9933-b6b9a01d6089') def test_create_security_group_rule_duplicate(self): # Negative test: Create Security Group rule duplicate should fail # Creating a Security Group to add rule to it sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = 'tcp' from_port = 22 to_port = 22 rule = self.rules_client.create_security_group_rule( parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port)['security_group_rule'] self.addCleanup(self.rules_client.delete_security_group_rule, rule['id']) # Add the same rule to the group should fail self.assertRaises(lib_exc.BadRequest, self.rules_client.create_security_group_rule, parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('84c81249-9f6e-439c-9bbf-cbb0d2cddbdf') def test_create_security_group_rule_with_invalid_ip_protocol(self): # Negative test: Creation of Security Group rule should FAIL # with invalid ip_protocol # Creating a Security Group to add rule to it sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = data_utils.rand_name('999') from_port = 22 to_port = 22 self.assertRaises(lib_exc.BadRequest, self.rules_client.create_security_group_rule, parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('12bbc875-1045-4f7a-be46-751277baedb9') def test_create_security_group_rule_with_invalid_from_port(self): # Negative test: Creation of Security Group rule should FAIL # with invalid from_port # Creating a Security Group to add rule to it sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = 'tcp' from_port = data_utils.rand_int_id(start=65536) to_port = 22 self.assertRaises(lib_exc.BadRequest, self.rules_client.create_security_group_rule, parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('ff88804d-144f-45d1-bf59-dd155838a43a') def test_create_security_group_rule_with_invalid_to_port(self): # Negative test: Creation of Security Group rule should FAIL # with invalid to_port # Creating a Security Group to add rule to it sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = 'tcp' from_port = 22 to_port = data_utils.rand_int_id(start=65536) self.assertRaises(lib_exc.BadRequest, self.rules_client.create_security_group_rule, parent_group_id=parent_group_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('00296fa9-0576-496a-ae15-fbab843189e0') def test_create_security_group_rule_with_invalid_port_range(self): # Negative test: Creation of Security Group rule should FAIL # with invalid port range. # Creating a Security Group to add rule to it. sg = self.create_security_group() # Adding a rule to the created Security Group secgroup_id = sg['id'] ip_protocol = 'tcp' from_port = 22 to_port = 21 self.assertRaises(lib_exc.BadRequest, self.rules_client.create_security_group_rule, parent_group_id=secgroup_id, ip_protocol=ip_protocol, from_port=from_port, to_port=to_port) @decorators.attr(type=['negative']) @decorators.idempotent_id('56fddcca-dbb8-4494-a0db-96e9f869527c') def test_delete_security_group_rule_with_non_existent_id(self): # Negative test: Deletion of Security Group rule should be FAIL # with non existent id non_existent_rule_id = self.generate_random_security_group_id() self.assertRaises(lib_exc.NotFound, self.rules_client.delete_security_group_rule, non_existent_rule_id) tempest-17.2.0/tempest/cmd/0000775000175100017510000000000013207045130015525 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/cmd/init.py0000666000175100017510000001747413207044712017066 0ustar zuulzuul00000000000000# 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 os import shutil import sys from cliff import command from oslo_config import generator from oslo_log import log as logging from six import moves from testrepository import commands from tempest.cmd import workspace LOG = logging.getLogger(__name__) TESTR_CONF = """[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \\ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \\ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \\ ${PYTHON:-python} -m subunit.run discover -t %s %s $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list group_regex=([^\.]*\.)* """ def get_tempest_default_config_dir(): """Get default config directory of tempest There are 3 dirs that get tried in priority order. First is /etc/tempest, if that doesn't exist it looks for a tempest dir in the XDG_CONFIG_HOME dir (defaulting to ~/.config/tempest) and last it tries for a ~/.tempest/etc directory. If none of these exist a ~/.tempest/etc directory will be created. :return: default config dir """ global_conf_dir = '/etc/tempest' xdg_config = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) user_xdg_global_path = os.path.join(xdg_config, 'tempest') user_global_path = os.path.join(os.path.expanduser('~'), '.tempest/etc') if os.path.isdir(global_conf_dir): return global_conf_dir elif os.path.isdir(user_xdg_global_path): return user_xdg_global_path elif os.path.isdir(user_global_path): return user_global_path else: os.makedirs(user_global_path) return user_global_path class TempestInit(command.Command): """Setup a local working environment for running tempest""" def get_parser(self, prog_name): parser = super(TempestInit, self).get_parser(prog_name) parser.add_argument('dir', nargs='?', default=os.getcwd(), help="The path to the workspace directory. If you " "omit this argument, the workspace directory is " "your current directory") parser.add_argument('--config-dir', '-c', default=None) parser.add_argument('--show-global-config-dir', '-s', action='store_true', dest='show_global_dir', help="Print the global config dir location, " "then exit") parser.add_argument('--name', help="The workspace name", default=None) parser.add_argument('--workspace-path', default=None, help="The path to the workspace file, the default " "is ~/.tempest/workspace.yaml") return parser def generate_testr_conf(self, local_path): testr_conf_path = os.path.join(local_path, '.testr.conf') top_level_path = os.path.dirname(os.path.dirname(__file__)) discover_path = os.path.join(top_level_path, 'test_discover') testr_conf = TESTR_CONF % (top_level_path, discover_path) with open(testr_conf_path, 'w+') as testr_conf_file: testr_conf_file.write(testr_conf) def get_configparser(self, conf_path): config_parse = moves.configparser.ConfigParser() config_parse.optionxform = str # get any existing values if a config file already exists if os.path.isfile(conf_path): # use read() for Python 2 and 3 compatibility config_parse.read(conf_path) return config_parse def update_local_conf(self, conf_path, lock_dir, log_dir): config_parse = self.get_configparser(conf_path) # Set local lock_dir in tempest conf if not config_parse.has_section('oslo_concurrency'): config_parse.add_section('oslo_concurrency') config_parse.set('oslo_concurrency', 'lock_path', lock_dir) # Set local log_dir in tempest conf config_parse.set('DEFAULT', 'log_dir', log_dir) # Set default log filename to tempest.log config_parse.set('DEFAULT', 'log_file', 'tempest.log') # write out a new file with the updated configurations with open(conf_path, 'w+') as conf_file: config_parse.write(conf_file) def copy_config(self, etc_dir, config_dir): if os.path.isdir(config_dir): shutil.copytree(config_dir, etc_dir) else: LOG.warning("Global config dir %s can't be found", config_dir) def generate_sample_config(self, local_dir): conf_generator = os.path.join(os.path.dirname(__file__), 'config-generator.tempest.conf') output_file = os.path.join(local_dir, 'etc/tempest.conf.sample') if os.path.isfile(conf_generator): generator.main(['--config-file', conf_generator, '--output-file', output_file]) else: LOG.warning("Skipping sample config generation because global " "config file %s can't be found", conf_generator) def create_working_dir(self, local_dir, config_dir): # make sure we are working with abspath however tempest init is called local_dir = os.path.abspath(local_dir) # Create local dir if missing if not os.path.isdir(local_dir): LOG.debug('Creating local working dir: %s', local_dir) os.mkdir(local_dir) elif not os.listdir(local_dir) == []: raise OSError("Directory you are trying to initialize already " "exists and is not empty: %s" % local_dir) lock_dir = os.path.join(local_dir, 'tempest_lock') etc_dir = os.path.join(local_dir, 'etc') config_path = os.path.join(etc_dir, 'tempest.conf') log_dir = os.path.join(local_dir, 'logs') testr_dir = os.path.join(local_dir, '.testrepository') # Create lock dir if not os.path.isdir(lock_dir): LOG.debug('Creating lock dir: %s', lock_dir) os.mkdir(lock_dir) # Create log dir if not os.path.isdir(log_dir): LOG.debug('Creating log dir: %s', log_dir) os.mkdir(log_dir) # Create and copy local etc dir self.copy_config(etc_dir, config_dir) # Generate the sample config file self.generate_sample_config(local_dir) # Update local confs to reflect local paths self.update_local_conf(config_path, lock_dir, log_dir) # Generate a testr conf file self.generate_testr_conf(local_dir) # setup local testr working dir if not os.path.isdir(testr_dir): commands.run_argv(['testr', 'init', '-d', local_dir], sys.stdin, sys.stdout, sys.stderr) def take_action(self, parsed_args): workspace_manager = workspace.WorkspaceManager( parsed_args.workspace_path) name = parsed_args.name or parsed_args.dir.split(os.path.sep)[-1] config_dir = parsed_args.config_dir or get_tempest_default_config_dir() if parsed_args.show_global_dir: print("Global config dir is located at: %s" % config_dir) sys.exit(0) self.create_working_dir(parsed_args.dir, config_dir) workspace_manager.register_new_workspace( name, parsed_args.dir, init=True) tempest-17.2.0/tempest/cmd/cleanup_service.py0000666000175100017510000007427013207044712021267 0ustar zuulzuul00000000000000#!/usr/bin/env python # Copyright 2015 Dell 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_log import log as logging from tempest import clients from tempest.common import credentials_factory as credentials from tempest.common import identity from tempest.common import utils from tempest.common.utils import net_info from tempest import config LOG = logging.getLogger(__name__) CONF = config.CONF CONF_FLAVORS = None CONF_IMAGES = None CONF_NETWORKS = [] CONF_PRIV_NETWORK_NAME = None CONF_PUB_NETWORK = None CONF_PUB_ROUTER = None CONF_PROJECTS = None CONF_USERS = None IS_CINDER = None IS_GLANCE = None IS_HEAT = None IS_NEUTRON = None IS_NOVA = None def init_conf(): global CONF_FLAVORS global CONF_IMAGES global CONF_NETWORKS global CONF_PRIV_NETWORK global CONF_PRIV_NETWORK_NAME global CONF_PUB_NETWORK global CONF_PUB_ROUTER global CONF_PROJECTS global CONF_USERS global IS_CINDER global IS_GLANCE global IS_HEAT global IS_NEUTRON global IS_NOVA IS_CINDER = CONF.service_available.cinder IS_GLANCE = CONF.service_available.glance IS_HEAT = CONF.service_available.heat IS_NEUTRON = CONF.service_available.neutron IS_NOVA = CONF.service_available.nova CONF_FLAVORS = [CONF.compute.flavor_ref, CONF.compute.flavor_ref_alt] CONF_IMAGES = [CONF.compute.image_ref, CONF.compute.image_ref_alt] CONF_PRIV_NETWORK_NAME = CONF.compute.fixed_network_name CONF_PUB_NETWORK = CONF.network.public_network_id CONF_PUB_ROUTER = CONF.network.public_router_id CONF_PROJECTS = [CONF.auth.admin_project_name] CONF_USERS = [CONF.auth.admin_username] if IS_NEUTRON: CONF_PRIV_NETWORK = _get_network_id(CONF.compute.fixed_network_name, CONF.auth.admin_project_name) CONF_NETWORKS = [CONF_PUB_NETWORK, CONF_PRIV_NETWORK] def _get_network_id(net_name, project_name): am = clients.Manager( credentials.get_configured_admin_credentials()) net_cl = am.networks_client pr_cl = am.projects_client networks = net_cl.list_networks() project = identity.get_project_by_name(pr_cl, project_name) p_id = project['id'] n_id = None for net in networks['networks']: if (net['project_id'] == p_id and net['name'] == net_name): n_id = net['id'] break return n_id class BaseService(object): def __init__(self, kwargs): self.client = None for key, value in kwargs.items(): setattr(self, key, value) self.tenant_filter = {} if hasattr(self, 'tenant_id'): self.tenant_filter['tenant_id'] = self.tenant_id def _filter_by_tenant_id(self, item_list): if (item_list is None or not item_list or not hasattr(self, 'tenant_id') or self.tenant_id is None or 'tenant_id' not in item_list[0]): return item_list return [item for item in item_list if item['tenant_id'] == self.tenant_id] def list(self): pass def delete(self): pass def dry_run(self): pass def save_state(self): pass def run(self): if self.is_dry_run: self.dry_run() elif self.is_save_state: self.save_state() else: self.delete() class SnapshotService(BaseService): def __init__(self, manager, **kwargs): super(SnapshotService, self).__init__(kwargs) self.client = manager.snapshots_client_latest def list(self): client = self.client snaps = client.list_snapshots()['snapshots'] LOG.debug("List count, %s Snapshots", len(snaps)) return snaps def delete(self): snaps = self.list() client = self.client for snap in snaps: try: client.delete_snapshot(snap['id']) except Exception: LOG.exception("Delete Snapshot exception.") def dry_run(self): snaps = self.list() self.data['snapshots'] = snaps class ServerService(BaseService): def __init__(self, manager, **kwargs): super(ServerService, self).__init__(kwargs) self.client = manager.servers_client self.server_groups_client = manager.server_groups_client def list(self): client = self.client servers_body = client.list_servers() servers = servers_body['servers'] LOG.debug("List count, %s Servers", len(servers)) return servers def delete(self): client = self.client servers = self.list() for server in servers: try: client.delete_server(server['id']) except Exception: LOG.exception("Delete Server exception.") def dry_run(self): servers = self.list() self.data['servers'] = servers class ServerGroupService(ServerService): def list(self): client = self.server_groups_client sgs = client.list_server_groups()['server_groups'] LOG.debug("List count, %s Server Groups", len(sgs)) return sgs def delete(self): client = self.client sgs = self.list() for sg in sgs: try: client.delete_server_group(sg['id']) except Exception: LOG.exception("Delete Server Group exception.") def dry_run(self): sgs = self.list() self.data['server_groups'] = sgs class StackService(BaseService): def __init__(self, manager, **kwargs): super(StackService, self).__init__(kwargs) params = config.service_client_config('orchestration') self.client = manager.orchestration.OrchestrationClient( manager.auth_provider, **params) def list(self): client = self.client stacks = client.list_stacks()['stacks'] LOG.debug("List count, %s Stacks", len(stacks)) return stacks def delete(self): client = self.client stacks = self.list() for stack in stacks: try: client.delete_stack(stack['id']) except Exception: LOG.exception("Delete Stack exception.") def dry_run(self): stacks = self.list() self.data['stacks'] = stacks class KeyPairService(BaseService): def __init__(self, manager, **kwargs): super(KeyPairService, self).__init__(kwargs) self.client = manager.keypairs_client def list(self): client = self.client keypairs = client.list_keypairs()['keypairs'] LOG.debug("List count, %s Keypairs", len(keypairs)) return keypairs def delete(self): client = self.client keypairs = self.list() for k in keypairs: try: name = k['keypair']['name'] client.delete_keypair(name) except Exception: LOG.exception("Delete Keypairs exception.") def dry_run(self): keypairs = self.list() self.data['keypairs'] = keypairs class SecurityGroupService(BaseService): def __init__(self, manager, **kwargs): super(SecurityGroupService, self).__init__(kwargs) self.client = manager.compute_security_groups_client def list(self): client = self.client secgrps = client.list_security_groups()['security_groups'] secgrp_del = [grp for grp in secgrps if grp['name'] != 'default'] LOG.debug("List count, %s Security Groups", len(secgrp_del)) return secgrp_del def delete(self): client = self.client secgrp_del = self.list() for g in secgrp_del: try: client.delete_security_group(g['id']) except Exception: LOG.exception("Delete Security Groups exception.") def dry_run(self): secgrp_del = self.list() self.data['security_groups'] = secgrp_del class FloatingIpService(BaseService): def __init__(self, manager, **kwargs): super(FloatingIpService, self).__init__(kwargs) self.client = manager.compute_floating_ips_client def list(self): client = self.client floating_ips = client.list_floating_ips()['floating_ips'] LOG.debug("List count, %s Floating IPs", len(floating_ips)) return floating_ips def delete(self): client = self.client floating_ips = self.list() for f in floating_ips: try: client.delete_floating_ip(f['id']) except Exception: LOG.exception("Delete Floating IPs exception.") def dry_run(self): floating_ips = self.list() self.data['floating_ips'] = floating_ips class VolumeService(BaseService): def __init__(self, manager, **kwargs): super(VolumeService, self).__init__(kwargs) self.client = manager.volumes_client_latest def list(self): client = self.client vols = client.list_volumes()['volumes'] LOG.debug("List count, %s Volumes", len(vols)) return vols def delete(self): client = self.client vols = self.list() for v in vols: try: client.delete_volume(v['id']) except Exception: LOG.exception("Delete Volume exception.") def dry_run(self): vols = self.list() self.data['volumes'] = vols class VolumeQuotaService(BaseService): def __init__(self, manager, **kwargs): super(VolumeQuotaService, self).__init__(kwargs) self.client = manager.volume_quotas_v2_client def delete(self): client = self.client try: client.delete_quota_set(self.tenant_id) except Exception: LOG.exception("Delete Volume Quotas exception.") def dry_run(self): quotas = self.client.show_quota_set( self.tenant_id, params={'usage': True})['quota_set'] self.data['volume_quotas'] = quotas class NovaQuotaService(BaseService): def __init__(self, manager, **kwargs): super(NovaQuotaService, self).__init__(kwargs) self.client = manager.quotas_client self.limits_client = manager.limits_client def delete(self): client = self.client try: client.delete_quota_set(self.tenant_id) except Exception: LOG.exception("Delete Quotas exception.") def dry_run(self): client = self.limits_client quotas = client.show_limits()['limits'] self.data['compute_quotas'] = quotas['absolute'] # Begin network service classes class NetworkService(BaseService): def __init__(self, manager, **kwargs): super(NetworkService, self).__init__(kwargs) self.networks_client = manager.networks_client self.subnets_client = manager.subnets_client self.ports_client = manager.ports_client self.floating_ips_client = manager.floating_ips_client self.metering_labels_client = manager.metering_labels_client self.metering_label_rules_client = manager.metering_label_rules_client self.security_groups_client = manager.security_groups_client self.routers_client = manager.routers_client def _filter_by_conf_networks(self, item_list): if not item_list or not all(('network_id' in i for i in item_list)): return item_list return [item for item in item_list if item['network_id'] not in CONF_NETWORKS] def list(self): client = self.networks_client networks = client.list_networks(**self.tenant_filter) networks = networks['networks'] # filter out networks declared in tempest.conf if self.is_preserve: networks = [network for network in networks if network['id'] not in CONF_NETWORKS] LOG.debug("List count, %s Networks", networks) return networks def delete(self): client = self.networks_client networks = self.list() for n in networks: try: client.delete_network(n['id']) except Exception: LOG.exception("Delete Network exception.") def dry_run(self): networks = self.list() self.data['networks'] = networks class NetworkFloatingIpService(NetworkService): def list(self): client = self.floating_ips_client flips = client.list_floatingips(**self.tenant_filter) flips = flips['floatingips'] LOG.debug("List count, %s Network Floating IPs", len(flips)) return flips def delete(self): client = self.client flips = self.list() for flip in flips: try: client.delete_floatingip(flip['id']) except Exception: LOG.exception("Delete Network Floating IP exception.") def dry_run(self): flips = self.list() self.data['floating_ips'] = flips class NetworkRouterService(NetworkService): def list(self): client = self.routers_client routers = client.list_routers(**self.tenant_filter) routers = routers['routers'] if self.is_preserve: routers = [router for router in routers if router['id'] != CONF_PUB_ROUTER] LOG.debug("List count, %s Routers", len(routers)) return routers def delete(self): client = self.routers_client ports_client = self.ports_client routers = self.list() for router in routers: try: rid = router['id'] ports = [port for port in ports_client.list_ports(device_id=rid)['ports'] if net_info.is_router_interface_port(port)] for port in ports: client.remove_router_interface(rid, port_id=port['id']) client.delete_router(rid) except Exception: LOG.exception("Delete Router exception.") def dry_run(self): routers = self.list() self.data['routers'] = routers class NetworkHealthMonitorService(NetworkService): def list(self): client = self.client hms = client.list_health_monitors() hms = hms['health_monitors'] hms = self._filter_by_tenant_id(hms) LOG.debug("List count, %s Health Monitors", len(hms)) return hms def delete(self): client = self.client hms = self.list() for hm in hms: try: client.delete_health_monitor(hm['id']) except Exception: LOG.exception("Delete Health Monitor exception.") def dry_run(self): hms = self.list() self.data['health_monitors'] = hms class NetworkMemberService(NetworkService): def list(self): client = self.client members = client.list_members() members = members['members'] members = self._filter_by_tenant_id(members) LOG.debug("List count, %s Members", len(members)) return members def delete(self): client = self.client members = self.list() for member in members: try: client.delete_member(member['id']) except Exception: LOG.exception("Delete Member exception.") def dry_run(self): members = self.list() self.data['members'] = members class NetworkVipService(NetworkService): def list(self): client = self.client vips = client.list_vips() vips = vips['vips'] vips = self._filter_by_tenant_id(vips) LOG.debug("List count, %s VIPs", len(vips)) return vips def delete(self): client = self.client vips = self.list() for vip in vips: try: client.delete_vip(vip['id']) except Exception: LOG.exception("Delete VIP exception.") def dry_run(self): vips = self.list() self.data['vips'] = vips class NetworkPoolService(NetworkService): def list(self): client = self.client pools = client.list_pools() pools = pools['pools'] pools = self._filter_by_tenant_id(pools) LOG.debug("List count, %s Pools", len(pools)) return pools def delete(self): client = self.client pools = self.list() for pool in pools: try: client.delete_pool(pool['id']) except Exception: LOG.exception("Delete Pool exception.") def dry_run(self): pools = self.list() self.data['pools'] = pools class NetworkMeteringLabelRuleService(NetworkService): def list(self): client = self.metering_label_rules_client rules = client.list_metering_label_rules() rules = rules['metering_label_rules'] rules = self._filter_by_tenant_id(rules) LOG.debug("List count, %s Metering Label Rules", len(rules)) return rules def delete(self): client = self.metering_label_rules_client rules = self.list() for rule in rules: try: client.delete_metering_label_rule(rule['id']) except Exception: LOG.exception("Delete Metering Label Rule exception.") def dry_run(self): rules = self.list() self.data['rules'] = rules class NetworkMeteringLabelService(NetworkService): def list(self): client = self.metering_labels_client labels = client.list_metering_labels() labels = labels['metering_labels'] labels = self._filter_by_tenant_id(labels) LOG.debug("List count, %s Metering Labels", len(labels)) return labels def delete(self): client = self.metering_labels_client labels = self.list() for label in labels: try: client.delete_metering_label(label['id']) except Exception: LOG.exception("Delete Metering Label exception.") def dry_run(self): labels = self.list() self.data['labels'] = labels class NetworkPortService(NetworkService): def list(self): client = self.ports_client ports = [port for port in client.list_ports(**self.tenant_filter)['ports'] if port["device_owner"] == "" or port["device_owner"].startswith("compute:")] if self.is_preserve: ports = self._filter_by_conf_networks(ports) LOG.debug("List count, %s Ports", len(ports)) return ports def delete(self): client = self.ports_client ports = self.list() for port in ports: try: client.delete_port(port['id']) except Exception: LOG.exception("Delete Port exception.") def dry_run(self): ports = self.list() self.data['ports'] = ports class NetworkSecGroupService(NetworkService): def list(self): client = self.security_groups_client filter = self.tenant_filter # cannot delete default sec group so never show it. secgroups = [secgroup for secgroup in client.list_security_groups(**filter)['security_groups'] if secgroup['name'] != 'default'] if self.is_preserve: secgroups = self._filter_by_conf_networks(secgroups) LOG.debug("List count, %s security_groups", len(secgroups)) return secgroups def delete(self): client = self.client secgroups = self.list() for secgroup in secgroups: try: client.delete_secgroup(secgroup['id']) except Exception: LOG.exception("Delete security_group exception.") def dry_run(self): secgroups = self.list() self.data['secgroups'] = secgroups class NetworkSubnetService(NetworkService): def list(self): client = self.subnets_client subnets = client.list_subnets(**self.tenant_filter) subnets = subnets['subnets'] if self.is_preserve: subnets = self._filter_by_conf_networks(subnets) LOG.debug("List count, %s Subnets", len(subnets)) return subnets def delete(self): client = self.subnets_client subnets = self.list() for subnet in subnets: try: client.delete_subnet(subnet['id']) except Exception: LOG.exception("Delete Subnet exception.") def dry_run(self): subnets = self.list() self.data['subnets'] = subnets # begin global services class FlavorService(BaseService): def __init__(self, manager, **kwargs): super(FlavorService, self).__init__(kwargs) self.client = manager.flavors_client def list(self): client = self.client flavors = client.list_flavors({"is_public": None})['flavors'] if not self.is_save_state: # recreate list removing saved flavors flavors = [flavor for flavor in flavors if flavor['id'] not in self.saved_state_json['flavors'].keys()] if self.is_preserve: flavors = [flavor for flavor in flavors if flavor['id'] not in CONF_FLAVORS] LOG.debug("List count, %s Flavors after reconcile", len(flavors)) return flavors def delete(self): client = self.client flavors = self.list() for flavor in flavors: try: client.delete_flavor(flavor['id']) except Exception: LOG.exception("Delete Flavor exception.") def dry_run(self): flavors = self.list() self.data['flavors'] = flavors def save_state(self): flavors = self.list() self.data['flavors'] = {} for flavor in flavors: self.data['flavors'][flavor['id']] = flavor['name'] class ImageService(BaseService): def __init__(self, manager, **kwargs): super(ImageService, self).__init__(kwargs) self.client = manager.compute_images_client def list(self): client = self.client images = client.list_images({"all_tenants": True})['images'] if not self.is_save_state: images = [image for image in images if image['id'] not in self.saved_state_json['images'].keys()] if self.is_preserve: images = [image for image in images if image['id'] not in CONF_IMAGES] LOG.debug("List count, %s Images after reconcile", len(images)) return images def delete(self): client = self.client images = self.list() for image in images: try: client.delete_image(image['id']) except Exception: LOG.exception("Delete Image exception.") def dry_run(self): images = self.list() self.data['images'] = images def save_state(self): self.data['images'] = {} images = self.list() for image in images: self.data['images'][image['id']] = image['name'] class IdentityService(BaseService): def __init__(self, manager, **kwargs): super(IdentityService, self).__init__(kwargs) self.client = manager.identity_v3_client class UserService(BaseService): def __init__(self, manager, **kwargs): super(UserService, self).__init__(kwargs) self.client = manager.users_v3_client def list(self): users = self.client.list_users()['users'] if not self.is_save_state: users = [user for user in users if user['id'] not in self.saved_state_json['users'].keys()] if self.is_preserve: users = [user for user in users if user['name'] not in CONF_USERS] elif not self.is_save_state: # Never delete admin user users = [user for user in users if user['name'] != CONF.auth.admin_username] LOG.debug("List count, %s Users after reconcile", len(users)) return users def delete(self): users = self.list() for user in users: try: self.client.delete_user(user['id']) except Exception: LOG.exception("Delete User exception.") def dry_run(self): users = self.list() self.data['users'] = users def save_state(self): users = self.list() self.data['users'] = {} for user in users: self.data['users'][user['id']] = user['name'] class RoleService(BaseService): def __init__(self, manager, **kwargs): super(RoleService, self).__init__(kwargs) self.client = manager.roles_client def list(self): try: roles = self.client.list_roles()['roles'] # reconcile roles with saved state and never list admin role if not self.is_save_state: roles = [role for role in roles if (role['id'] not in self.saved_state_json['roles'].keys() and role['name'] != CONF.identity.admin_role)] LOG.debug("List count, %s Roles after reconcile", len(roles)) return roles except Exception: LOG.exception("Cannot retrieve Roles.") return [] def delete(self): roles = self.list() for role in roles: try: self.client.delete_role(role['id']) except Exception: LOG.exception("Delete Role exception.") def dry_run(self): roles = self.list() self.data['roles'] = roles def save_state(self): roles = self.list() self.data['roles'] = {} for role in roles: self.data['roles'][role['id']] = role['name'] class ProjectService(BaseService): def __init__(self, manager, **kwargs): super(ProjectService, self).__init__(kwargs) self.client = manager.projects_client def list(self): projects = self.client.list_projects()['projects'] if not self.is_save_state: projects = [project for project in projects if (project['id'] not in self.saved_state_json['projects'].keys() and project['name'] != CONF.auth.admin_project_name)] if self.is_preserve: projects = [project for project in projects if project['name'] not in CONF_PROJECTS] LOG.debug("List count, %s Projects after reconcile", len(projects)) return projects def delete(self): projects = self.list() for project in projects: try: self.client.delete_project(project['id']) except Exception: LOG.exception("Delete project exception.") def dry_run(self): projects = self.list() self.data['projects'] = projects def save_state(self): projects = self.list() self.data['projects'] = {} for project in projects: self.data['projects'][project['id']] = project['name'] class DomainService(BaseService): def __init__(self, manager, **kwargs): super(DomainService, self).__init__(kwargs) self.client = manager.domains_client def list(self): client = self.client domains = client.list_domains()['domains'] if not self.is_save_state: domains = [domain for domain in domains if domain['id'] not in self.saved_state_json['domains'].keys()] LOG.debug("List count, %s Domains after reconcile", len(domains)) return domains def delete(self): client = self.client domains = self.list() for domain in domains: try: client.update_domain(domain['id'], enabled=False) client.delete_domain(domain['id']) except Exception: LOG.exception("Delete Domain exception.") def dry_run(self): domains = self.list() self.data['domains'] = domains def save_state(self): domains = self.list() self.data['domains'] = {} for domain in domains: self.data['domains'][domain['id']] = domain['name'] def get_project_cleanup_services(): project_services = [] # TODO(gmann): Tempest should provide some plugin hook for cleanup # script extension to plugin tests also. if IS_NOVA: project_services.append(ServerService) project_services.append(KeyPairService) project_services.append(SecurityGroupService) project_services.append(ServerGroupService) if not IS_NEUTRON: project_services.append(FloatingIpService) project_services.append(NovaQuotaService) if IS_HEAT: project_services.append(StackService) if IS_NEUTRON: project_services.append(NetworkFloatingIpService) if utils.is_extension_enabled('metering', 'network'): project_services.append(NetworkMeteringLabelRuleService) project_services.append(NetworkMeteringLabelService) project_services.append(NetworkRouterService) project_services.append(NetworkPortService) project_services.append(NetworkSubnetService) project_services.append(NetworkService) project_services.append(NetworkSecGroupService) if IS_CINDER: project_services.append(SnapshotService) project_services.append(VolumeService) project_services.append(VolumeQuotaService) return project_services def get_global_cleanup_services(): global_services = [] if IS_NOVA: global_services.append(FlavorService) if IS_GLANCE: global_services.append(ImageService) global_services.append(UserService) global_services.append(ProjectService) global_services.append(DomainService) global_services.append(RoleService) return global_services tempest-17.2.0/tempest/cmd/account_generator.py0000777000175100017510000003045213207044712021617 0ustar zuulzuul00000000000000#!/usr/bin/env python # Copyright 2015 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. """ Utility for creating **accounts.yaml** file for concurrent test runs. Creates one primary user, one alt user, one swift admin, one stack owner and one admin (optionally) for each concurrent thread. The utility creates user for each tenant. The **accounts.yaml** file will be valid and contain credentials for created users, so each user will be in separate tenant and have the username, tenant_name, password and roles. **Usage:** ``tempest account-generator [-h] [OPTIONS] accounts_file.yaml``. Positional Arguments -------------------- **accounts_file.yaml** (Required) Provide an output accounts yaml file. Utility creates a .yaml file in the directory where the command is ran. The appropriate name for the file is *accounts.yaml* and it should be placed in *tempest/etc* directory. Authentication -------------- Account generator creates users and tenants so it needs the admin credentials of your cloud to operate properly. The corresponding info can be given either through CLI options or environment variables. You're probably familiar with these, but just to remind: ======== ======================== ==================== Param CLI Environment Variable ======== ======================== ==================== Username --os-username OS_USERNAME Password --os-password OS_PASSWORD Project --os-project-name OS_PROJECT_NAME Tenant --os-tenant-name (depr.) OS_TENANT_NAME Domain --os-domain-name OS_DOMAIN_NAME ======== ======================== ==================== Optional Arguments ------------------ **-h**, **--help** (Optional) Shows help message with the description of utility and its arguments, and exits. **-c /etc/tempest.conf**, **--config-file /etc/tempest.conf** (Optional) Path to tempest config file. If not specified, it searches for tempest.conf in these locations: - ./etc/ - /etc/tempest - ~/.tempest/ - ~/ - /etc/ **--os-username ** (Optional) Name used for authentication with the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User should have permissions to create new user accounts and tenants. **--os-password ** (Optional) Password used for authentication with the OpenStack Identity service. Defaults to env[OS_PASSWORD]. **--os-project-name ** (Optional) Project to request authorization on. Defaults to env[OS_PROJECT_NAME]. **--os-tenant-name ** (Optional, deprecated) Tenant to request authorization on. Defaults to env[OS_TENANT_NAME]. **--os-domain-name ** (Optional) Domain the user and project belong to. Defaults to env[OS_DOMAIN_NAME]. **--tag TAG** (Optional) Resources tag. Each created resource (user, project) will have the prefix with the given TAG in its name. Using tag is recommended for the further using, cleaning resources. **-r CONCURRENCY**, **--concurrency CONCURRENCY** (Optional) Concurrency count (default: 1). The number of accounts required can be estimated as CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in a different tenant. This is required to provide isolation between test for running in parallel. **--with-admin** (Optional) Creates admin for each concurrent group (default: False). **-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts using the specified version of the identity API. (default: '3'). To see help on specific argument, please do: ``tempest account-generator [OPTIONS] -h``. """ import argparse import os import traceback from cliff import command from oslo_log import log as logging import yaml from tempest.common import credentials_factory from tempest import config from tempest.lib.common import dynamic_creds LOG = None CONF = config.CONF DESCRIPTION = ('Create accounts.yaml file for concurrent test runs.%s' 'One primary user, one alt user, ' 'one swift admin, one stack owner ' 'and one admin (optionally) will be created ' 'for each concurrent thread.' % os.linesep) def setup_logging(): global LOG logging.setup(CONF, __name__) LOG = logging.getLogger(__name__) def get_credential_provider(opts): identity_version = "".join(['v', str(opts.identity_version)]) # NOTE(andreaf) For now tempest.conf controls whether resources will # actually be created. Once we remove the dependency from tempest.conf # we will need extra CLI option(s) to control this. network_resources = {'router': True, 'network': True, 'subnet': True, 'dhcp': True} admin_creds_dict = {'username': opts.os_username, 'password': opts.os_password} _project_name = opts.os_project_name or opts.os_tenant_name if opts.identity_version == 3: admin_creds_dict['project_name'] = _project_name admin_creds_dict['domain_name'] = opts.os_domain_name or 'Default' elif opts.identity_version == 2: admin_creds_dict['tenant_name'] = _project_name admin_creds = credentials_factory.get_credentials( fill_in=False, identity_version=identity_version, **admin_creds_dict) return dynamic_creds.DynamicCredentialProvider( name=opts.tag, network_resources=network_resources, **credentials_factory.get_dynamic_provider_params( identity_version, admin_creds=admin_creds)) def generate_resources(cred_provider, admin): # Create the list of resources to be provisioned for each process # NOTE(andreaf) get_credentials expects a string for types or a list for # roles. Adding all required inputs to the spec list. spec = ['primary', 'alt'] if CONF.service_available.swift: spec.append([CONF.object_storage.operator_role]) spec.append([CONF.object_storage.reseller_admin_role]) if CONF.service_available.heat: spec.append([CONF.orchestration.stack_owner_role, CONF.object_storage.operator_role]) if admin: spec.append('admin') resources = [] for cred_type in spec: resources.append((cred_type, cred_provider.get_credentials( credential_type=cred_type))) return resources def dump_accounts(resources, identity_version, account_file): accounts = [] for resource in resources: cred_type, test_resource = resource account = { 'username': test_resource.username, 'password': test_resource.password } if identity_version == 3: account['project_name'] = test_resource.project_name account['domain_name'] = test_resource.domain_name else: account['project_name'] = test_resource.tenant_name # If the spec includes 'admin' credentials are defined via type, # else they are defined via list of roles. if cred_type == 'admin': account['types'] = [cred_type] elif cred_type not in ['primary', 'alt']: account['roles'] = cred_type if test_resource.network: account['resources'] = {} if test_resource.network: account['resources']['network'] = test_resource.network['name'] accounts.append(account) if os.path.exists(account_file): os.rename(account_file, '.'.join((account_file, 'bak'))) with open(account_file, 'w') as f: yaml.safe_dump(accounts, f, default_flow_style=False) LOG.info('%s generated successfully!', account_file) def _parser_add_args(parser): parser.add_argument('-c', '--config-file', metavar='/etc/tempest.conf', help='path to tempest config file') parser.add_argument('--os-username', metavar='', default=os.environ.get('OS_USERNAME'), help='User should have permissions ' 'to create new user accounts and ' 'tenants. Defaults to env[OS_USERNAME].') parser.add_argument('--os-password', metavar='', default=os.environ.get('OS_PASSWORD'), help='Defaults to env[OS_PASSWORD].') parser.add_argument('--os-project-name', metavar='', default=os.environ.get('OS_PROJECT_NAME'), help='Defaults to env[OS_PROJECT_NAME].') parser.add_argument('--os-tenant-name', metavar='', default=os.environ.get('OS_TENANT_NAME'), help='Defaults to env[OS_TENANT_NAME].') parser.add_argument('--os-domain-name', metavar='', default=os.environ.get('OS_DOMAIN_NAME'), help='Defaults to env[OS_DOMAIN_NAME].') parser.add_argument('--tag', default='', required=False, dest='tag', help='Resources tag') parser.add_argument('-r', '--concurrency', default=1, type=int, required=False, dest='concurrency', help='Concurrency count') parser.add_argument('--with-admin', action='store_true', dest='admin', help='Creates admin for each concurrent group') parser.add_argument('-i', '--identity-version', default=3, choices=[2, 3], type=int, required=False, dest='identity_version', help='Version of the Identity API to use') parser.add_argument('accounts', metavar='accounts_file.yaml', help='Output accounts yaml file') def get_options(): usage_string = ('tempest account-generator [-h] ...\n\n' 'To see help on specific argument, do:\n' 'tempest account-generator -h') parser = argparse.ArgumentParser( description=DESCRIPTION, formatter_class=argparse.ArgumentDefaultsHelpFormatter, usage=usage_string ) _parser_add_args(parser) opts = parser.parse_args() return opts class TempestAccountGenerator(command.Command): def get_parser(self, prog_name): parser = super(TempestAccountGenerator, self).get_parser(prog_name) _parser_add_args(parser) return parser def take_action(self, parsed_args): try: main(parsed_args) except Exception: LOG.exception("Failure generating test accounts.") traceback.print_exc() raise def get_description(self): return DESCRIPTION def main(opts=None): setup_logging() if not opts: 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) if opts.os_tenant_name: LOG.warning("'os-tenant-name' and 'OS_TENANT_NAME' are both " "deprecated, please use 'os-project-name' or " "'OS_PROJECT_NAME' instead") resources = [] for count in range(opts.concurrency): # Use N different cred_providers to obtain different sets of creds cred_provider = get_credential_provider(opts) resources.extend(generate_resources(cred_provider, opts.admin)) dump_accounts(resources, opts.identity_version, opts.accounts) if __name__ == "__main__": main() tempest-17.2.0/tempest/cmd/__init__.py0000666000175100017510000000000013207044712017633 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/cmd/cleanup.py0000666000175100017510000003133313207044712017540 0ustar zuulzuul00000000000000#!/usr/bin/env python # # Copyright 2014 Dell 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. """ Utility for cleaning up environment after Tempest test run **Usage:** ``tempest cleanup [--help] [OPTIONS]`` If run with no arguments, ``tempest cleanup`` will query your OpenStack deployment and build a list of resources to delete and destroy them. This list will exclude the resources from ``saved_state.json`` and will include the configured admin account if the ``--delete-tempest-conf-objects`` flag is specified. By default the admin project is not deleted and the admin user specified in ``tempest.conf`` is never deleted. Example Run ----------- **WARNING: If step 1 is skipped in the example below, the cleanup procedure may delete resources that existed in the cloud before the test run. This may cause an unwanted destruction of cloud resources, so use caution with this command.** ``$ tempest cleanup --init-saved-state`` ``$ # Actual running of Tempest tests`` ``$ tempest cleanup`` Runtime Arguments ----------------- **--init-saved-state**: Initializes the saved state of the OpenStack deployment and will output a ``saved_state.json`` file containing resources from your deployment that will be preserved from the cleanup command. This should be done prior to running Tempest tests. **--delete-tempest-conf-objects**: If option is present, then the command will delete the admin project in addition to the resources associated with them on clean up. If option is not present, the command will delete the resources associated with the Tempest and alternate Tempest users and projects but will not delete the projects themselves. **--dry-run**: Creates a report (``./dry_run.json``) of the projects that will be cleaned up (in the ``_projects_to_clean`` dictionary [1]_) and the global objects that will be removed (domains, flavors, images, roles, projects, and users). Once the cleanup command is executed (e.g. run without parameters), running it again with **--dry-run** should yield an empty report. **--help**: Print the help text for the command and parameters. .. [1] The ``_projects_to_clean`` dictionary in ``dry_run.json`` lists the projects that ``tempest cleanup`` will loop through to delete child objects, but the command will, by default, not delete the projects themselves. This may differ from the ``projects`` list as you can clean the Tempest and alternate Tempest users and projects but they will not be deleted unless the **--delete-tempest-conf-objects** flag is used to force their deletion. """ import sys import traceback from cliff import command from oslo_log import log as logging from oslo_serialization import jsonutils as json from tempest import clients from tempest.cmd import cleanup_service from tempest.common import credentials_factory as credentials from tempest.common import identity from tempest import config SAVED_STATE_JSON = "saved_state.json" DRY_RUN_JSON = "dry_run.json" LOG = logging.getLogger(__name__) CONF = config.CONF class TempestCleanup(command.Command): def take_action(self, parsed_args): try: self.init(parsed_args) if not parsed_args.init_saved_state: self._cleanup() except Exception: LOG.exception("Failure during cleanup") traceback.print_exc() raise def init(self, parsed_args): cleanup_service.init_conf() self.options = parsed_args self.admin_mgr = clients.Manager( credentials.get_configured_admin_credentials()) self.dry_run_data = {} self.json_data = {} self.admin_id = "" self.admin_role_id = "" self.admin_project_id = "" self._init_admin_ids() self.admin_role_added = [] # available services self.project_services = cleanup_service.get_project_cleanup_services() self.global_services = cleanup_service.get_global_cleanup_services() if parsed_args.init_saved_state: self._init_state() return self._load_json() def _cleanup(self): print("Begin cleanup") is_dry_run = self.options.dry_run is_preserve = not self.options.delete_tempest_conf_objects is_save_state = False if is_dry_run: self.dry_run_data["_projects_to_clean"] = {} admin_mgr = self.admin_mgr # Always cleanup tempest and alt tempest projects unless # they are in saved state json. Therefore is_preserve is False kwargs = {'data': self.dry_run_data, 'is_dry_run': is_dry_run, 'saved_state_json': self.json_data, 'is_preserve': False, 'is_save_state': is_save_state} project_service = cleanup_service.ProjectService(admin_mgr, **kwargs) projects = project_service.list() print("Process %s projects" % len(projects)) # Loop through list of projects and clean them up. for project in projects: self._add_admin(project['id']) self._clean_project(project) kwargs = {'data': self.dry_run_data, 'is_dry_run': is_dry_run, 'saved_state_json': self.json_data, 'is_preserve': is_preserve, 'is_save_state': is_save_state} for service in self.global_services: svc = service(admin_mgr, **kwargs) svc.run() if is_dry_run: with open(DRY_RUN_JSON, 'w+') as f: f.write(json.dumps(self.dry_run_data, sort_keys=True, indent=2, separators=(',', ': '))) self._remove_admin_user_roles() def _remove_admin_user_roles(self): project_ids = self.admin_role_added LOG.debug("Removing admin user roles where needed for projects: %s", project_ids) for project_id in project_ids: self._remove_admin_role(project_id) def _clean_project(self, project): print("Cleaning project: %s " % project['name']) is_dry_run = self.options.dry_run dry_run_data = self.dry_run_data is_preserve = not self.options.delete_tempest_conf_objects project_id = project['id'] project_name = project['name'] project_data = None if is_dry_run: project_data = dry_run_data["_projects_to_clean"][project_id] = {} project_data['name'] = project_name kwargs = {"username": CONF.auth.admin_username, "password": CONF.auth.admin_password, "project_name": project['name']} mgr = clients.Manager(credentials=credentials.get_credentials( **kwargs)) kwargs = {'data': project_data, 'is_dry_run': is_dry_run, 'saved_state_json': None, 'is_preserve': is_preserve, 'is_save_state': False, 'project_id': project_id} for service in self.project_services: svc = service(mgr, **kwargs) svc.run() def _init_admin_ids(self): pr_cl = self.admin_mgr.projects_client rl_cl = self.admin_mgr.roles_v3_client rla_cl = self.admin_mgr.role_assignments_client us_cl = self.admin_mgr.users_v3_client project = identity.get_project_by_name(pr_cl, CONF.auth.admin_project_name) self.admin_project_id = project['id'] user = identity.get_user_by_project(us_cl, rla_cl, self.admin_project_id, CONF.auth.admin_username) self.admin_id = user['id'] roles = rl_cl.list_roles()['roles'] for role in roles: if role['name'] == CONF.identity.admin_role: self.admin_role_id = role['id'] break def get_parser(self, prog_name): parser = super(TempestCleanup, self).get_parser(prog_name) parser.add_argument('--init-saved-state', action="store_true", dest='init_saved_state', default=False, help="Creates JSON file: " + SAVED_STATE_JSON + ", representing the current state of your " "deployment, specifically object types " "tempest creates and destroys during a run. " "You must run with this flag prior to " "executing cleanup in normal mode, which is with " "no arguments.") parser.add_argument('--delete-tempest-conf-objects', action="store_true", dest='delete_tempest_conf_objects', default=False, help="Force deletion of the tempest and " "alternate tempest users and projects.") parser.add_argument('--dry-run', action="store_true", dest='dry_run', default=False, help="Generate JSON file:" + DRY_RUN_JSON + ", that reports the objects that would have " "been deleted had a full cleanup been run.") return parser def get_description(self): return 'Cleanup after tempest run' def _add_admin(self, project_id): rl_cl = self.admin_mgr.roles_v3_client needs_role = True roles = rl_cl.list_user_roles_on_project(project_id, self.admin_id)['roles'] for role in roles: if role['id'] == self.admin_role_id: needs_role = False LOG.debug("User already had admin privilege for this project") if needs_role: LOG.debug("Adding admin privilege for : %s", project_id) rl_cl.create_user_role_on_project(project_id, self.admin_id, self.admin_role_id) self.admin_role_added.append(project_id) def _remove_admin_role(self, project_id): LOG.debug("Remove admin user role for projectt: %s", project_id) # Must initialize Admin Manager for each user role # Otherwise authentication exception is thrown, weird id_cl = clients.Manager( credentials.get_configured_admin_credentials()).identity_client if (self._project_exists(project_id)): try: id_cl.delete_role_from_user_on_project(project_id, self.admin_id, self.admin_role_id) except Exception as ex: LOG.exception("Failed removing role from project which still" "exists, exception: %s", ex) def _project_exists(self, project_id): pr_cl = self.admin_mgr.projects_client try: p = pr_cl.show_project(project_id) LOG.debug("Project is: %s", str(p)) return True except Exception as ex: LOG.debug("Project no longer exists? %s", ex) return False def _init_state(self): print("Initializing saved state.") data = {} admin_mgr = self.admin_mgr kwargs = {'data': data, 'is_dry_run': False, 'saved_state_json': data, 'is_preserve': False, 'is_save_state': True} for service in self.global_services: svc = service(admin_mgr, **kwargs) svc.run() with open(SAVED_STATE_JSON, 'w+') as f: f.write(json.dumps(data, sort_keys=True, indent=2, separators=(',', ': '))) def _load_json(self): try: with open(SAVED_STATE_JSON) as json_file: self.json_data = json.load(json_file) except IOError as ex: LOG.exception("Failed loading saved state, please be sure you" " have first run cleanup with --init-saved-state " "flag prior to running tempest. Exception: %s", ex) sys.exit(ex) except Exception as ex: LOG.exception("Exception parsing saved state json : %s", ex) sys.exit(ex) tempest-17.2.0/tempest/cmd/verify_tempest_config.py0000666000175100017510000004426513207044712022513 0ustar zuulzuul00000000000000#!/usr/bin/env python # 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. """ Verifies user's current tempest configuration. This command is used for updating or user's tempest configuration file based on api queries or replacing all option in a tempest configuration file for a full list of extensions. General Options =============== -u, --update ------------ Update the config file with results from api queries. This assumes whatever is set in the config file is incorrect. -o FILE, --output=FILE ---------------------- Output file to write an updated config file to. This has to be a separate file from the original one. If one isn't specified with -u the values which should be changed will be printed to STDOUT. -r, --replace-ext ----------------- If specified the all option will be replaced with a full list of extensions. Environment Variables ===================== The command is workspace aware - it uses tempest config file tempest.conf located in ./etc/ directory. The path to the config file and it's name can be changed through environment variables. TEMPEST_CONFIG_DIR ------------------ Path to a directory where tempest configuration file is stored. If the variable is set, the default path (./etc/) is overridden. TEMPEST_CONFIG -------------- Name of a tempest configuration file. If the variable is specified, the default name (tempest.conf) is overridden. """ import argparse import os import re import sys import traceback from cliff import command from oslo_log import log as logging from oslo_serialization import jsonutils as json from six import moves from six.moves.urllib import parse as urlparse from tempest import clients from tempest.common import credentials_factory as credentials from tempest import config import tempest.lib.common.http from tempest.lib import exceptions as lib_exc CONF = config.CONF CONF_PARSER = None LOG = logging.getLogger(__name__) def _get_config_file(): config_dir = os.getcwd() default_config_dir = os.path.join(config_dir, "etc") default_config_file = "tempest.conf" conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir) conf_file = os.environ.get('TEMPEST_CONFIG', default_config_file) path = os.path.join(conf_dir, conf_file) fd = open(path, 'r+') return fd def change_option(option, group, value): if not CONF_PARSER.has_section(group): CONF_PARSER.add_section(group) CONF_PARSER.set(group, option, str(value)) def print_and_or_update(option, group, value, update): print('Config option %s in group %s should be changed to: %s' % (option, group, value)) if update: change_option(option, group, value) def contains_version(prefix, versions): return any([x for x in versions if x.startswith(prefix)]) def verify_glance_api_versions(os, update): # Check glance api versions # Since we want to verify that the configuration is correct, we cannot # rely on a specific version of the API being available. try: _, versions = os.image_v1.ImagesClient().get_versions() except lib_exc.NotFound: # If not found, we use v2. The assumption is that either v1 or v2 # are available, since glance is marked as available in the catalog. # If not, glance should be disabled in Tempest conf. try: versions = os.image_v2.VersionsClient().list_versions()['versions'] versions = [x['id'] for x in versions] except lib_exc.NotFound: msg = ('Glance is available in the catalog, but no known version, ' '(v1.x or v2.x) of Glance could be found, so Glance should ' 'be configured as not available') LOG.warn(msg) print_and_or_update('glance', 'service-available', False, update) return if CONF.image_feature_enabled.api_v1 != contains_version('v1.', versions): print_and_or_update('api_v1', 'image-feature-enabled', not CONF.image_feature_enabled.api_v1, update) if CONF.image_feature_enabled.api_v2 != contains_version('v2.', versions): print_and_or_update('api_v2', 'image-feature-enabled', not CONF.image_feature_enabled.api_v2, update) def _remove_version_project(url_path): # The regex matches strings like /v2.0, /v3/, /v2.1/project-id/ return re.sub(r'/v\d+(\.\d+)?(/[^/]+)?', '', url_path) def _get_unversioned_endpoint(base_url): endpoint_parts = urlparse.urlparse(base_url) new_path = _remove_version_project(endpoint_parts.path) endpoint_parts = endpoint_parts._replace(path=new_path) endpoint = urlparse.urlunparse(endpoint_parts) return endpoint def _get_api_versions(os, service): # Clients are used to obtain the base_url. Each client applies the # appropriate filters to the catalog to extract a base_url which # matches the configured region and endpoint_type. # The base URL is used to obtain the list of versions available. client_dict = { 'nova': os.compute.ServersClient(), 'keystone': os.identity_v3.IdentityClient( endpoint_type=CONF.identity.v3_endpoint_type), 'cinder': os.volume_v3.VolumesClient(), } if service != 'keystone' and service != 'cinder': # Since keystone and cinder may be listening on a path, # do not remove the path. client_dict[service].skip_path() endpoint = _get_unversioned_endpoint(client_dict[service].base_url) http = tempest.lib.common.http.ClosingHttp( CONF.identity.disable_ssl_certificate_validation, CONF.identity.ca_certificates_file) __, body = http.request(endpoint, 'GET') client_dict[service].reset_path() try: body = json.loads(body) except ValueError: LOG.error( 'Failed to get a JSON response from unversioned endpoint %s ' '(versioned endpoint was %s). Response is:\n%s', endpoint, client_dict[service].base_url, body[:100]) raise if service == 'keystone': versions = map(lambda x: x['id'], body['versions']['values']) else: versions = map(lambda x: x['id'], body['versions']) return list(versions) def verify_keystone_api_versions(os, update): # Check keystone api versions versions = _get_api_versions(os, 'keystone') if (CONF.identity_feature_enabled.api_v3 != contains_version('v3.', versions)): print_and_or_update('api_v3', 'identity-feature-enabled', not CONF.identity_feature_enabled.api_v3, update) def verify_cinder_api_versions(os, update): # Check cinder api versions versions = _get_api_versions(os, 'cinder') if (CONF.volume_feature_enabled.api_v1 != contains_version('v1.', versions)): print_and_or_update('api_v1', 'volume-feature-enabled', not CONF.volume_feature_enabled.api_v1, update) if (CONF.volume_feature_enabled.api_v2 != contains_version('v2.', versions)): print_and_or_update('api_v2', 'volume-feature-enabled', not CONF.volume_feature_enabled.api_v2, update) if (CONF.volume_feature_enabled.api_v3 != contains_version('v3.', versions)): print_and_or_update('api_v3', 'volume-feature-enabled', not CONF.volume_feature_enabled.api_v3, update) def verify_api_versions(os, service, update): verify = { 'cinder': verify_cinder_api_versions, 'glance': verify_glance_api_versions, 'keystone': verify_keystone_api_versions, } if service not in verify: return verify[service](os, update) def get_extension_client(os, service): extensions_client = { 'nova': os.compute.ExtensionsClient(), 'neutron': os.network.ExtensionsClient(), 'swift': os.object_storage.CapabilitiesClient(), # NOTE: Cinder v3 API is current and v2 and v1 are deprecated. # V3 extension API is the same as v2, so we reuse the v2 client # for v3 API also. 'cinder': os.volume_v2.ExtensionsClient(), } if service not in extensions_client: print('No tempest extensions client for %s' % service) sys.exit(1) return extensions_client[service] def get_enabled_extensions(service): extensions_options = { 'nova': CONF.compute_feature_enabled.api_extensions, 'cinder': CONF.volume_feature_enabled.api_extensions, 'neutron': CONF.network_feature_enabled.api_extensions, 'swift': CONF.object_storage_feature_enabled.discoverable_apis, } if service not in extensions_options: print('No supported extensions list option for %s' % service) sys.exit(1) return extensions_options[service] def verify_extensions(os, service, results): extensions_client = get_extension_client(os, service) if service != 'swift': resp = extensions_client.list_extensions() else: resp = extensions_client.list_capabilities() # For Nova, Cinder and Neutron we use the alias name rather than the # 'name' field because the alias is considered to be the canonical # name. if isinstance(resp, dict): if service == 'swift': # Remove Swift general information from extensions list resp.pop('swift') extensions = resp.keys() else: extensions = map(lambda x: x['alias'], resp['extensions']) else: extensions = map(lambda x: x['alias'], resp) extensions = list(extensions) if not results.get(service): results[service] = {} extensions_opt = get_enabled_extensions(service) if extensions_opt[0] == 'all': results[service]['extensions'] = extensions return results # Verify that all configured extensions are actually enabled for extension in extensions_opt: results[service][extension] = extension in extensions # Verify that there aren't additional extensions enabled that aren't # specified in the config list for extension in extensions: if extension not in extensions_opt: results[service][extension] = False return results def display_results(results, update, replace): update_dict = { 'swift': 'object-storage-feature-enabled', 'nova': 'compute-feature-enabled', 'cinder': 'volume-feature-enabled', 'neutron': 'network-feature-enabled', } for service in results: # If all extensions are specified as being enabled there is no way to # verify this so we just assume this to be true if results[service].get('extensions'): if replace: output_list = results[service].get('extensions') else: output_list = ['all'] else: extension_list = get_enabled_extensions(service) output_list = [] for extension in results[service]: if not results[service][extension]: if extension in extension_list: print("%s extension: %s should not be included in the " "list of enabled extensions" % (service, extension)) else: print("%s extension: %s should be included in the list" " of enabled extensions" % (service, extension)) output_list.append(extension) else: output_list.append(extension) if update: # Sort List output_list.sort() # Convert list to a string output_string = ', '.join(output_list) if service == 'swift': change_option('discoverable_apis', update_dict[service], output_string) else: change_option('api_extensions', update_dict[service], output_string) def check_service_availability(os, update): services = [] avail_services = [] codename_match = { 'volume': 'cinder', 'network': 'neutron', 'image': 'glance', 'object_storage': 'swift', 'compute': 'nova', 'orchestration': 'heat', 'baremetal': 'ironic', 'identity': 'keystone', } # Get catalog list for endpoints to use for validation _token, auth_data = os.auth_provider.get_auth() if os.auth_version == 'v2': catalog_key = 'serviceCatalog' else: catalog_key = 'catalog' for entry in auth_data[catalog_key]: services.append(entry['type']) # Pull all catalog types from config file and compare against endpoint list for cfgname in dir(CONF._config): cfg = getattr(CONF, cfgname) catalog_type = getattr(cfg, 'catalog_type', None) if not catalog_type: continue else: if cfgname == 'identity': # Keystone is a required service for tempest continue if catalog_type not in services: if getattr(CONF.service_available, codename_match[cfgname]): print('Endpoint type %s not found either disable service ' '%s or fix the catalog_type in the config file' % ( catalog_type, codename_match[cfgname])) if update: change_option(codename_match[cfgname], 'service_available', False) else: if not getattr(CONF.service_available, codename_match[cfgname]): print('Endpoint type %s is available, service %s should be' ' set as available in the config file.' % ( catalog_type, codename_match[cfgname])) if update: change_option(codename_match[cfgname], 'service_available', True) # If we are going to enable this we should allow # extension checks. avail_services.append(codename_match[cfgname]) else: avail_services.append(codename_match[cfgname]) return avail_services def _parser_add_args(parser): parser.add_argument('-u', '--update', action='store_true', help='Update the config file with results from api ' 'queries. This assumes whatever is set in the ' 'config file is incorrect. In the case of ' 'endpoint checks where it could either be the ' 'incorrect catalog type or the service available ' 'option the service available option is assumed ' 'to be incorrect and is thus changed') parser.add_argument('-o', '--output', help="Output file to write an updated config file to. " "This has to be a separate file from the " "original config file. If one isn't specified " "with -u the values which should be changed " "will be printed to STDOUT") parser.add_argument('-r', '--replace-ext', action='store_true', help="If specified the all option will be replaced " "with a full list of extensions") def parse_args(): parser = argparse.ArgumentParser() _parser_add_args(parser) opts = parser.parse_args() return opts def main(opts=None): print('Running config verification...') if opts is None: print("Use of: 'verify-tempest-config' is deprecated, " "please use: 'tempest verify-config'") opts = parse_args() update = opts.update replace = opts.replace_ext global CONF_PARSER if update: conf_file = _get_config_file() CONF_PARSER = moves.configparser.ConfigParser() CONF_PARSER.optionxform = str CONF_PARSER.readfp(conf_file) # Indicate not to create network resources as part of getting credentials net_resources = { 'network': False, 'router': False, 'subnet': False, 'dhcp': False } icreds = credentials.get_credentials_provider( 'verify_tempest_config', network_resources=net_resources) try: os = clients.Manager(icreds.get_primary_creds().credentials) services = check_service_availability(os, update) results = {} for service in ['nova', 'cinder', 'neutron', 'swift']: if service not in services: continue results = verify_extensions(os, service, results) # Verify API versions of all services in the keystone catalog and # keystone itself. services.append('keystone') for service in services: verify_api_versions(os, service, update) display_results(results, update, replace) if update: conf_file.close() if opts.output: with open(opts.output, 'w+') as outfile: CONF_PARSER.write(outfile) finally: icreds.clear_creds() class TempestVerifyConfig(command.Command): """Verify your current tempest configuration""" def get_parser(self, prog_name): parser = super(TempestVerifyConfig, self).get_parser(prog_name) _parser_add_args(parser) return parser def take_action(self, parsed_args): try: main(parsed_args) except Exception: LOG.exception("Failure verifying configuration.") traceback.print_exc() raise if __name__ == "__main__": main() tempest-17.2.0/tempest/cmd/main.py0000666000175100017510000000311213207044712017027 0ustar zuulzuul00000000000000# Copyright 2015 Dell 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 sys from cliff import app from cliff import commandmanager from oslo_log import log as logging from pbr import version class Main(app.App): log = logging.getLogger(__name__) def __init__(self): super(Main, self).__init__( description='Tempest cli application', version=version.VersionInfo('tempest').version_string_with_vcs(), command_manager=commandmanager.CommandManager('tempest.cm'), deferred_help=True, ) def initialize_app(self, argv): self.log.debug('tempest initialize_app') def prepare_to_run_command(self, cmd): self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__) def clean_up(self, cmd, result, err): self.log.debug('tempest clean_up %s', cmd.__class__.__name__) if err: self.log.debug('tempest got an error: %s', err) def main(argv=sys.argv[1:]): the_app = Main() return the_app.run(argv) if __name__ == '__main__': sys.exit(main(sys.argv[1:])) tempest-17.2.0/tempest/cmd/subunit_describe_calls.py0000666000175100017510000002621613207044712022624 0ustar zuulzuul00000000000000# Copyright 2016 Rackspace # # 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. """ subunit-describe-calls is a parser for subunit streams to determine what REST API calls are made inside of a test and in what order they are called. Runtime Arguments ----------------- **--subunit, -s**: (Optional) The path to the subunit file being parsed, defaults to stdin **--non-subunit-name, -n**: (Optional) The file_name that the logs are being stored in **--output-file, -o**: (Optional) The path where the JSON output will be written to. This contains more information than is present in stdout. **--ports, -p**: (Optional) The path to a JSON file describing the ports being used by different services Usage ----- subunit-describe-calls will take in either stdin subunit v1 or v2 stream or a file path which contains either a subunit v1 or v2 stream passed via the --subunit parameter. This is then parsed checking for details contained in the file_bytes of the --non-subunit-name parameter (the default is pythonlogging which is what Tempest uses to store logs). By default the OpenStack Kilo release port defaults (http://bit.ly/22jpF5P) are used unless a file is provided via the --ports option. The resulting output is dumped in JSON output to the path provided in the --output-file option. Ports file JSON structure ^^^^^^^^^^^^^^^^^^^^^^^^^ :: { "": "", ... } Output file JSON structure ^^^^^^^^^^^^^^^^^^^^^^^^^^ :: { "full_test_name[with_id_and_tags]": [ { "name": "The ClassName.MethodName that made the call", "verb": "HTTP Verb", "service": "Name of the service", "url": "A shortened version of the URL called", "status_code": "The status code of the response", "request_headers": "The headers of the request", "request_body": "The body of the request", "response_headers": "The headers of the response", "response_body": "The body of the response" } ] } """ import argparse import collections import io import json import os import re import sys import subunit import testtools class UrlParser(testtools.TestResult): uuid_re = re.compile(r'(^|[^0-9a-f])[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-' '[0-9a-f]{4}-[0-9a-f]{12}([^0-9a-f]|$)') id_re = re.compile(r'(^|[^0-9a-z])[0-9a-z]{8}[0-9a-z]{4}[0-9a-z]{4}' '[0-9a-z]{4}[0-9a-z]{12}([^0-9a-z]|$)') ip_re = re.compile(r'(^|[^0-9])[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]' '{1,3}([^0-9]|$)') url_re = re.compile(r'.*INFO.*Request \((?P.*)\): (?P[\d]{3}) ' '(?P\w*) (?P.*) .*') port_re = re.compile(r'.*:(?P\d+).*') path_re = re.compile(r'http[s]?://[^/]*/(?P.*)') request_re = re.compile(r'.* Request - Headers: (?P.*)') response_re = re.compile(r'.* Response - Headers: (?P.*)') body_re = re.compile(r'.*Body: (?P.*)') # Based on newton defaults: # http://docs.openstack.org/newton/config-reference/ # firewalls-default-ports.html services = { "8776": "Block Storage", "8774": "Nova", "8773": "Nova-API", "8775": "Nova-API", "8386": "Sahara", "35357": "Keystone", "5000": "Keystone", "9292": "Glance", "9191": "Glance", "9696": "Neutron", "6000": "Swift", "6001": "Swift", "6002": "Swift", "8004": "Heat", "8000": "Heat", "8003": "Heat", "8777": "Ceilometer", "80": "Horizon", "8080": "Swift", "443": "SSL", "873": "rsync", "3260": "iSCSI", "3306": "MySQL", "5672": "AMQP", "8082": "murano"} def __init__(self, services=None): super(UrlParser, self).__init__() self.test_logs = {} self.services = services or self.services def addSuccess(self, test, details=None): output = test.shortDescription() or test.id() calls = self.parse_details(details) self.test_logs.update({output: calls}) def addSkip(self, test, err, details=None): output = test.shortDescription() or test.id() calls = self.parse_details(details) self.test_logs.update({output: calls}) def addError(self, test, err, details=None): output = test.shortDescription() or test.id() calls = self.parse_details(details) self.test_logs.update({output: calls}) def addFailure(self, test, err, details=None): output = test.shortDescription() or test.id() calls = self.parse_details(details) self.test_logs.update({output: calls}) def stopTestRun(self): super(UrlParser, self).stopTestRun() def startTestRun(self): super(UrlParser, self).startTestRun() def parse_details(self, details): if details is None: return calls = [] for _, detail in details.items(): in_request = False in_response = False current_call = {} for line in detail.as_text().split("\n"): url_match = self.url_re.match(line) request_match = self.request_re.match(line) response_match = self.response_re.match(line) body_match = self.body_re.match(line) if url_match is not None: if current_call != {}: calls.append(current_call.copy()) current_call = {} in_request, in_response = False, False current_call.update({ "name": url_match.group("name"), "verb": url_match.group("verb"), "status_code": url_match.group("code"), "service": self.get_service(url_match.group("url")), "url": self.url_path(url_match.group("url"))}) elif request_match is not None: in_request, in_response = True, False current_call.update( {"request_headers": request_match.group("headers")}) elif in_request and body_match is not None: in_request = False current_call.update( {"request_body": body_match.group( "body")}) elif response_match is not None: in_request, in_response = False, True current_call.update( {"response_headers": response_match.group( "headers")}) elif in_response and body_match is not None: in_response = False current_call.update( {"response_body": body_match.group("body")}) if current_call != {}: calls.append(current_call.copy()) return calls def get_service(self, url): match = self.port_re.match(url) if match is not None: return self.services.get(match.group("port"), "Unknown") return "Unknown" def url_path(self, url): match = self.path_re.match(url) if match is not None: path = match.group("path") path = self.uuid_re.sub(r'\1\2', path) path = self.ip_re.sub(r'\1\2', path) path = self.id_re.sub(r'\1\2', path) return path return url class FileAccumulator(testtools.StreamResult): def __init__(self, non_subunit_name='pythonlogging'): super(FileAccumulator, self).__init__() self.route_codes = collections.defaultdict(io.BytesIO) self.non_subunit_name = non_subunit_name def status(self, **kwargs): if kwargs.get('file_name') != self.non_subunit_name: return file_bytes = kwargs.get('file_bytes') if not file_bytes: return route_code = kwargs.get('route_code') stream = self.route_codes[route_code] stream.write(file_bytes) class ArgumentParser(argparse.ArgumentParser): def __init__(self): desc = "Outputs all HTTP calls a given test made that were logged." super(ArgumentParser, self).__init__(description=desc) self.prog = "subunit-describe-calls" self.add_argument( "-s", "--subunit", metavar="", nargs="?", type=argparse.FileType('rb'), default=sys.stdin, help="The path to the subunit output file.") self.add_argument( "-n", "--non-subunit-name", metavar="", default="pythonlogging", help="The name used in subunit to describe the file contents.") self.add_argument( "-o", "--output-file", metavar="", default=None, help="The output file name for the json.") self.add_argument( "-p", "--ports", metavar="", default=None, help="A JSON file describing the ports for each service.") def parse(stream, non_subunit_name, ports): if ports is not None and os.path.exists(ports): ports = json.loads(open(ports).read()) url_parser = UrlParser(ports) suite = subunit.ByteStreamToStreamResult( stream, non_subunit_name=non_subunit_name) result = testtools.StreamToExtendedDecorator(url_parser) accumulator = FileAccumulator(non_subunit_name) result = testtools.StreamResultRouter(result) result.add_rule(accumulator, 'test_id', test_id=None) result.startTestRun() suite.run(result) for bytes_io in accumulator.route_codes.values(): # v1 processing bytes_io.seek(0) suite = subunit.ProtocolTestCase(bytes_io) suite.run(url_parser) result.stopTestRun() return url_parser def output(url_parser, output_file): if output_file is not None: with open(output_file, "w") as outfile: outfile.write(json.dumps(url_parser.test_logs)) return for test_name in url_parser.test_logs: items = url_parser.test_logs[test_name] sys.stdout.write('{0}\n'.format(test_name)) if not items: sys.stdout.write('\n') continue for item in items: sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format( item.get('status_code'), item.get('verb'), item.get('service'), item.get('url'))) sys.stdout.write('\n') def entry_point(): cl_args = ArgumentParser().parse_args() parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports) output(parser, cl_args.output_file) if __name__ == "__main__": entry_point() tempest-17.2.0/tempest/cmd/list_plugins.py0000666000175100017510000000241113207044712020620 0ustar zuulzuul00000000000000#!/usr/bin/env python # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Utility for listing all currently installed Tempest plugins. **Usage:** ``tempest list-plugins``. """ from cliff import command import prettytable from tempest.test_discover import plugins as plg class TempestListPlugins(command.Command): def take_action(self, parsed_args): self._list_plugins() def get_description(self): return 'List all tempest plugins' def _list_plugins(self): plugins = plg.TempestTestPluginManager() output = prettytable.PrettyTable(["Name", "EntryPoint"]) for plugin in plugins.ext_plugins.extensions: output.add_row([ plugin.name, plugin.entry_point_target]) print(output) tempest-17.2.0/tempest/cmd/config-generator.tempest.conf0000666000175100017510000000017513207044712023317 0ustar zuulzuul00000000000000[DEFAULT] output_file = etc/tempest.conf.sample namespace = tempest.config namespace = oslo.concurrency namespace = oslo.log tempest-17.2.0/tempest/cmd/run.py0000666000175100017510000003666513207044712016732 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Runs tempest tests This command is used for running the tempest tests Test Selection ============== Tempest run has several options: * **--regex/-r**: This is a selection regex like what testr uses. It will run any tests that match on re.match() with the regex * **--smoke/-s**: Run all the tests tagged as smoke There are also the **--blacklist-file** and **--whitelist-file** options that let you pass a filepath to tempest run with the file format being a line separated regex, with '#' used to signify the start of a comment on a line. For example:: # Regex file ^regex1 # Match these tests .*regex2 # Match those tests The blacklist file will be used to construct a negative lookahead regex and the whitelist file will simply OR all the regexes in the file. The whitelist and blacklist file options are mutually exclusive so you can't use them together. However, you can combine either with a normal regex or the *--smoke* flag. When used with a blacklist file the generated regex will be combined to something like:: ^((?!black_regex1|black_regex2).)*$cli_regex1 When combined with a whitelist file all the regexes from the file and the CLI regexes will be ORed. You can also use the **--list-tests** option in conjunction with selection arguments to list which tests will be run. You can also use the **--load-list** option that lets you pass a filepath to tempest run with the file format being in a non-regex format, similar to the tests generated by the **--list-tests** option. You can specify target tests by removing unnecessary tests from a list file which is generated from **--list-tests** option. Test Execution ============== There are several options to control how the tests are executed. By default tempest will run in parallel with a worker for each CPU present on the machine. If you want to adjust the number of workers use the **--concurrency** option and if you want to run tests serially use **--serial/-t** Running with Workspaces ----------------------- Tempest run enables you to run your tempest tests from any setup tempest workspace it relies on you having setup a tempest workspace with either the ``tempest init`` or ``tempest workspace`` commands. Then using the ``--workspace`` CLI option you can specify which one of your workspaces you want to run tempest from. Using this option you don't have to run Tempest directly with you current working directory being the workspace, Tempest will take care of managing everything to be executed from there. Running from Anywhere --------------------- Tempest run provides you with an option to execute tempest from anywhere on your system. You are required to provide a config file in this case with the ``--config-file`` option. When run tempest will create a .testrepository directory and a .testr.conf file in your current working directory. This way you can use testr commands directly to inspect the state of the previous run. Test Output =========== By default tempest run's output to STDOUT will be generated using the subunit-trace output filter. But, if you would prefer a subunit v2 stream be output to STDOUT use the **--subunit** flag Combining Runs ============== There are certain situations in which you want to split a single run of tempest across 2 executions of tempest run. (for example to run part of the tests serially and others in parallel) To accomplish this but still treat the results as a single run you can leverage the **--combine** option which will append the current run's results with the previous runs. """ import io import os import sys import tempfile import threading from cliff import command from os_testr import regex_builder from os_testr import subunit_trace from oslo_serialization import jsonutils as json import six from testrepository.commands import run_argv from tempest import clients from tempest.cmd import cleanup_service from tempest.cmd import init from tempest.cmd import workspace from tempest.common import credentials_factory as credentials from tempest import config CONF = config.CONF SAVED_STATE_JSON = "saved_state.json" class TempestRun(command.Command): def _set_env(self, config_file=None): if config_file: CONF.set_config_path(os.path.abspath(config_file)) # NOTE(mtreinish): This is needed so that testr doesn't gobble up any # stacktraces on failure. if 'TESTR_PDB' in os.environ: return else: os.environ["TESTR_PDB"] = "" # NOTE(dims): most of our .testr.conf try to test for PYTHON # environment variable and fall back to "python", under python3 # if it does not exist. we should set it to the python3 executable # to deal with this situation better for now. if six.PY3 and 'PYTHON' not in os.environ: os.environ['PYTHON'] = sys.executable def _create_testrepository(self): if not os.path.isdir('.testrepository'): returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout, sys.stderr) if returncode: sys.exit(returncode) def _create_testr_conf(self): top_level_path = os.path.dirname(os.path.dirname(__file__)) discover_path = os.path.join(top_level_path, 'test_discover') file_contents = init.TESTR_CONF % (top_level_path, discover_path) with open('.testr.conf', 'w+') as testr_conf_file: testr_conf_file.write(file_contents) def take_action(self, parsed_args): returncode = 0 if parsed_args.config_file: self._set_env(parsed_args.config_file) else: self._set_env() # Workspace execution mode if parsed_args.workspace: workspace_mgr = workspace.WorkspaceManager( parsed_args.workspace_path) path = workspace_mgr.get_workspace(parsed_args.workspace) if not path: sys.exit( "The %r workspace isn't registered in " "%r. Use 'tempest init' to " "register the workspace." % (parsed_args.workspace, workspace_mgr.path)) os.chdir(path) # NOTE(mtreinish): tempest init should create a .testrepository dir # but since workspaces can be imported let's sanity check and # ensure that one is created self._create_testrepository() # Local execution mode elif os.path.isfile('.testr.conf'): # If you're running in local execution mode and there is not a # testrepository dir create one self._create_testrepository() # local execution with config file mode elif parsed_args.config_file: self._create_testr_conf() self._create_testrepository() else: print("No .testr.conf file was found for local execution") sys.exit(2) if parsed_args.state: self._init_state() else: pass if parsed_args.combine: temp_stream = tempfile.NamedTemporaryFile() return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin, temp_stream, sys.stderr) if return_code > 0: sys.exit(return_code) regex = self._build_regex(parsed_args) if parsed_args.list_tests: argv = ['tempest', 'list-tests', regex] returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr) else: options = self._build_options(parsed_args) returncode = self._run(regex, options) if returncode > 0: sys.exit(returncode) if parsed_args.combine: return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin, temp_stream, sys.stderr) if return_code > 0: sys.exit(return_code) returncode = run_argv(['tempest', 'load', temp_stream.name], sys.stdin, sys.stdout, sys.stderr) sys.exit(returncode) def get_description(self): return 'Run tempest' def _init_state(self): print("Initializing saved state.") data = {} self.global_services = cleanup_service.get_global_cleanup_services() self.admin_mgr = clients.Manager( credentials.get_configured_admin_credentials()) admin_mgr = self.admin_mgr kwargs = {'data': data, 'is_dry_run': False, 'saved_state_json': data, 'is_preserve': False, 'is_save_state': True} for service in self.global_services: svc = service(admin_mgr, **kwargs) svc.run() with open(SAVED_STATE_JSON, 'w+') as f: f.write(json.dumps(data, sort_keys=True, indent=2, separators=(',', ': '))) def get_parser(self, prog_name): parser = super(TempestRun, self).get_parser(prog_name) parser = self._add_args(parser) return parser def _add_args(self, parser): # workspace args parser.add_argument('--workspace', default=None, help='Name of tempest workspace to use for running' ' tests. You can see a list of workspaces ' 'with tempest workspace list') parser.add_argument('--workspace-path', default=None, dest='workspace_path', help="The path to the workspace file, the default " "is ~/.tempest/workspace.yaml") # Configuration flags parser.add_argument('--config-file', default=None, dest='config_file', help='Configuration file to run tempest with') # test selection args regex = parser.add_mutually_exclusive_group() regex.add_argument('--smoke', '-s', action='store_true', help="Run the smoke tests only") regex.add_argument('--regex', '-r', default='', help='A normal testr selection regex used to ' 'specify a subset of tests to run') list_selector = parser.add_mutually_exclusive_group() list_selector.add_argument('--whitelist-file', '--whitelist_file', help="Path to a whitelist file, this file " "contains a separate regex on each " "newline.") list_selector.add_argument('--blacklist-file', '--blacklist_file', help='Path to a blacklist file, this file ' 'contains a separate regex exclude on ' 'each newline') list_selector.add_argument('--load-list', '--load_list', help='Path to a non-regex whitelist file, ' 'this file contains a seperate test ' 'on each newline. This command' 'supports files created by the tempest' 'run ``--list-tests`` command') # list only args parser.add_argument('--list-tests', '-l', action='store_true', help='List tests', default=False) # execution args parser.add_argument('--concurrency', '-w', help="The number of workers to use, defaults to " "the number of cpus") parallel = parser.add_mutually_exclusive_group() parallel.add_argument('--parallel', dest='parallel', action='store_true', help='Run tests in parallel (this is the' ' default)') parallel.add_argument('--serial', '-t', dest='parallel', action='store_false', help='Run tests serially') parser.add_argument('--save-state', dest='state', action='store_true', help="To save the state of the cloud before " "running tempest.") # output args parser.add_argument("--subunit", action='store_true', help='Enable subunit v2 output') parser.add_argument("--combine", action='store_true', help='Combine the output of this run with the ' "previous run's as a combined stream in the " "testr repository after it finish") parser.set_defaults(parallel=True) return parser def _build_regex(self, parsed_args): regex = '' if parsed_args.smoke: regex = 'smoke' elif parsed_args.regex: regex = parsed_args.regex if parsed_args.whitelist_file or parsed_args.blacklist_file: regex = regex_builder.construct_regex(parsed_args.blacklist_file, parsed_args.whitelist_file, regex, False) return regex def _build_options(self, parsed_args): options = [] if parsed_args.subunit: options.append("--subunit") if parsed_args.parallel: options.append("--parallel") if parsed_args.concurrency: options.append("--concurrency=%s" % parsed_args.concurrency) if parsed_args.load_list: options.append("--load-list=%s" % parsed_args.load_list) return options def _run(self, regex, options): returncode = 0 argv = ['tempest', 'run', regex] + options if '--subunit' in options: returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr) else: argv.append('--subunit') stdin = io.StringIO() stdout_r, stdout_w = os.pipe() subunit_w = os.fdopen(stdout_w, 'wt') subunit_r = os.fdopen(stdout_r) returncodes = {} def run_argv_thread(): returncodes['testr'] = run_argv(argv, stdin, subunit_w, sys.stderr) subunit_w.close() run_thread = threading.Thread(target=run_argv_thread) run_thread.start() returncodes['subunit-trace'] = subunit_trace.trace( subunit_r, sys.stdout, post_fails=True, print_failures=True) run_thread.join() subunit_r.close() # python version of pipefail if returncodes['testr']: returncode = returncodes['testr'] elif returncodes['subunit-trace']: returncode = returncodes['subunit-trace'] return returncode tempest-17.2.0/tempest/cmd/workspace.py0000666000175100017510000002057013207044712020110 0ustar zuulzuul00000000000000# Copyright 2016 Rackspace # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Manages Tempest workspaces This command is used for managing tempest workspaces Commands ======== list ---- Outputs the name and path of all known tempest workspaces register -------- Registers a new tempest workspace via a given --name and --path rename ------ Renames a tempest workspace from --old-name to --new-name move ---- Changes the path of a given tempest workspace --name to --path remove ------ Deletes the entry for a given tempest workspace --name --rmdir Deletes the given tempest workspace directory General Options =============== **--workspace_path**: Allows the user to specify a different location for the workspace.yaml file containing the workspace definitions instead of ~/.tempest/workspace.yaml """ import os import shutil import sys from cliff import command from cliff import lister from oslo_concurrency import lockutils import yaml from tempest import config CONF = config.CONF class WorkspaceManager(object): def __init__(self, path=None): lockutils.get_lock_path(CONF) self.path = path or os.path.join( os.path.expanduser("~"), ".tempest", "workspace.yaml") if not os.path.isdir(os.path.dirname(self.path)): os.makedirs(self.path.rsplit(os.path.sep, 1)[0]) self.workspaces = {} @lockutils.synchronized('workspaces', external=True) def get_workspace(self, name): """Returns the workspace that has the given name If the workspace isn't registered then `None` is returned. """ self._populate() return self.workspaces.get(name) @lockutils.synchronized('workspaces', external=True) def rename_workspace(self, old_name, new_name): self._populate() self._name_exists(old_name) self._workspace_name_exists(new_name) self.workspaces[new_name] = self.workspaces.pop(old_name) self._write_file() @lockutils.synchronized('workspaces', external=True) def move_workspace(self, name, path): self._populate() path = os.path.abspath(os.path.expanduser(path)) self._name_exists(name) self._validate_path(path) self.workspaces[name] = path self._write_file() def _name_exists(self, name): if name not in self.workspaces: print("A workspace was not found with name: {0}".format(name)) sys.exit(1) @lockutils.synchronized('workspaces', external=True) def remove_workspace_entry(self, name): self._populate() self._name_exists(name) workspace_path = self.workspaces.pop(name) self._write_file() return workspace_path @lockutils.synchronized('workspaces', external=True) def remove_workspace_directory(self, workspace_path): shutil.rmtree(workspace_path) @lockutils.synchronized('workspaces', external=True) def list_workspaces(self): self._populate() self._validate_workspaces() return self.workspaces def _workspace_name_exists(self, name): if name in self.workspaces: print("A workspace already exists with name: {0}.".format( name)) sys.exit(1) def _validate_path(self, path): if not os.path.exists(path): print("Path does not exist.") sys.exit(1) @lockutils.synchronized('workspaces', external=True) def register_new_workspace(self, name, path, init=False): """Adds the new workspace and writes out the new workspace config""" self._populate() path = os.path.abspath(os.path.expanduser(path)) # This only happens when register is called from outside of init if not init: self._validate_path(path) self._workspace_name_exists(name) self.workspaces[name] = path self._write_file() def _validate_workspaces(self): if self.workspaces is not None: self.workspaces = {n: p for n, p in self.workspaces.items() if os.path.exists(p)} self._write_file() def _write_file(self): with open(self.path, 'w') as f: f.write(yaml.dump(self.workspaces)) def _populate(self): if not os.path.isfile(self.path): return with open(self.path, 'r') as f: self.workspaces = yaml.safe_load(f) or {} def add_global_arguments(parser): parser.add_argument( '--workspace-path', required=False, default=None, help="The path to the workspace file, the default is " "~/.tempest/workspace.yaml") return parser class TempestWorkspaceRegister(command.Command): def get_description(self): return ('Registers a new tempest workspace via a given ' '--name and --path') def get_parser(self, prog_name): parser = super(TempestWorkspaceRegister, self).get_parser(prog_name) add_global_arguments(parser) parser.add_argument('--name', required=True) parser.add_argument('--path', required=True) return parser def take_action(self, parsed_args): self.manager = WorkspaceManager(parsed_args.workspace_path) self.manager.register_new_workspace(parsed_args.name, parsed_args.path) sys.exit(0) class TempestWorkspaceRename(command.Command): def get_description(self): return 'Renames a tempest workspace from --old-name to --new-name' def get_parser(self, prog_name): parser = super(TempestWorkspaceRename, self).get_parser(prog_name) add_global_arguments(parser) parser.add_argument('--old-name', required=True) parser.add_argument('--new-name', required=True) return parser def take_action(self, parsed_args): self.manager = WorkspaceManager(parsed_args.workspace_path) self.manager.rename_workspace( parsed_args.old_name, parsed_args.new_name) sys.exit(0) class TempestWorkspaceMove(command.Command): def get_description(self): return 'Changes the path of a given tempest workspace --name to --path' def get_parser(self, prog_name): parser = super(TempestWorkspaceMove, self).get_parser(prog_name) add_global_arguments(parser) parser.add_argument('--name', required=True) parser.add_argument('--path', required=True) return parser def take_action(self, parsed_args): self.manager = WorkspaceManager(parsed_args.workspace_path) self.manager.move_workspace(parsed_args.name, parsed_args.path) sys.exit(0) class TempestWorkspaceRemove(command.Command): def get_description(self): return 'Deletes the entry for a given tempest workspace --name' def get_parser(self, prog_name): parser = super(TempestWorkspaceRemove, self).get_parser(prog_name) add_global_arguments(parser) parser.add_argument('--name', required=True) parser.add_argument('--rmdir', action='store_true', help='Deletes the given workspace directory') return parser def take_action(self, parsed_args): self.manager = WorkspaceManager(parsed_args.workspace_path) workspace_path = self.manager.remove_workspace_entry(parsed_args.name) if parsed_args.rmdir: self.manager.remove_workspace_directory(workspace_path) sys.exit(0) class TempestWorkspaceList(lister.Lister): def get_description(self): return 'Outputs the name and path of all known tempest workspaces' def get_parser(self, prog_name): parser = super(TempestWorkspaceList, self).get_parser(prog_name) add_global_arguments(parser) return parser def take_action(self, parsed_args): self.manager = WorkspaceManager(parsed_args.workspace_path) return (("Name", "Path"), ((n, p) for n, p in self.manager.list_workspaces().items())) tempest-17.2.0/tempest/config.py0000666000175100017510000020045313207044712016614 0ustar zuulzuul00000000000000# 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 __future__ import print_function import os import tempfile from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import log as logging from tempest.lib import exceptions from tempest.lib.services import clients from tempest.test_discover import plugins # TODO(marun) Replace use of oslo_config's global ConfigOpts # (cfg.CONF) instance with a local instance (cfg.ConfigOpts()) once # the cli tests move to the clients. The cli tests rely on oslo # incubator modules that use the global cfg.CONF. _CONF = cfg.CONF def register_opt_group(conf, opt_group, options): if opt_group: conf.register_group(opt_group) for opt in options: conf.register_opt(opt, group=getattr(opt_group, 'name', None)) auth_group = cfg.OptGroup(name='auth', title="Options for authentication and credentials") AuthGroup = [ cfg.StrOpt('test_accounts_file', help="Path to the yaml file that contains the list of " "credentials to use for running tests. If used when " "running in parallel you have to make sure sufficient " "credentials are provided in the accounts file. For " "example if no tests with roles are being run it requires " "at least `2 * CONC` distinct accounts configured in " " the `test_accounts_file`, with CONC == the " "number of concurrent test processes."), cfg.BoolOpt('use_dynamic_credentials', default=True, help="Allows test cases to create/destroy projects and " "users. This option requires that OpenStack Identity " "API admin credentials are known. If false, isolated " "test cases and parallel execution, can still be " "achieved configuring a list of test accounts", deprecated_opts=[cfg.DeprecatedOpt('allow_tenant_isolation', group='auth'), cfg.DeprecatedOpt('allow_tenant_isolation', group='compute'), cfg.DeprecatedOpt('allow_tenant_isolation', group='orchestration')]), cfg.ListOpt('tempest_roles', help="Roles to assign to all users created by tempest", default=[]), cfg.StrOpt('default_credentials_domain_name', default='Default', help="Default domain used when getting v3 credentials. " "This is the name keystone uses for v2 compatibility.", deprecated_opts=[cfg.DeprecatedOpt( 'tenant_isolation_domain_name', group='auth')]), cfg.BoolOpt('create_isolated_networks', default=True, help="If use_dynamic_credentials is set to True and Neutron " "is enabled Tempest will try to create a usable network, " "subnet, and router when needed for each project it " "creates. However in some neutron configurations, like " "with VLAN provider networks, this doesn't work. So if " "set to False the isolated networks will not be created"), cfg.StrOpt('admin_username', help="Username for an administrative user. This is needed for " "authenticating requests made by project isolation to " "create users and projects", deprecated_group='identity'), cfg.StrOpt('admin_project_name', help="Project name to use for an administrative user. This is " "needed for authenticating requests made by project " "isolation to create users and projects", deprecated_opts=[cfg.DeprecatedOpt('admin_tenant_name', group='auth'), cfg.DeprecatedOpt('admin_tenant_name', group='identity')]), cfg.StrOpt('admin_password', help="Password to use for an administrative user. This is " "needed for authenticating requests made by project " "isolation to create users and projects", secret=True, deprecated_group='identity'), cfg.StrOpt('admin_domain_name', help="Admin domain name for authentication (Keystone V3)." "The same domain applies to user and project", deprecated_group='identity'), ] identity_group = cfg.OptGroup(name='identity', title="Keystone Configuration Options") IdentityGroup = [ cfg.StrOpt('catalog_type', default='identity', help="Catalog type of the Identity service."), cfg.BoolOpt('disable_ssl_certificate_validation', default=False, help="Set to True if using self-signed SSL certificates."), cfg.StrOpt('ca_certificates_file', default=None, help='Specify a CA bundle file to use in verifying a ' 'TLS (https) server certificate.'), cfg.StrOpt('uri', help="Full URI of the OpenStack Identity API (Keystone), v2"), cfg.StrOpt('uri_v3', help='Full URI of the OpenStack Identity API (Keystone), v3'), cfg.StrOpt('auth_version', default='v3', help="Identity API version to be used for authentication " "for API tests."), cfg.StrOpt('region', default='RegionOne', help="The identity region name to use. Also used as the other " "services' region name unless they are set explicitly. " "If no such region is found in the service catalog, the " "first found one is used."), cfg.StrOpt('v2_admin_endpoint_type', default='adminURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The admin endpoint type to use for OpenStack Identity " "(Keystone) API v2"), cfg.StrOpt('v2_public_endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The public endpoint type to use for OpenStack Identity " "(Keystone) API v2", deprecated_opts=[cfg.DeprecatedOpt('endpoint_type', group='identity')]), cfg.StrOpt('v3_endpoint_type', default='adminURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for OpenStack Identity " "(Keystone) API v3. The default value adminURL is " "deprecated and will be modified to publicURL in " "the next release."), cfg.StrOpt('admin_role', default='admin', help="Role required to administrate keystone."), cfg.StrOpt('default_domain_id', default='default', help="ID of the default domain"), cfg.BoolOpt('admin_domain_scope', default=False, help="Whether keystone identity v3 policy required " "a domain scoped token to use admin APIs"), # Security Compliance (PCI-DSS) cfg.IntOpt('user_lockout_failure_attempts', default=2, help="The number of unsuccessful login attempts the user is " "allowed before having the account locked."), cfg.IntOpt('user_lockout_duration', default=5, help="The number of seconds a user account will remain " "locked."), cfg.IntOpt('user_unique_last_password_count', default=2, help="The number of passwords for a user that must be unique " "before an old password can be reused."), ] service_clients_group = cfg.OptGroup(name='service-clients', title="Service Clients Options") ServiceClientsGroup = [ cfg.IntOpt('http_timeout', default=60, help='Timeout in seconds to wait for the http request to ' 'return'), cfg.StrOpt('proxy_url', help='Specify an http proxy to use.') ] identity_feature_group = cfg.OptGroup(name='identity-feature-enabled', title='Enabled Identity Features') IdentityFeatureGroup = [ cfg.BoolOpt('trust', default=True, help='Does the identity service have delegation and ' 'impersonation enabled'), cfg.BoolOpt('api_v2', default=False, help='Is the v2 identity API enabled', deprecated_for_removal=True, deprecated_reason='The identity v2.0 API was removed in the ' 'Queens release. Tests that exercise the ' 'v2.0 API will be removed from tempest in ' 'the v22.0.0 release. They are kept only to ' 'test stable branches.'), cfg.BoolOpt('api_v2_admin', default=True, help="Is the v2 identity admin API available? This setting " "only applies if api_v2 is set to True."), cfg.BoolOpt('api_v3', default=True, help='Is the v3 identity API enabled'), cfg.ListOpt('api_extensions', default=['all'], help="A list of enabled identity extensions with a special " "entry all which indicates every extension is enabled. " "Empty list indicates all extensions are disabled. " "To get the list of extensions run: " "'openstack extension list --identity'"), # TODO(rodrigods): This is a feature flag for bug 1590578 which is fixed # in Newton and Ocata. This option can be removed after Mitaka is end of # life. cfg.BoolOpt('forbid_global_implied_dsr', default=False, help='Does the environment forbid global roles implying ' 'domain specific ones?', deprecated_for_removal=True, deprecated_reason="This feature flag was introduced to " "support testing of old OpenStack versions, " "which are not supported anymore"), cfg.BoolOpt('domain_specific_drivers', default=False, help='Are domain specific drivers enabled? ' 'This configuration value should be same as ' '[identity]->domain_specific_drivers_enabled ' 'in keystone.conf.'), cfg.BoolOpt('security_compliance', default=False, help='Does the environment have the security compliance ' 'settings enabled?') ] compute_group = cfg.OptGroup(name='compute', title='Compute Service Options') ComputeGroup = [ cfg.StrOpt('image_ref', help="Valid primary image reference to be used in tests. " "This is a required option"), cfg.StrOpt('image_ref_alt', help="Valid secondary image reference to be used in tests. " "This is a required option, but if only one image is " "available duplicate the value of image_ref above"), cfg.StrOpt('flavor_ref', default="1", help="Valid primary flavor to use in tests."), cfg.StrOpt('flavor_ref_alt', default="2", help='Valid secondary flavor to be used in tests.'), cfg.IntOpt('build_interval', default=1, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for an instance to build. " "Other services that do not define build_timeout will " "inherit this value."), cfg.IntOpt('ready_wait', default=0, help="Additional wait time for clean state, when there is " "no OS-EXT-STS extension available"), cfg.StrOpt('fixed_network_name', help="Name of the fixed network that is visible to all test " "projects. If multiple networks are available for a " "project, this is the network which will be used for " "creating servers if tempest does not create a network or " "a network is not specified elsewhere. It may be used for " "ssh validation only if floating IPs are disabled."), cfg.StrOpt('catalog_type', default='compute', help="Catalog type of the Compute service."), cfg.StrOpt('region', default='', help="The compute region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the compute service."), cfg.StrOpt('volume_device_name', default='vdb', help="Expected device name when a volume is attached to " "an instance. Not all hypervisors guarantee that they " "will respect the user defined device name, tests may " "fail if inappropriate device name is set."), cfg.IntOpt('shelved_offload_time', default=0, help='Time in seconds before a shelved instance is eligible ' 'for removing from a host. -1 never offload, 0 offload ' 'when shelved. This time should be the same as the time ' 'of nova.conf, and some tests will run for as long as the ' 'time.'), cfg.IntOpt('min_compute_nodes', default=1, help=('The minimum number of compute nodes expected. This will ' 'be utilized by some multinode specific tests to ensure ' 'that requests match the expected size of the cluster ' 'you are testing with.')), cfg.StrOpt('hypervisor_type', default=None, help="Hypervisor type of the test target on heterogeneous " "compute environment. The value can be 'QEMU', 'xen' or " "something."), cfg.StrOpt('min_microversion', default=None, help="Lower version of the test target microversion range. " "The format is 'X.Y', where 'X' and 'Y' are int values. " "Tempest selects tests based on the range between " "min_microversion and max_microversion. " "If both values are not specified, Tempest avoids tests " "which require a microversion. Valid values are string " "with format 'X.Y' or string 'latest'"), cfg.StrOpt('max_microversion', default=None, help="Upper version of the test target microversion range. " "The format is 'X.Y', where 'X' and 'Y' are int values. " "Tempest selects tests based on the range between " "min_microversion and max_microversion. " "If both values are not specified, Tempest avoids tests " "which require a microversion. Valid values are string " "with format 'X.Y' or string 'latest'"), ] compute_features_group = cfg.OptGroup(name='compute-feature-enabled', title="Enabled Compute Service Features") ComputeFeaturesGroup = [ cfg.BoolOpt('disk_config', default=True, help="If false, skip disk config tests"), cfg.ListOpt('api_extensions', default=['all'], help='A list of enabled compute extensions with a special ' 'entry all which indicates every extension is enabled. ' 'Each extension should be specified with alias name. ' 'Empty list indicates all extensions are disabled', deprecated_for_removal=True, deprecated_reason='The Nova extensions API and mechanism ' 'is deprecated. This option will be ' 'removed when all releases supported ' 'by tempest no longer contain the Nova ' 'extensions API and mechanism.'), cfg.BoolOpt('change_password', default=False, help="Does the test environment support changing the admin " "password?"), cfg.BoolOpt('console_output', default=True, help="Does the test environment support obtaining instance " "serial console output?"), cfg.BoolOpt('resize', default=False, help="Does the test environment support resizing? When you " "enable this feature, 'flavor_ref_alt' should be set and " "it should refer to a larger flavor than 'flavor_ref' " "one."), cfg.BoolOpt('pause', default=True, help="Does the test environment support pausing?"), cfg.BoolOpt('shelve', default=True, help="Does the test environment support shelving/unshelving?"), cfg.BoolOpt('suspend', default=True, help="Does the test environment support suspend/resume?"), cfg.BoolOpt('cold_migration', default=True, help="Does the test environment support cold migration?"), cfg.BoolOpt('live_migration', default=True, help="Does the test environment support live migration?"), cfg.BoolOpt('live_migrate_back_and_forth', default=False, help="Does the test environment support live migrating " "VM back and forth between different versions of " "nova-compute?"), cfg.BoolOpt('metadata_service', default=True, help="Does the test environment support metadata service? " "Ignored unless validation.run_validation=true."), cfg.BoolOpt('block_migration_for_live_migration', default=False, help="Does the test environment use block devices for live " "migration"), cfg.BoolOpt('block_migrate_cinder_iscsi', default=False, help="Does the test environment support block migration with " "Cinder iSCSI volumes. Note: libvirt >= 1.2.17 is required " "to support this if using the libvirt compute driver."), cfg.BoolOpt('vnc_console', default=False, help='Enable VNC console. This configuration value should ' 'be same as [nova.vnc]->vnc_enabled in nova.conf'), cfg.BoolOpt('spice_console', default=False, help='Enable Spice console. This configuration value should ' 'be same as [nova.spice]->enabled in nova.conf'), cfg.BoolOpt('rdp_console', default=False, help='Enable RDP console. This configuration value should ' 'be same as [nova.rdp]->enabled in nova.conf'), cfg.BoolOpt('serial_console', default=False, help='Enable serial console. This configuration value ' 'should be the same as [nova.serial_console]->enabled ' 'in nova.conf'), cfg.BoolOpt('rescue', default=True, help='Does the test environment support instance rescue ' 'mode?'), cfg.BoolOpt('enable_instance_password', default=True, help='Enables returning of the instance password by the ' 'relevant server API calls such as create, rebuild ' 'or rescue. This configuration value should be same as ' 'nova.conf: DEFAULT.enable_instance_password'), cfg.BoolOpt('interface_attach', default=True, help='Does the test environment support dynamic network ' 'interface attachment?'), cfg.BoolOpt('snapshot', default=True, help='Does the test environment support creating snapshot ' 'images of running instances?'), cfg.BoolOpt('nova_cert', default=False, help='Does the test environment have the nova cert running?', deprecated_for_removal=True, deprecated_reason="On Nova side, the nova-cert service is " "deprecated and the service will be removed " "as early as Ocata."), cfg.BoolOpt('personality', default=False, help='Does the test environment support server personality'), cfg.BoolOpt('attach_encrypted_volume', default=True, help='Does the test environment support attaching an ' 'encrypted volume to a running server instance? This may ' 'depend on the combination of compute_driver in nova and ' 'the volume_driver(s) in cinder.'), 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 that are included " "with nova are enabled. Empty list indicates all filters " "are disabled. The full list of available filters is in " "nova.conf: DEFAULT.scheduler_available_filters. If the " "default value is overridden in nova.conf by the test " "environment (which means that a different set of " "filters is enabled than what is included in Nova by " "default) then, this option must be configured to " "contain the same filters that Nova uses in the test " "environment."), cfg.BoolOpt('swap_volume', default=False, help='Does the test environment support in-place swapping of ' 'volumes attached to a server instance?'), ] image_group = cfg.OptGroup(name='image', title="Image Service Options") ImageGroup = [ cfg.StrOpt('catalog_type', default='image', help='Catalog type of the Image service.'), cfg.StrOpt('region', default='', help="The image region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the image service."), cfg.StrOpt('http_image', default='http://download.cirros-cloud.net/0.3.1/' 'cirros-0.3.1-x86_64-uec.tar.gz', help='http accessible image'), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for an image to " "become available."), cfg.IntOpt('build_interval', default=1, help="Time in seconds between image operation status " "checks."), cfg.ListOpt('container_formats', default=['ami', 'ari', 'aki', 'bare', 'ovf', 'ova'], help="A list of image's container formats " "users can specify."), cfg.ListOpt('disk_formats', default=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso', 'vhdx'], help="A list of image's disk formats " "users can specify.") ] image_feature_group = cfg.OptGroup(name='image-feature-enabled', title='Enabled image service features') ImageFeaturesGroup = [ cfg.BoolOpt('api_v2', default=True, help="Is the v2 image API enabled", deprecated_for_removal=True, deprecated_reason='Glance v1 APIs are deprecated and v2 APIs ' 'are current one. In future, Tempest will ' 'test v2 APIs only so this config option ' 'will be removed.'), cfg.BoolOpt('api_v1', default=True, help="Is the v1 image API enabled", deprecated_for_removal=True, deprecated_reason='Glance v1 APIs are deprecated and v2 APIs ' 'are current one. In future, Tempest will ' 'test v2 APIs only so this config option ' 'will be removed.'), cfg.BoolOpt('deactivate_image', default=False, help="Is the deactivate-image feature enabled." " The feature has been integrated since Kilo.", deprecated_for_removal=True, deprecated_reason="All supported versions of OpenStack now " "support the 'deactivate_image' feature"), ] network_group = cfg.OptGroup(name='network', title='Network Service Options') NetworkGroup = [ cfg.StrOpt('catalog_type', default='network', help='Catalog type of the Neutron service.'), cfg.StrOpt('region', default='', help="The network region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the network service."), cfg.StrOpt('project_network_cidr', default="10.100.0.0/16", help="The cidr block to allocate project ipv4 subnets from"), cfg.IntOpt('project_network_mask_bits', default=28, help="The mask bits for project ipv4 subnets"), cfg.StrOpt('project_network_v6_cidr', default="2003::/48", help="The cidr block to allocate project ipv6 subnets from"), cfg.IntOpt('project_network_v6_mask_bits', default=64, help="The mask bits for project ipv6 subnets"), cfg.BoolOpt('project_networks_reachable', default=False, help="Whether project networks can be reached directly from " "the test client. This must be set to True when the " "'fixed' connect_method is selected."), cfg.StrOpt('public_network_id', default="", help="Id of the public network that provides external " "connectivity"), cfg.StrOpt('floating_network_name', help="Default floating network name. Used to allocate floating " "IPs when neutron is enabled."), cfg.StrOpt('public_router_id', default="", help="Id of the public router that provides external " "connectivity. This should only be used when Neutron's " "'allow_overlapping_ips' is set to 'False' in " "neutron.conf. usually not needed past 'Grizzly' release"), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for network operation to " "complete."), cfg.IntOpt('build_interval', default=1, help="Time in seconds between network operation status " "checks."), cfg.ListOpt('dns_servers', default=["8.8.8.8", "8.8.4.4"], help="List of dns servers which should be used" " for subnet creation"), cfg.StrOpt('port_vnic_type', choices=[None, 'normal', 'direct', 'macvtap'], help="vnic_type to use when Launching instances" " with pre-configured ports." " Supported ports are:" " ['normal','direct','macvtap']"), cfg.ListOpt('default_network', default=["1.0.0.0/16", "2.0.0.0/16"], help="List of ip pools" " for subnetpools creation"), cfg.BoolOpt('shared_physical_network', default=False, help="The environment does not support network separation " "between tenants."), ] network_feature_group = cfg.OptGroup(name='network-feature-enabled', title='Enabled network service features') NetworkFeaturesGroup = [ cfg.BoolOpt('ipv6', default=True, help="Allow the execution of IPv6 tests"), cfg.ListOpt('api_extensions', default=['all'], help="A list of enabled network extensions with a special " "entry all which indicates every extension is enabled. " "Empty list indicates all extensions are disabled. " "To get the list of extensions run: 'neutron ext-list'"), cfg.BoolOpt('ipv6_subnet_attributes', default=False, help="Allow the execution of IPv6 subnet tests that use " "the extended IPv6 attributes ipv6_ra_mode " "and ipv6_address_mode" ), cfg.BoolOpt('port_admin_state_change', default=True, help="Does the test environment support changing" " port admin state"), cfg.BoolOpt('port_security', default=False, help="Does the test environment support port security?"), cfg.BoolOpt('floating_ips', default=True, help='Does the test environment support floating_ips') ] validation_group = cfg.OptGroup(name='validation', title='SSH Validation options') ValidationGroup = [ cfg.BoolOpt('run_validation', default=False, help='Enable ssh on created servers and creation of additional' ' validation resources to enable remote access'), cfg.BoolOpt('security_group', default=True, help='Enable/disable security groups.'), cfg.BoolOpt('security_group_rules', default=True, help='Enable/disable security group rules.'), cfg.StrOpt('connect_method', default='floating', choices=['fixed', 'floating'], help='Default IP type used for validation: ' '-fixed: uses the first IP belonging to the fixed network ' '-floating: creates and uses a floating IP'), cfg.StrOpt('auth_method', default='keypair', choices=['keypair'], help='Default authentication method to the instance. ' 'Only ssh via keypair is supported for now. ' 'Additional methods will be handled in a separate spec.'), cfg.IntOpt('ip_version_for_ssh', default=4, help='Default IP version for ssh connections.'), cfg.IntOpt('ping_timeout', default=120, help='Timeout in seconds to wait for ping to succeed.'), cfg.IntOpt('connect_timeout', default=60, help='Timeout in seconds to wait for the TCP connection to be ' 'successful.'), cfg.IntOpt('ssh_timeout', default=300, help='Timeout in seconds to wait for the ssh banner.'), cfg.StrOpt('image_ssh_user', default="root", help="User name used to authenticate to an instance."), cfg.StrOpt('image_ssh_password', default="password", help="Password used to authenticate to an instance."), cfg.StrOpt('ssh_shell_prologue', default="set -eu -o pipefail; PATH=$$PATH:/sbin;", help="Shell fragments to use before executing a command " "when sshing to a guest."), cfg.IntOpt('ping_size', default=56, help="The packet size for ping packets originating " "from remote linux hosts"), cfg.IntOpt('ping_count', default=1, help="The number of ping packets originating from remote " "linux hosts"), cfg.StrOpt('floating_ip_range', default='10.0.0.0/29', help='Unallocated floating IP range, which will be used to ' 'test the floating IP bulk feature for CRUD operation. ' 'This block must not overlap an existing floating IP ' 'pool.'), cfg.StrOpt('network_for_ssh', default='public', help="Network used for SSH connections. Ignored if " "connect_method=floating."), ] volume_group = cfg.OptGroup(name='volume', title='Block Storage Options') VolumeGroup = [ cfg.IntOpt('build_interval', default=1, help='Time in seconds between volume availability checks.'), cfg.IntOpt('build_timeout', default=300, help='Timeout in seconds to wait for a volume to become ' 'available.'), cfg.StrOpt('catalog_type', default='volume', help="Catalog type of the Volume Service"), cfg.StrOpt('region', default='', help="The volume region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the volume service."), cfg.ListOpt('backend_names', default=['BACKEND_1', 'BACKEND_2'], help='A list of backend names separated by comma. ' 'The backend name must be declared in cinder.conf'), cfg.StrOpt('storage_protocol', default='iSCSI', help='Backend protocol to target when creating volume types'), cfg.StrOpt('vendor_name', default='Open Source', help='Backend vendor to target when creating volume types'), cfg.StrOpt('disk_format', default='raw', help='Disk format to use when copying a volume to image'), cfg.IntOpt('volume_size', default=1, help='Default size in GB for volumes created by volumes tests'), cfg.ListOpt('manage_volume_ref', default=['source-name', 'volume-%s'], help="A reference to existing volume for volume manage. " "It contains two elements, the first is ref type " "(like 'source-name', 'source-id', etc), the second is " "volume name template used in storage backend"), cfg.ListOpt('manage_snapshot_ref', default=['source-name', '_snapshot-%s'], help="A reference to existing snapshot for snapshot manage. " "It contains two elements, the first is ref type " "(like 'source-name', 'source-id', etc), the second is " "snapshot name template used in storage backend"), cfg.StrOpt('min_microversion', default=None, help="Lower version of the test target microversion range. " "The format is 'X.Y', where 'X' and 'Y' are int values. " "Tempest selects tests based on the range between " "min_microversion and max_microversion. " "If both values are not specified, Tempest avoids tests " "which require a microversion. Valid values are string " "with format 'X.Y' or string 'latest'",), cfg.StrOpt('max_microversion', default=None, help="Upper version of the test target microversion range. " "The format is 'X.Y', where 'X' and 'Y' are int values. " "Tempest selects tests based on the range between " "min_microversion and max_microversion. " "If both values are not specified, Tempest avoids tests " "which require a microversion. Valid values are string " "with format 'X.Y' or string 'latest'",), ] volume_feature_group = cfg.OptGroup(name='volume-feature-enabled', title='Enabled Cinder Features') VolumeFeaturesGroup = [ cfg.BoolOpt('multi_backend', default=False, help="Runs Cinder multi-backend test (requires 2 backends)"), cfg.BoolOpt('backup', default=True, help='Runs Cinder volumes backup test'), cfg.BoolOpt('snapshot', default=True, help='Runs Cinder volume snapshot test'), cfg.BoolOpt('clone', default=True, help='Runs Cinder volume clone test'), cfg.BoolOpt('manage_snapshot', default=False, help='Runs Cinder manage snapshot tests'), cfg.BoolOpt('manage_volume', default=False, help='Runs Cinder manage volume tests'), cfg.ListOpt('api_extensions', default=['all'], help='A list of enabled volume extensions with a special ' 'entry all which indicates every extension is enabled. ' 'Empty list indicates all extensions are disabled'), cfg.BoolOpt('api_v1', default=False, help="Is the v1 volume API enabled", deprecated_for_removal=True, deprecated_reason="The v1 volume API has been deprecated " "since Juno release, and the API will be " "removed."), cfg.BoolOpt('api_v2', default=True, help="Is the v2 volume API enabled"), cfg.BoolOpt('api_v3', default=True, help="Is the v3 volume API enabled"), cfg.BoolOpt('extend_attached_volume', default=False, help='Does the cloud support extending the size of a volume ' 'which is currently attached to a server instance? This ' 'depends on the 3.42 volume API microversion and the ' '2.51 compute API microversion. Also, not all volume or ' 'compute backends support this operation.') ] object_storage_group = cfg.OptGroup(name='object-storage', title='Object Storage Service Options') ObjectStoreGroup = [ cfg.StrOpt('catalog_type', default='object-store', help="Catalog type of the Object-Storage service."), cfg.StrOpt('region', default='', help="The object-storage region name to use. If empty, the " "value of identity.region is used instead. If no such " "region is found in the service catalog, the first found " "one is used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the object-store service."), cfg.IntOpt('container_sync_timeout', default=600, help="Number of seconds to time on waiting for a container " "to container synchronization complete."), cfg.IntOpt('container_sync_interval', default=5, help="Number of seconds to wait while looping to check the " "status of a container to container synchronization"), cfg.StrOpt('operator_role', default='Member', help="Role to add to users created for swift tests to " "enable creating containers"), cfg.StrOpt('reseller_admin_role', default='ResellerAdmin', help="User role that has reseller admin"), cfg.StrOpt('realm_name', default='realm1', help="Name of sync realm. A sync realm is a set of clusters " "that have agreed to allow container syncing with each " "other. Set the same realm name as Swift's " "container-sync-realms.conf"), cfg.StrOpt('cluster_name', default='name1', help="One name of cluster which is set in the realm whose name " "is set in 'realm_name' item in this file. Set the " "same cluster name as Swift's container-sync-realms.conf"), ] object_storage_feature_group = cfg.OptGroup( name='object-storage-feature-enabled', title='Enabled object-storage features') ObjectStoreFeaturesGroup = [ cfg.ListOpt('discoverable_apis', default=['all'], help="A list of the enabled optional discoverable apis. " "A single entry, all, indicates that all of these " "features are expected to be enabled"), cfg.BoolOpt('container_sync', default=True, help="Execute (old style) container-sync tests"), cfg.BoolOpt('object_versioning', default=True, help="Execute object-versioning tests"), cfg.BoolOpt('discoverability', default=True, help="Execute discoverability tests"), ] orchestration_group = cfg.OptGroup(name='orchestration', title='Orchestration Service Options') OrchestrationGroup = [ cfg.StrOpt('catalog_type', default='orchestration', help="Catalog type of the Orchestration service.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.StrOpt('region', default='', help="The orchestration region name to use. If empty, the " "value of identity.region is used instead. If no such " "region is found in the service catalog, the first found " "one is used.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the orchestration service.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.StrOpt('stack_owner_role', default='heat_stack_owner', help='Role required for users to be able to manage stacks', deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.IntOpt('build_interval', default=1, help="Time in seconds between build status checks.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.IntOpt('build_timeout', default=1200, help="Timeout in seconds to wait for a stack to build.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.StrOpt('instance_type', default='m1.micro', help="Instance type for tests. Needs to be big enough for a " "full OS plus the test workload", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.StrOpt('keypair_name', help="Name of existing keypair to launch servers with.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.IntOpt('max_template_size', default=524288, help="Value must match heat configuration of the same name.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), cfg.IntOpt('max_resources_per_stack', default=1000, help="Value must match heat configuration of the same name.", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), ] scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options') ScenarioGroup = [ cfg.StrOpt('img_dir', default='/opt/stack/new/devstack/files/images/' 'cirros-0.3.1-x86_64-uec', help='Directory containing image files', deprecated_for_removal=True), cfg.StrOpt('img_file', deprecated_name='qcow2_img_file', default='cirros-0.3.1-x86_64-disk.img', help='Image file name'), cfg.StrOpt('img_disk_format', default='qcow2', help='Image disk format'), cfg.StrOpt('img_container_format', default='bare', help='Image container format'), cfg.DictOpt('img_properties', help='Glance image properties. ' 'Use for custom images which require them'), cfg.StrOpt('ami_img_file', default='cirros-0.3.1-x86_64-blank.img', help='AMI image file name', deprecated_for_removal=True), cfg.StrOpt('ari_img_file', default='cirros-0.3.1-x86_64-initrd', help='ARI image file name', deprecated_for_removal=True), cfg.StrOpt('aki_img_file', default='cirros-0.3.1-x86_64-vmlinuz', help='AKI image file name', deprecated_for_removal=True), # TODO(yfried): add support for dhcpcd cfg.StrOpt('dhcp_client', default='udhcpc', choices=["udhcpc", "dhclient", ""], help='DHCP client used by images to renew DCHP lease. ' 'If left empty, update operation will be skipped. ' 'Supported clients: "udhcpc", "dhclient"') ] service_available_group = cfg.OptGroup(name="service_available", title="Available OpenStack Services") ServiceAvailableGroup = [ cfg.BoolOpt('cinder', default=True, help="Whether or not cinder is expected to be available"), cfg.BoolOpt('neutron', default=False, help="Whether or not neutron is expected to be available"), cfg.BoolOpt('glance', default=True, help="Whether or not glance is expected to be available"), cfg.BoolOpt('swift', default=True, help="Whether or not swift is expected to be available"), cfg.BoolOpt('nova', default=True, help="Whether or not nova is expected to be available"), cfg.BoolOpt('heat', default=False, help="Whether or not Heat is expected to be available", deprecated_for_removal=True, deprecated_reason='Heat support will be removed from Tempest'), ] debug_group = cfg.OptGroup(name="debug", title="Debug System") DebugGroup = [ cfg.StrOpt('trace_requests', default='', help="""A regex to determine which requests should be traced. This is a regex to match the caller for rest client requests to be able to selectively trace calls out of specific classes and methods. It largely exists for test development, and is not expected to be used in a real deploy of tempest. This will be matched against the discovered ClassName:method in the test environment. Expected values for this field are: * ClassName:test_method_name - traces one test_method * ClassName:setUp(Class) - traces specific setup functions * ClassName:tearDown(Class) - traces specific teardown functions * ClassName:_run_cleanups - traces the cleanup functions If nothing is specified, this feature is not enabled. To trace everything specify .* as the regex. """) ] DefaultGroup = [ cfg.StrOpt('resources_prefix', default='tempest', help="Prefix to be added when generating the name for " "test resources. It can be used to discover all " "resources associated with a specific test run when " "running tempest on a real-life cloud", deprecated_for_removal=True, deprecated_reason="It is enough to add 'tempest' as this " "prefix to ideintify resources which are " "created by Tempest and no projects set " "this option on OpenStack dev community."), cfg.BoolOpt('pause_teardown', default=False, help="""Whether to pause a test in global teardown. The best use case is investigating used resources of one test. A test can be run as follows: $ ostestr --pdb TEST_ID or $ python -m testtools.run TEST_ID"""), ] _opts = [ (auth_group, AuthGroup), (compute_group, ComputeGroup), (compute_features_group, ComputeFeaturesGroup), (identity_group, IdentityGroup), (service_clients_group, ServiceClientsGroup), (identity_feature_group, IdentityFeatureGroup), (image_group, ImageGroup), (image_feature_group, ImageFeaturesGroup), (network_group, NetworkGroup), (network_feature_group, NetworkFeaturesGroup), (validation_group, ValidationGroup), (volume_group, VolumeGroup), (volume_feature_group, VolumeFeaturesGroup), (object_storage_group, ObjectStoreGroup), (object_storage_feature_group, ObjectStoreFeaturesGroup), (orchestration_group, OrchestrationGroup), (scenario_group, ScenarioGroup), (service_available_group, ServiceAvailableGroup), (debug_group, DebugGroup), (None, DefaultGroup) ] def register_opts(): ext_plugins = plugins.TempestTestPluginManager() # Register in-tree tempest config options for g, o in _opts: register_opt_group(_CONF, g, o) # Call external plugin config option registration ext_plugins.register_plugin_opts(_CONF) def list_opts(): """Return a list of oslo.config options available. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users. """ ext_plugins = plugins.TempestTestPluginManager() # 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 # this should never be called outside of this class class TempestConfigPrivate(object): """Provides OpenStack configuration information.""" DEFAULT_CONFIG_DIR = os.path.join(os.getcwd(), "etc") DEFAULT_CONFIG_FILE = "tempest.conf" def __getattr__(self, attr): # Handles config options from the default group return getattr(_CONF, attr) def _set_attrs(self): # This methods ensures that config options in Tempest as well as # in Tempest plugins can be accessed via: # CONF.. # where: # normalised_group_name = group_name.replace('-', '_') # Attributes are set at __init__ time *only* for known option groups self.auth = _CONF.auth self.compute = _CONF.compute self.compute_feature_enabled = _CONF['compute-feature-enabled'] self.identity = _CONF.identity self.service_clients = _CONF['service-clients'] self.identity_feature_enabled = _CONF['identity-feature-enabled'] self.image = _CONF.image self.image_feature_enabled = _CONF['image-feature-enabled'] self.network = _CONF.network self.network_feature_enabled = _CONF['network-feature-enabled'] self.validation = _CONF.validation self.volume = _CONF.volume self.volume_feature_enabled = _CONF['volume-feature-enabled'] self.object_storage = _CONF['object-storage'] self.object_storage_feature_enabled = _CONF[ 'object-storage-feature-enabled'] self.orchestration = _CONF.orchestration self.scenario = _CONF.scenario self.service_available = _CONF.service_available self.debug = _CONF.debug logging.tempest_set_log_file('tempest.log') # Setting attributes for plugins # NOTE(andreaf) Plugins have no access to the TempestConfigPrivate # instance at discovery time, so they have no way of setting these # aliases themselves. ext_plugins = plugins.TempestTestPluginManager() for group, _ in ext_plugins.get_plugin_options_list(): if isinstance(group, cfg.OptGroup): # If we have an OptGroup group_name = group.name group_dest = group.dest else: # If we have a group name as a string group_name = group group_dest = group.replace('-', '_') # NOTE(andreaf) We can set the attribute safely here since in # case of name conflict we would not have reached this point. setattr(self, group_dest, _CONF[group_name]) def __init__(self, parse_conf=True, config_path=None): """Initialize a configuration from a conf directory and conf file.""" super(TempestConfigPrivate, self).__init__() config_files = [] failsafe_path = "/etc/tempest/" + self.DEFAULT_CONFIG_FILE if config_path: path = config_path else: # Environment variables override defaults... conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', self.DEFAULT_CONFIG_DIR) conf_file = os.environ.get('TEMPEST_CONFIG', self.DEFAULT_CONFIG_FILE) path = os.path.join(conf_dir, conf_file) if not os.path.isfile(path): path = failsafe_path # only parse the config file if we expect one to exist. This is needed # to remove an issue with the config file up to date checker. if parse_conf: config_files.append(path) logging.register_options(_CONF) if os.path.isfile(path): _CONF([], project='tempest', default_config_files=config_files) else: _CONF([], project='tempest') logging_cfg_path = "%s/logging.conf" % os.path.dirname(path) 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 logging.setup(_CONF, 'tempest') LOG = logging.getLogger('tempest') LOG.info("Using tempest config file %s", path) register_opts() self._set_attrs() if parse_conf: _CONF.log_opt_values(LOG, logging.DEBUG) class TempestConfigProxy(object): _config = None _path = None _extra_log_defaults = [ ('paramiko.transport', logging.INFO), ('requests.packages.urllib3.connectionpool', logging.WARN), ] def _fix_log_levels(self): """Tweak the oslo log defaults.""" for name, level in self._extra_log_defaults: logging.getLogger(name).logger.setLevel(level) def __getattr__(self, attr): if not self._config: self._fix_log_levels() lock_dir = os.path.join(tempfile.gettempdir(), 'tempest-lock') lockutils.set_defaults(lock_dir) self._config = TempestConfigPrivate(config_path=self._path) # Pushing tempest internal service client configuration to the # service clients register. Doing this in the config module ensures # that the configuration is available by the time we register the # service clients. # NOTE(andreaf) This has to be done at the time the first # attribute is accessed, to ensure all plugins have been already # loaded, options registered, and _config is set. _register_tempest_service_clients() # Registering service clients and pushing their configuration to # the service clients register. Doing this in the config module # ensures that the configuration is available by the time we # discover tests from plugins. plugins.TempestTestPluginManager()._register_service_clients() return getattr(self._config, attr) def set_config_path(self, path): self._path = path CONF = TempestConfigProxy() def service_client_config(service_client_name=None): """Return a dict with the parameters to init service clients Extracts from CONF the settings specific to the service_client_name and api_version, and formats them as dict ready to be passed to the service clients __init__: * `region` (default to identity) * `catalog_type` * `endpoint_type` * `build_timeout` (object-storage and identity default to compute) * `build_interval` (object-storage and identity default to compute) The following common settings are always returned, even if `service_client_name` is None: * `disable_ssl_certificate_validation` * `ca_certs` * `trace_requests` * `http_timeout` * `proxy_url` The dict returned by this does not fit a few service clients: * The endpoint type is not returned for identity client, since it takes three different values for v2 admin, v2 public and v3 * The `ServersClient` from compute accepts an optional `enable_instance_password` parameter, which is not returned. * The `VolumesClient` for both v1 and v2 volume accept an optional `default_volume_size` parameter, which is not returned. * The `TokenClient` and `V3TokenClient` have a very different interface, only auth_url is needed for them. :param service_client_name: str Name of the service. Supported values are 'compute', 'identity', 'image', 'network', 'object-storage', 'volume' :return: dictionary of __init__ parameters for the service clients :rtype: dict """ _parameters = { 'disable_ssl_certificate_validation': CONF.identity.disable_ssl_certificate_validation, 'ca_certs': CONF.identity.ca_certificates_file, 'trace_requests': CONF.debug.trace_requests, 'http_timeout': CONF.service_clients.http_timeout, 'proxy_url': CONF.service_clients.proxy_url, } if service_client_name is None: return _parameters # Get the group of options first, by normalising the service_group_name # Services with a '-' in the name have an '_' in the option group name config_group = service_client_name.replace('-', '_') # NOTE(andreaf) Check if the config group exists. This allows for this # helper to be used for settings from registered plugins as well try: options = getattr(CONF, config_group) except cfg.NoSuchOptError: # Option group not defined raise exceptions.UnknownServiceClient(services=service_client_name) # Set endpoint_type # Identity uses different settings depending on API version, so do not # return the endpoint at all. if service_client_name != 'identity': _parameters['endpoint_type'] = getattr(options, 'endpoint_type') # Set build_* # Object storage and identity groups do not have conf settings for # build_* parameters, and we default to compute in any case for setting in ['build_timeout', 'build_interval']: if not hasattr(options, setting) or not getattr(options, setting): _parameters[setting] = getattr(CONF.compute, setting) else: _parameters[setting] = getattr(options, setting) # Set region # If a service client does not define region or region is not set # default to the identity region if not hasattr(options, 'region') or not getattr(options, 'region'): _parameters['region'] = CONF.identity.region else: _parameters['region'] = getattr(options, 'region') # Set service _parameters['service'] = getattr(options, 'catalog_type') return _parameters def _register_tempest_service_clients(): # Register tempest own service clients using the same mechanism used # for external plugins. # The configuration data is pushed to the registry so that automatic # configuration of tempest own service clients is possible both for # tempest as well as for the plugins. service_clients = clients.tempest_modules() registry = clients.ClientsRegistry() all_clients = [] for service_client in service_clients: module = service_clients[service_client] configs = service_client.split('.')[0] service_client_data = dict( name=service_client.replace('.', '_').replace('-', '_'), service_version=service_client, module_path=module.__name__, client_names=module.__all__, **service_client_config(configs) ) all_clients.append(service_client_data) # NOTE(andreaf) Internal service clients do not actually belong # to a plugin, so using '__tempest__' to indicate a virtual plugin # which holds internal service clients. registry.register_service_client('__tempest__', all_clients) tempest-17.2.0/tempest/lib/0000775000175100017510000000000013207045130015530 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/0000775000175100017510000000000013207045130017621 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/0000775000175100017510000000000013207045130021457 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/__init__.py0000666000175100017510000000000013207044712023565 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/volume/0000775000175100017510000000000013207045130022766 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/volume/versions.py0000666000175100017510000000456213207044712025226 0ustar zuulzuul00000000000000# 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. list_versions = { 'status_code': [300], 'response_body': { 'type': 'object', 'properties': { 'versions': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'status': {'type': 'string'}, 'updated': {'type': 'string'}, 'id': {'type': 'string'}, 'links': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'href': {'type': 'string', 'format': 'uri'}, 'rel': {'type': 'string'}, 'type': {'type': 'string'}, }, 'required': ['href', 'rel'] } }, 'min_version': {'type': 'string'}, 'version': {'type': 'string'}, 'media-types': { 'type': 'array', 'properties': { 'base': {'type': 'string'}, 'type': {'type': 'string'} }, 'required': ['base', 'type'] } }, 'required': ['status', 'updated', 'id', 'links', 'min_version', 'version', 'media-types'] } } }, 'required': ['versions'], } } tempest-17.2.0/tempest/lib/api_schema/response/volume/__init__.py0000666000175100017510000000000013207044712025074 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/0000775000175100017510000000000013207045130023133 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_47/0000775000175100017510000000000013207045130023774 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_47/__init__.py0000666000175100017510000000000013207044712026102 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_47/servers.py0000666000175100017510000000257113207044712026053 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_26 import servers as servers226 flavor = { 'type': 'object', 'properties': { 'original_name': {'type': 'string'}, 'disk': {'type': 'integer'}, 'ephemeral': {'type': 'integer'}, 'ram': {'type': 'integer'}, 'swap': {'type': 'integer'}, 'vcpus': {'type': 'integer'}, 'extra_specs': { 'type': 'object', 'patternProperties': { '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'} } } }, 'additionalProperties': False, 'required': ['original_name', 'disk', 'ephemeral', 'ram', 'swap', 'vcpus'] } get_server = copy.deepcopy(servers226.get_server) get_server['response_body']['properties']['server'][ 'properties'].update({'flavor': flavor}) tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_2/0000775000175100017510000000000013207045130023703 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_2/__init__.py0000666000175100017510000000000013207044712026011 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_2/keypairs.py0000666000175100017510000000314013207044712026111 0ustar zuulzuul00000000000000# 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 copy from tempest.lib.api_schema.response.compute.v2_1 import keypairs get_keypair = copy.deepcopy(keypairs.get_keypair) get_keypair['response_body']['properties']['keypair'][ 'properties'].update({'type': {'type': 'string'}}) get_keypair['response_body']['properties']['keypair'][ 'required'].append('type') create_keypair = copy.deepcopy(keypairs.create_keypair) create_keypair['status_code'] = [201] create_keypair['response_body']['properties']['keypair'][ 'properties'].update({'type': {'type': 'string'}}) create_keypair['response_body']['properties']['keypair'][ 'required'].append('type') delete_keypair = { 'status_code': [204], } list_keypairs = copy.deepcopy(keypairs.list_keypairs) list_keypairs['response_body']['properties']['keypairs'][ 'items']['properties']['keypair'][ 'properties'].update({'type': {'type': 'string'}}) list_keypairs['response_body']['properties']['keypairs'][ 'items']['properties']['keypair']['required'].append('type') tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_19/0000775000175100017510000000000013207045130023773 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_19/__init__.py0000666000175100017510000000000013207044712026101 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_19/servers.py0000666000175100017510000000450413207044712026050 0ustar zuulzuul00000000000000# 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 copy from tempest.lib.api_schema.response.compute.v2_1 import servers as serversv21 from tempest.lib.api_schema.response.compute.v2_16 import servers \ as serversv216 list_servers = copy.deepcopy(serversv216.list_servers) get_server = copy.deepcopy(serversv216.get_server) get_server['response_body']['properties']['server'][ 'properties'].update({'description': {'type': ['string', 'null']}}) get_server['response_body']['properties']['server'][ 'required'].append('description') list_servers_detail = copy.deepcopy(serversv216.list_servers_detail) list_servers_detail['response_body']['properties']['servers']['items'][ 'properties'].update({'description': {'type': ['string', 'null']}}) list_servers_detail['response_body']['properties']['servers']['items'][ 'required'].append('description') update_server = copy.deepcopy(serversv21.update_server) update_server['response_body']['properties']['server'][ 'properties'].update({'description': {'type': ['string', 'null']}}) update_server['response_body']['properties']['server'][ 'required'].append('description') rebuild_server = copy.deepcopy(serversv21.rebuild_server) rebuild_server['response_body']['properties']['server'][ 'properties'].update({'description': {'type': ['string', 'null']}}) rebuild_server['response_body']['properties']['server'][ 'required'].append('description') rebuild_server_with_admin_pass = copy.deepcopy( serversv21.rebuild_server_with_admin_pass) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'properties'].update({'description': {'type': ['string', 'null']}}) rebuild_server_with_admin_pass['response_body']['properties']['server'][ 'required'].append('description') tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_23/0000775000175100017510000000000013207045130023766 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_23/__init__.py0000666000175100017510000000000013207044712026074 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_23/migrations.py0000666000175100017510000000515113207044712026525 0ustar zuulzuul00000000000000# 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 # Compute microversion 2.23: # New attributes in 'migrations' list. # 'migration_type' # 'links' 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']}, # New attributes in version 2.23 'migration_type': {'type': ['string', 'null']}, 'links': parameter_types.links }, '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', 'migration_type' ] } } }, 'additionalProperties': False, 'required': ['migrations'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_9/0000775000175100017510000000000013207045130023712 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_9/__init__.py0000666000175100017510000000000013207044712026020 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_9/servers.py0000666000175100017510000000243313207044712025766 0ustar zuulzuul00000000000000# 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 copy from tempest.lib.api_schema.response.compute.v2_6 import servers list_servers = copy.deepcopy(servers.list_servers) get_server = copy.deepcopy(servers.get_server) get_server['response_body']['properties']['server'][ 'properties'].update({'locked': {'type': 'boolean'}}) get_server['response_body']['properties']['server'][ 'required'].append('locked') list_servers_detail = copy.deepcopy(servers.list_servers_detail) list_servers_detail['response_body']['properties']['servers']['items'][ 'properties'].update({'locked': {'type': 'boolean'}}) list_servers_detail['response_body']['properties']['servers']['items'][ 'required'].append('locked') tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_6/0000775000175100017510000000000013207045130023707 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_6/__init__.py0000666000175100017510000000000013207044712026015 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_6/servers.py0000666000175100017510000000335513207044712025767 0ustar zuulzuul00000000000000# Copyright 2016 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.api_schema.response.compute.v2_3 import servers list_servers = copy.deepcopy(servers.list_servers) get_server = copy.deepcopy(servers.get_server) list_servers_detail = copy.deepcopy(servers.list_servers_detail) # NOTE: The consolidated remote console API got introduced with v2.6 # with bp/consolidate-console-api. See Nova commit 578bafeda get_remote_consoles = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'remote_console': { 'type': 'object', 'properties': { 'protocol': {'enum': ['vnc', 'rdp', 'serial', 'spice']}, 'type': {'enum': ['novnc', 'xpvnc', 'rdp-html5', 'spice-html5', 'serial']}, 'url': { 'type': 'string', 'format': 'uri' } }, 'additionalProperties': False, 'required': ['protocol', 'type', 'url'] } }, 'additionalProperties': False, 'required': ['remote_console'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/0000775000175100017510000000000013207045130023702 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/interfaces.py0000666000175100017510000000434413207044712026413 0ustar zuulzuul00000000000000# 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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/snapshots.py0000666000175100017510000000354613207044712026315 0ustar zuulzuul00000000000000# 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 tempest.lib.api_schema.response.compute.v2_1 import parameter_types common_snapshot_info = { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'volumeId': {'type': 'string'}, 'status': {'type': 'string'}, 'size': {'type': 'integer'}, 'createdAt': parameter_types.date_time, '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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/hosts.py0000666000175100017510000000727613207044712025437 0ustar zuulzuul00000000000000# 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, 'anyOf': [ {'required': ['host', 'status']}, {'required': ['host', 'maintenance_mode']} ] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py0000666000175100017510000000576413207044712027137 0ustar zuulzuul00000000000000# 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 _server_usages = { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'ended_at': parameter_types.date_time_or_null, 'flavor': {'type': 'string'}, 'hours': {'type': 'number'}, 'instance_id': {'type': 'string'}, 'local_gb': {'type': 'integer'}, 'memory_mb': {'type': 'integer'}, 'name': {'type': 'string'}, 'started_at': parameter_types.date_time, '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': parameter_types.date_time, 'stop': parameter_types.date_time, '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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/flavors.py0000666000175100017510000000646113207044712025746 0ustar zuulzuul00000000000000# 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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/services.py0000666000175100017510000000606513207044712026115 0ustar zuulzuul00000000000000# 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_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': parameter_types.date_time_or_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'] } } disable_log_reason = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'service': { 'type': 'object', 'properties': { 'disabled_reason': {'type': 'string'}, 'binary': {'type': 'string'}, 'host': {'type': 'string'}, 'status': {'type': 'string'} }, 'additionalProperties': False, 'required': ['disabled_reason', 'binary', 'host', 'status'] } }, 'additionalProperties': False, 'required': ['service'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py0000666000175100017510000000371013207044712027410 0ustar zuulzuul00000000000000# 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') tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/versions.py0000666000175100017510000000624013207044712026135 0ustar zuulzuul00000000000000# 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.api_schema.response.compute.v2_1 import parameter_types _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': parameter_types.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 } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/parameter_types.py0000666000175100017510000000612713207044712027475 0ustar zuulzuul00000000000000# 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': {'enum': [4, 6]}, 'addr': { 'type': 'string', 'oneOf': [ {'format': 'ipv4'}, {'format': 'ipv6'} ] } }, 'additionalProperties': False, 'required': ['version', 'addr'] } } } } date_time = { 'type': 'string', 'format': 'iso8601-date-time' } date_time_or_null = { 'type': ['string', 'null'], 'format': 'iso8601-date-time' } 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'}, # NOTE(gmann): Validating this as string only as this # date in header is returned in different format than # ISO 8601 date time format which is not consistent with # other date-time format in nova. # This API is already deprecated so not worth to fix # on nova side. 'date': { 'type': 'string' } } power_state = { 'type': 'integer', # 0: NOSTATE # 1: RUNNING # 3: PAUSED # 4: SHUTDOWN # 6: CRASHED # 7: SUSPENDED 'enum': [0, 1, 3, 4, 6, 7] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/floating_ips.py0000666000175100017510000001110513207044712026737 0ustar zuulzuul00000000000000# 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'], } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py0000666000175100017510000000536213207044712031473 0ustar zuulzuul00000000000000# 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', 'patternProperties': { # NOTE: Here is a host name. '^.+$': { 'type': 'object', 'properties': { 'state': {'type': 'string'}, 'instances': {'type': 'integer'}, 'errors': {'type': 'integer'}, 'message': {'type': 'string'} }, 'additionalProperties': False, 'required': ['state', 'instances', 'errors', 'message'] } } }, '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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/limits.py0000666000175100017510000001231513207044712025566 0ustar zuulzuul00000000000000# 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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py0000666000175100017510000000261113207044712026235 0ustar zuulzuul00000000000000# 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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/__init__.py0000666000175100017510000000000013207044712026010 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/aggregates.py0000666000175100017510000000540113207044712026374 0ustar zuulzuul00000000000000# 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-aggregate api doesn't have 'hosts' and 'metadata' attributes. aggregate_for_create = { 'type': 'object', 'properties': { 'availability_zone': {'type': ['string', 'null']}, 'created_at': parameter_types.date_time, 'deleted': {'type': 'boolean'}, 'deleted_at': parameter_types.date_time_or_null, 'id': {'type': 'integer'}, 'name': {'type': 'string'}, 'updated_at': parameter_types.date_time_or_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'] = parameter_types.date_time 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 tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/servers.py0000666000175100017510000004266513207044712025771 0ustar zuulzuul00000000000000# 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 }, # NOTE(gmann): This will be empty object if there is no # flavor info present in DB. This can happen when flavor info is # deleted after server creation. 'additionalProperties': False }, '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': parameter_types.date_time, 'updated': parameter_types.date_time, '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': parameter_types.power_state, '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', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'} }, 'additionalProperties': False, }, }, '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', 'additionalProperties': False, } } rescue_server_with_admin_pass = copy.deepcopy(rescue_server) rescue_server_with_admin_pass['response_body'].update( {'properties': {'adminPass': {'type': 'string'}}}) rescue_server_with_admin_pass['response_body'].update( {'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': parameter_types.date_time, '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': parameter_types.date_time, # The finish_time, result and optionally traceback are all # possibly None (null) until the event is actually finished. # The traceback would only be set if there was an error, but # when the event is complete both finish_time and result will # be set. 'finish_time': parameter_types.date_time_or_null, 'result': {'type': ['string', 'null']}, '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] } evacuate_server = { 'status_code': [200] } evacuate_server_with_admin_pass = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'adminPass': {'type': 'string'} }, 'additionalProperties': False, 'required': ['adminPass'] } } show_server_diagnostics = { 'status_code': [200], 'response_body': { 'type': 'object' } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/security_groups.py0000666000175100017510000000662113207044712027536 0ustar zuulzuul00000000000000# 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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/availability_zone.py0000666000175100017510000000542713207044712030000 0ustar zuulzuul00000000000000# 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 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', 'null'], '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': parameter_types.date_time_or_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 tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/agents.py0000666000175100017510000000460213207044712025546 0ustar zuulzuul00000000000000# 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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/quotas.py0000666000175100017510000001351613207044712025605 0ustar zuulzuul00000000000000# 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']) get_quota_set_details = copy.deepcopy(get_quota_set) get_quota_set_details['response_body']['properties']['quota_set'][ 'properties'] = { 'id': {'type': 'string'}, 'instances': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'cores': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'ram': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'floating_ips': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'fixed_ips': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'metadata_items': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'key_pairs': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'security_groups': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'security_group_rules': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'server_group_members': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'server_groups': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'injected_files': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'injected_file_content_bytes': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } }, 'injected_file_path_bytes': { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} } } } delete_quota = { 'status_code': [202] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/certificates.py0000666000175100017510000000260013207044712026726 0ustar zuulzuul00000000000000# 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) tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/quota_classes.py0000666000175100017510000000272613207044712027140 0ustar zuulzuul00000000000000# 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'] tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py0000666000175100017510000000236513207044712030345 0ustar zuulzuul00000000000000# 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'} } } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/volumes.py0000666000175100017510000001261313207044712025760 0ustar zuulzuul00000000000000# 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 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': parameter_types.date_time, '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': parameter_types.date_time, '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] } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/images.py0000666000175100017510000001107213207044712025531 0ustar zuulzuul00000000000000# 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'}}) image_status_enums = ['ACTIVE', 'SAVING', 'DELETED', 'ERROR', 'UNKNOWN'] common_image_schema = { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'status': {'enum': image_status_enums}, 'updated': parameter_types.date_time, 'links': image_links, 'name': {'type': ['string', 'null']}, 'created': parameter_types.date_time, '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', 'null']} }, '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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py0000666000175100017510000000267713207044712027524 0ustar zuulzuul00000000000000# 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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/extensions.py0000666000175100017510000000332513207044712026465 0ustar zuulzuul00000000000000# 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_extensions = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'extensions': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'updated': parameter_types.date_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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py0000666000175100017510000000400013207044712032073 0ustar zuulzuul00000000000000# 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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/keypairs.py0000666000175100017510000000732613207044712026122 0ustar zuulzuul00000000000000# 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_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': parameter_types.date_time, 'updated_at': parameter_types.date_time_or_null, 'deleted_at': parameter_types.date_time_or_null, 'id': {'type': 'integer'} }, 'additionalProperties': False, 'required': ['public_key', 'name', 'fingerprint', 'user_id', 'deleted', 'created_at', 'updated_at', 'deleted_at', 'id'] } }, '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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/hypervisors.py0000666000175100017510000001567213207044712026673 0ustar zuulzuul00000000000000# 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'. tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/migrations.py0000666000175100017510000000445213207044712026444 0ustar zuulzuul00000000000000# 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_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': parameter_types.date_time, 'updated_at': parameter_types.date_time_or_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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_1/flavors_access.py0000666000175100017510000000243113207044712027260 0ustar zuulzuul00000000000000# 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'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_48/0000775000175100017510000000000013207045130023775 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_48/__init__.py0000666000175100017510000000000013207044712026103 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_48/servers.py0000666000175100017510000001157013207044712026053 0ustar zuulzuul00000000000000# Copyright 2017 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 copy from tempest.lib.api_schema.response.compute.v2_1 import parameter_types from tempest.lib.api_schema.response.compute.v2_47 import servers as servers247 show_server_diagnostics = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'state': { 'type': 'string', 'enum': [ 'pending', 'running', 'paused', 'shutdown', 'crashed', 'suspended'] }, 'driver': { 'type': 'string', 'enum': [ 'libvirt', 'xenapi', 'vmwareapi', 'ironic', 'hyperv'] }, 'hypervisor': {'type': ['string', 'null']}, 'hypervisor_os': {'type': ['string', 'null']}, 'uptime': {'type': ['integer', 'null']}, 'config_drive': {'type': 'boolean'}, 'num_cpus': {'type': 'integer'}, 'num_nics': {'type': 'integer'}, 'num_disks': {'type': 'integer'}, 'memory_details': { 'type': 'object', 'properties': { 'maximum': {'type': ['integer', 'null']}, 'used': {'type': ['integer', 'null']} }, 'additionalProperties': False, 'required': ['maximum', 'used'] }, 'cpu_details': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'null']}, 'time': {'type': ['integer', 'null']}, 'utilisation': {'type': ['integer', 'null']} }, 'additionalProperties': False, 'required': ['id', 'time', 'utilisation'] } }, 'nic_details': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'mac_address': {'oneOf': [parameter_types.mac_address, {'type': 'null'}]}, 'rx_octets': {'type': ['integer', 'null']}, 'rx_errors': {'type': ['integer', 'null']}, 'rx_drop': {'type': ['integer', 'null']}, 'rx_packets': {'type': ['integer', 'null']}, 'rx_rate': {'type': ['integer', 'null']}, 'tx_octets': {'type': ['integer', 'null']}, 'tx_errors': {'type': ['integer', 'null']}, 'tx_drop': {'type': ['integer', 'null']}, 'tx_packets': {'type': ['integer', 'null']}, 'tx_rate': {'type': ['integer', 'null']} }, 'additionalProperties': False, 'required': ['mac_address', 'rx_octets', 'rx_errors', 'rx_drop', 'rx_packets', 'rx_rate', 'tx_octets', 'tx_errors', 'tx_drop', 'tx_packets', 'tx_rate'] } }, 'disk_details': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'read_bytes': {'type': ['integer', 'null']}, 'read_requests': {'type': ['integer', 'null']}, 'write_bytes': {'type': ['integer', 'null']}, 'write_requests': {'type': ['integer', 'null']}, 'errors_count': {'type': ['integer', 'null']} }, 'additionalProperties': False, 'required': ['read_bytes', 'read_requests', 'write_bytes', 'write_requests', 'errors_count'] } } }, 'additionalProperties': False, 'required': [ 'state', 'driver', 'hypervisor', 'hypervisor_os', 'uptime', 'config_drive', 'num_cpus', 'num_nics', 'num_disks', 'memory_details', 'cpu_details', 'nic_details', 'disk_details'], } } get_server = copy.deepcopy(servers247.get_server) tempest-17.2.0/tempest/lib/api_schema/response/compute/__init__.py0000666000175100017510000000000013207044712025241 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_11/0000775000175100017510000000000013207045130023763 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_11/services.py0000666000175100017510000000307213207044712026171 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.lib.api_schema.response.compute.v2_1 import services list_services = copy.deepcopy(services.list_services) list_services['response_body']['properties']['services']['items'][ 'properties']['forced_down'] = {'type': 'boolean'} list_services['response_body']['properties']['services']['items'][ 'required'].append('forced_down') update_forced_down = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'service': { 'type': 'object', 'properties': { 'binary': {'type': 'string'}, 'host': {'type': 'string'}, 'forced_down': {'type': 'boolean'} }, 'additionalProperties': False, 'required': ['binary', 'host', 'forced_down'] } }, 'additionalProperties': False, 'required': ['service'] } } tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_11/__init__.py0000666000175100017510000000000013207044712026071 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_16/0000775000175100017510000000000013207045130023770 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_16/__init__.py0000666000175100017510000000000013207044712026076 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_16/servers.py0000666000175100017510000001431513207044712026046 0ustar zuulzuul00000000000000# 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 from tempest.lib.api_schema.response.compute.v2_9 import servers # Compute microversion 2.16: # 1. New attributes in 'server' dict. # 'host_status' server_detail = { '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, 'key_name': {'type': ['string', 'null']}, 'security_groups': {'type': 'array'}, 'OS-SRV-USG:launched_at': {'type': ['string', 'null']}, 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']}, 'OS-EXT-AZ:availability_zone': {'type': 'string'}, 'OS-EXT-STS:task_state': {'type': ['string', 'null']}, 'OS-EXT-STS:vm_state': {'type': 'string'}, 'OS-EXT-STS:power_state': parameter_types.power_state, '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']}, 'config_drive': {'type': 'string'}, 'os-extended-volumes:volumes_attached': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'delete_on_termination': {'type': 'boolean'} }, 'additionalProperties': False, }, }, 'OS-EXT-SRV-ATTR:reservation_id': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:launch_index': {'type': 'integer'}, 'OS-EXT-SRV-ATTR:kernel_id': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:ramdisk_id': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:hostname': {'type': 'string'}, 'OS-EXT-SRV-ATTR:root_device_name': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:user_data': {'type': ['string', 'null']}, 'locked': {'type': 'boolean'}, # NOTE(gmann): new attributes in version 2.16 'host_status': {'type': 'string'} }, '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'] } 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)dd: 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'] } } list_servers = copy.deepcopy(servers.list_servers) tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_3/0000775000175100017510000000000013207045130023704 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_3/__init__.py0000666000175100017510000000000013207044712026012 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_3/servers.py0000666000175100017510000001471413207044712025765 0ustar zuulzuul00000000000000# 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 from tempest.lib.api_schema.response.compute.v2_1 import servers # Compute microversion 2.3: # 1. New attributes in 'os-extended-volumes:volumes_attached' dict. # 'delete_on_termination' # 2. New attributes in 'server' dict. # 'OS-EXT-SRV-ATTR:reservation_id' # 'OS-EXT-SRV-ATTR:launch_index' # 'OS-EXT-SRV-ATTR:kernel_id' # 'OS-EXT-SRV-ATTR:ramdisk_id' # 'OS-EXT-SRV-ATTR:hostname' # 'OS-EXT-SRV-ATTR:root_device_name' # 'OS-EXT-SRV-ATTR:user_data' server_detail = { '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, 'key_name': {'type': ['string', 'null']}, 'security_groups': {'type': 'array'}, 'OS-SRV-USG:launched_at': {'type': ['string', 'null']}, 'OS-SRV-USG:terminated_at': {'type': ['string', 'null']}, 'OS-EXT-AZ:availability_zone': {'type': 'string'}, 'OS-EXT-STS:task_state': {'type': ['string', 'null']}, 'OS-EXT-STS:vm_state': {'type': 'string'}, 'OS-EXT-STS:power_state': parameter_types.power_state, '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']}, 'config_drive': {'type': 'string'}, # NOTE(gmann): new attributes in version 2.3 'os-extended-volumes:volumes_attached': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'delete_on_termination': {'type': 'boolean'} }, 'additionalProperties': False, }, }, 'OS-EXT-SRV-ATTR:reservation_id': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:launch_index': {'type': 'integer'}, 'OS-EXT-SRV-ATTR:kernel_id': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:ramdisk_id': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:hostname': {'type': 'string'}, 'OS-EXT-SRV-ATTR:root_device_name': {'type': ['string', 'null']}, 'OS-EXT-SRV-ATTR:user_data': {'type': ['string', 'null']}, }, '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'] } 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)dd: 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'] } } list_servers = copy.deepcopy(servers.list_servers) tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_13/0000775000175100017510000000000013207045130023765 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_13/__init__.py0000666000175100017510000000000013207044712026073 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_13/servers.py0000666000175100017510000000261613207044712026044 0ustar zuulzuul00000000000000# Copyright 2017 NTT 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 servers common_server_group = copy.deepcopy(servers.common_server_group) common_server_group['properties']['project_id'] = {'type': 'string'} common_server_group['properties']['user_id'] = {'type': 'string'} common_server_group['required'].append('project_id') common_server_group['required'].append('user_id') create_show_server_group = copy.deepcopy(servers.create_show_server_group) create_show_server_group['response_body']['properties'][ 'server_group'] = common_server_group delete_server_group = copy.deepcopy(servers.delete_server_group) list_server_groups = copy.deepcopy(servers.list_server_groups) list_server_groups['response_body']['properties']['server_groups'][ 'items'] = common_server_group tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_26/0000775000175100017510000000000013207045130023771 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_26/__init__.py0000666000175100017510000000000013207044712026077 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/api_schema/response/compute/v2_26/servers.py0000666000175100017510000000452613207044712026052 0ustar zuulzuul00000000000000# Copyright 2016 IBM Corp. # Copyright 2017 AT&T 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.api_schema.response.compute.v2_1 import servers as servers21 from tempest.lib.api_schema.response.compute.v2_19 import servers as servers219 # The 2.26 microversion changes the server GET and (detailed) LIST responses to # include the server 'tags' which is just a list of strings. tag_items = { 'type': 'array', 'maxItems': 50, 'items': { 'type': 'string', 'pattern': '^[^,/]*$', 'maxLength': 60 } } get_server = copy.deepcopy(servers219.get_server) get_server['response_body']['properties']['server'][ 'properties'].update({'tags': tag_items}) get_server['response_body']['properties']['server'][ 'required'].append('tags') list_servers_detail = copy.deepcopy(servers219.list_servers_detail) list_servers_detail['response_body']['properties']['servers']['items'][ 'properties'].update({'tags': tag_items}) list_servers_detail['response_body']['properties']['servers']['items'][ 'required'].append('tags') # list response schema wasn't changed for v2.26 so use v2.1 list_servers = copy.deepcopy(servers21.list_servers) list_tags = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'tags': tag_items, }, 'additionalProperties': False, 'required': ['tags'] } } update_all_tags = copy.deepcopy(list_tags) delete_all_tags = {'status_code': [204]} check_tag_existence = {'status_code': [204]} update_tag = { 'status_code': [201, 204], 'response_header': { 'type': 'object', 'properties': { 'location': { 'type': 'string' } }, 'required': ['location'] } } delete_tag = {'status_code': [204]} tempest-17.2.0/tempest/lib/api_schema/__init__.py0000666000175100017510000000000013207044712021727 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/cli/0000775000175100017510000000000013207045130016277 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/cli/output_parser.py0000666000175100017510000001131713207044712021577 0ustar zuulzuul00000000000000# 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 re from oslo_log import log as logging 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 table_: 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 tempest-17.2.0/tempest/lib/cli/__init__.py0000666000175100017510000000000013207044712020405 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/cli/base.py0000666000175100017510000004337113207044712017602 0ustar zuulzuul00000000000000# 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 os import shlex import subprocess from oslo_log import log as logging 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', prefix=''): """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 :param prefix: prefix to insert before command :type prefix: string """ cmd = ' '.join([prefix, os.path.join(cli_dir, cmd), flags, action, params]) cmd = cmd.strip() 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 :param prefix: prefix to insert before commands :type prefix: string :param user_domain_name: User's domain name :type user_domain_name: string :param user_domain_id: User's domain ID :type user_domain_id: string :param project_domain_name: Project's domain name :type project_domain_name: string :param project_domain_id: Project's domain ID :type project_domain_id: string """ def __init__(self, username='', password='', tenant_name='', uri='', cli_dir='', insecure=False, prefix='', user_domain_name=None, user_domain_id=None, project_domain_name=None, project_domain_id=None, *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 self.prefix = prefix self.user_domain_name = user_domain_name self.user_domain_id = user_domain_id self.project_domain_name = project_domain_name self.project_domain_id = project_domain_id 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 += ' --os-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.user_domain_name is not None: creds += ' --os-user-domain-name %s' % self.user_domain_name if self.user_domain_id is not None: creds += ' --os-user-domain-id %s' % self.user_domain_id if self.project_domain_name is not None: creds += ' --os-project-domain-name %s' % self.project_domain_name if self.project_domain_id is not None: creds += ' --os-project-domain-id %s' % self.project_domain_id if self.insecure: flags = creds + ' --insecure ' + flags else: flags = creds + ' ' + flags return execute(cmd, action, flags, params, fail_ok, merge_stderr, self.cli_dir, prefix=self.prefix) 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])) tempest-17.2.0/tempest/lib/common/0000775000175100017510000000000013207045130017020 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/common/api_version_utils.py0000666000175100017510000001747613207044712023156 0ustar zuulzuul00000000000000# 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 testtools from tempest.lib.common import api_version_request from tempest.lib import exceptions LATEST_MICROVERSION = 'latest' class BaseMicroversionTest(object): """Mixin class for API microversion test class.""" # NOTE: Basically, each microversion is small API change and we # can use the same tests for most microversions in most cases. # So it is nice to define the test class as possible to run # 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_MICROVERSION def check_skip_with_microversion(test_min_version, test_max_version, cfg_min_version, cfg_max_version): """Checks API microversions range and returns whether test needs to be skip Compare the test and configured microversion range and returns whether test microversion range is out of configured one. This method can be used to skip the test based on configured and test microversion range. :param test_min_version: Test Minimum Microversion :param test_max_version: Test Maximum Microversion :param cfg_min_version: Configured Minimum Microversion :param cfg_max_version: Configured Maximum Microversion :returns: boolean """ min_version = api_version_request.APIVersionRequest(test_min_version) max_version = api_version_request.APIVersionRequest(test_max_version) config_min_version = api_version_request.APIVersionRequest(cfg_min_version) config_max_version = api_version_request.APIVersionRequest(cfg_max_version) if ((min_version > max_version) or (config_min_version > config_max_version)): msg = ("Test Class versions [%s - %s]. " "Configuration versions [%s - %s]." % (min_version.get_string(), max_version.get_string(), config_min_version.get_string(), config_max_version.get_string())) raise exceptions.InvalidAPIVersionRange(msg) # NOTE: Select tests which are in range of configuration like # config min config max # ----------------+--------------------------+---------------- # ...don't-select| # ...select... ...select... ...select... # |don't-select... # ......................select............................ if (max_version < config_min_version or config_max_version < min_version): msg = ("The microversion range[%s - %s] of this test is out of the " "configuration range[%s - %s]." % (min_version.get_string(), max_version.get_string(), config_min_version.get_string(), config_max_version.get_string())) raise testtools.TestCase.skipException(msg) def select_request_microversion(test_min_version, cfg_min_version): """Select microversion from test and configuration min version. Compare requested microversion and return the maximum microversion out of those. :param test_min_version: Test Minimum Microversion :param cfg_min_version: Configured Minimum Microversion :returns: Selected microversion string """ test_version = api_version_request.APIVersionRequest(test_min_version) cfg_version = api_version_request.APIVersionRequest(cfg_min_version) max_version = cfg_version if cfg_version >= test_version else test_version return max_version.get_string() def assert_version_header_matches_request(api_microversion_header_name, api_microversion, response_header): """Checks API microversion in response header Verify whether microversion is present in response header and with specified 'api_microversion' value. :param api_microversion_header_name: Microversion header name Example- "X-OpenStack-Nova-API-Version" :param api_microversion: Microversion number like "2.10" :param response_header: Response header where microversion is expected to be present. """ api_microversion_header_name = api_microversion_header_name.lower() if (api_microversion_header_name not in response_header or api_microversion != response_header[api_microversion_header_name]): msg = ("Microversion header '%s' with value '%s' does not match in " "response - %s. " % (api_microversion_header_name, api_microversion, response_header)) raise exceptions.InvalidHTTPResponseHeader(msg) def compare_version_header_to_response(api_microversion_header_name, api_microversion, response_header, operation='eq'): """Compares API microversion in response header to ``api_microversion``. Compare the ``api_microversion`` value in response header if microversion header is present in response, otherwise return false. To make this function work for APIs which do not return microversion header in response (example compute v2.0), this function does *not* raise InvalidHTTPResponseHeader. :param api_microversion_header_name: Microversion header name. Example: 'Openstack-Api-Version'. :param api_microversion: Microversion number. Example: * '2.10' for the old-style header name, 'X-OpenStack-Nova-API-Version' * 'Compute 2.10' for the new-style header name, 'Openstack-Api-Version' :param response_header: Response header where microversion is expected to be present. :param operation: The boolean operation to use to compare the ``api_microversion`` to the microversion in ``response_header``. Can be 'lt', 'eq', 'gt', 'le', 'ne', 'ge'. Default is 'eq'. The operation type should be based on the order of the arguments: ``api_microversion`` ``response_header`` microversion. :returns: True if the comparison is logically true, else False if the comparison is logically false or if ``api_microversion_header_name`` is missing in the ``response_header``. :raises InvalidParam: If the operation is not lt, eq, gt, le, ne or ge. """ api_microversion_header_name = api_microversion_header_name.lower() if api_microversion_header_name not in response_header: return False op = getattr(api_version_request.APIVersionRequest, '__%s__' % operation, None) if op is None: msg = ("Operation %s is invalid. Valid options include: lt, eq, gt, " "le, ne, ge." % operation) raise exceptions.InvalidParam(invalid_param=msg) # Remove "volume" from "volume ", for example, so that the # microversion can be converted to `APIVersionRequest`. api_version = api_microversion.split(' ')[-1] resp_version = response_header[api_microversion_header_name].split(' ')[-1] if not op( api_version_request.APIVersionRequest(api_version), api_version_request.APIVersionRequest(resp_version)): return False return True tempest-17.2.0/tempest/lib/common/dynamic_creds.py0000666000175100017510000005402213207044712022210 0ustar zuulzuul00000000000000# 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 ipaddress import netaddr from oslo_log import log as logging import six from tempest.lib.common import cred_client from tempest.lib.common import cred_provider from tempest.lib.common.utils import data_utils from tempest.lib import exceptions as lib_exc from tempest.lib.services import clients LOG = logging.getLogger(__name__) class DynamicCredentialProvider(cred_provider.CredentialProvider): """Creates credentials dynamically for tests A credential provider that, based on an initial set of admin credentials, creates new credentials on the fly for tests to use and then discard. :param str identity_version: identity API version to use `v2` or `v3` :param str admin_role: name of the admin role added to admin users :param str name: names of dynamic resources include this parameter when specified :param str credentials_domain: name of the domain where the users are created. If not defined, the project domain from admin_credentials is used :param dict network_resources: network resources to be created for the created credentials :param Credentials admin_creds: initial admin credentials :param bool identity_admin_domain_scope: Set to true if admin should be scoped to the domain. By default this is False and the admin role is scoped to the project. :param str identity_admin_role: The role name to use for admin :param list extra_roles: A list of strings for extra roles that should be assigned to all created users :param bool neutron_available: Whether we are running in an environemnt with neutron :param bool create_networks: Whether dynamic project networks should be created or not :param project_network_cidr: The CIDR to use for created project networks :param project_network_mask_bits: The network mask bits to use for created project networks :param public_network_id: The id for the public network to use :param identity_admin_endpoint_type: The endpoint type for identity admin clients. Defaults to public. :param identity_uri: Identity URI of the target cloud """ def __init__(self, identity_version, name=None, network_resources=None, credentials_domain=None, admin_role=None, admin_creds=None, identity_admin_domain_scope=False, identity_admin_role='admin', extra_roles=None, neutron_available=False, create_networks=True, project_network_cidr=None, project_network_mask_bits=None, public_network_id=None, resource_prefix=None, identity_admin_endpoint_type='public', identity_uri=None): super(DynamicCredentialProvider, self).__init__( identity_version=identity_version, identity_uri=identity_uri, admin_role=admin_role, name=name, credentials_domain=credentials_domain, network_resources=network_resources) self.network_resources = network_resources self._creds = {} self.ports = [] self.resource_prefix = resource_prefix or '' self.neutron_available = neutron_available self.create_networks = create_networks self.project_network_cidr = project_network_cidr self.project_network_mask_bits = project_network_mask_bits self.public_network_id = public_network_id self.default_admin_creds = admin_creds self.identity_admin_domain_scope = identity_admin_domain_scope self.identity_admin_role = identity_admin_role or 'admin' self.identity_admin_endpoint_type = identity_admin_endpoint_type self.extra_roles = extra_roles or [] (self.identity_admin_client, self.tenants_admin_client, self.users_admin_client, self.roles_admin_client, self.domains_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( identity_admin_endpoint_type) # Domain where isolated credentials are provisioned (v3 only). # Use that of the admin account is None is configured. self.creds_domain_name = None if self.identity_version == 'v3': self.creds_domain_name = ( self.default_admin_creds.project_domain_name or self.credentials_domain) self.creds_client = cred_client.get_creds_client( self.identity_admin_client, self.tenants_admin_client, self.users_admin_client, self.roles_admin_client, self.domains_admin_client, self.creds_domain_name) def _get_admin_clients(self, endpoint_type): """Returns a tuple with instances of the following admin clients (in this order): identity network """ os = clients.ServiceClients(self.default_admin_creds, self.identity_uri) params = {'endpoint_type': endpoint_type} if self.identity_version == 'v2': return (os.identity_v2.IdentityClient(**params), os.identity_v2.TenantsClient(**params), os.identity_v2.UsersClient(**params), os.identity_v2.RolesClient(**params), None, os.network.NetworksClient(), os.network.RoutersClient(), os.network.SubnetsClient(), os.network.PortsClient(), os.network.SecurityGroupsClient()) else: # We use a dedicated client manager for identity client in case we # need a different token scope for them. scope = 'domain' if self.identity_admin_domain_scope else 'project' identity_os = clients.ServiceClients(self.default_admin_creds, self.identity_uri, scope=scope) return (identity_os.identity_v3.IdentityClient(**params), identity_os.identity_v3.ProjectsClient(**params), identity_os.identity_v3.UsersClient(**params), identity_os.identity_v3.RolesClient(**params), identity_os.identity_v3.DomainsClient(**params), os.network.NetworksClient(), os.network.RoutersClient(), os.network.SubnetsClient(), os.network.PortsClient(), os.network.SecurityGroupsClient()) def _create_creds(self, admin=False, roles=None): """Create credentials with random name. Creates project and user. When admin flag is True create user with admin role. Assign user with additional roles (for example _member_) and roles requested by caller. :param admin: Flag if to assign to the user admin role :type admin: bool :param roles: Roles to assign for the user :type roles: list :return: Readonly Credentials with network resources """ root = self.name project_name = data_utils.rand_name(root, prefix=self.resource_prefix) project_desc = project_name + "-desc" project = self.creds_client.create_project( name=project_name, description=project_desc) # NOTE(andreaf) User and project can be distinguished from the context, # having the same ID in both makes it easier to match them and debug. username = project_name user_password = data_utils.rand_password() email = data_utils.rand_name( root, prefix=self.resource_prefix) + "@example.com" user = self.creds_client.create_user( username, user_password, project, email) role_assigned = False if admin: self.creds_client.assign_user_role(user, project, self.admin_role) role_assigned = True if (self.identity_version == 'v3' and self.identity_admin_domain_scope): self.creds_client.assign_user_role_on_domain( user, self.identity_admin_role) # Add roles specified in config file for conf_role in self.extra_roles: self.creds_client.assign_user_role(user, project, conf_role) role_assigned = True # Add roles requested by caller if roles: for role in roles: self.creds_client.assign_user_role(user, project, role) role_assigned = True # NOTE(mtreinish) For a user to have access to a project with v3 auth # it must beassigned a role on the project. So we need to ensure that # our newly created user has a role on the newly created project. if self.identity_version == 'v3' and not role_assigned: try: self.creds_client.create_user_role('Member') except lib_exc.Conflict: LOG.warning('Member role already exists, ignoring conflict.') self.creds_client.assign_user_role(user, project, 'Member') creds = self.creds_client.get_credentials(user, project, user_password) return cred_provider.TestResources(creds) def _create_network_resources(self, tenant_id): """The function creates network resources in the given tenant. The function checks if network_resources class member is empty, In case it is, it will create a network, a subnet and a router for the tenant according to the given tenant id parameter. Otherwise it will create a network resource according to the values from network_resources dict. :param tenant_id: The tenant id to create resources for. :type tenant_id: str :raises: InvalidConfiguration, Exception :returns: network resources(network,subnet,router) :rtype: tuple """ network = None subnet = None router = None # Make sure settings if self.network_resources: if self.network_resources['router']: if (not self.network_resources['subnet'] or not self.network_resources['network']): raise lib_exc.InvalidConfiguration( 'A router requires a subnet and network') elif self.network_resources['subnet']: if not self.network_resources['network']: raise lib_exc.InvalidConfiguration( 'A subnet requires a network') elif self.network_resources['dhcp']: raise lib_exc.InvalidConfiguration('DHCP requires a subnet') rand_name_root = data_utils.rand_name( self.name, prefix=self.resource_prefix) if not self.network_resources or self.network_resources['network']: network_name = rand_name_root + "-network" network = self._create_network(network_name, tenant_id) try: if not self.network_resources or self.network_resources['subnet']: subnet_name = rand_name_root + "-subnet" subnet = self._create_subnet(subnet_name, tenant_id, network['id']) if not self.network_resources or self.network_resources['router']: router_name = rand_name_root + "-router" router = self._create_router(router_name, tenant_id) self._add_router_interface(router['id'], subnet['id']) except Exception: 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 def _create_network(self, name, tenant_id): resp_body = self.networks_admin_client.create_network( name=name, tenant_id=tenant_id) return resp_body['network'] def _create_subnet(self, subnet_name, tenant_id, network_id): base_cidr = netaddr.IPNetwork(self.project_network_cidr) mask_bits = self.project_network_mask_bits for subnet_cidr in base_cidr.subnet(mask_bits): try: if self.network_resources: resp_body = self.subnets_admin_client.\ create_subnet( network_id=network_id, cidr=str(subnet_cidr), name=subnet_name, tenant_id=tenant_id, enable_dhcp=self.network_resources['dhcp'], ip_version=(ipaddress.ip_network( six.text_type(subnet_cidr)).version)) else: resp_body = self.subnets_admin_client.\ create_subnet(network_id=network_id, cidr=str(subnet_cidr), name=subnet_name, tenant_id=tenant_id, ip_version=(ipaddress.ip_network( six.text_type(subnet_cidr)).version)) break except lib_exc.BadRequest as e: if 'overlaps with another subnet' not in str(e): raise else: message = 'Available CIDR for subnet creation could not be found' raise Exception(message) return resp_body['subnet'] def _create_router(self, router_name, tenant_id): kwargs = {'name': router_name, 'tenant_id': tenant_id} if self.public_network_id: kwargs['external_gateway_info'] = dict( network_id=self.public_network_id) resp_body = self.routers_admin_client.create_router(**kwargs) return resp_body['router'] def _add_router_interface(self, router_id, subnet_id): self.routers_admin_client.add_router_interface(router_id, subnet_id=subnet_id) def get_credentials(self, credential_type): if self._creds.get(str(credential_type)): credentials = self._creds[str(credential_type)] else: if credential_type in ['primary', 'alt', 'admin']: is_admin = (credential_type == 'admin') credentials = self._create_creds(admin=is_admin) else: credentials = self._create_creds(roles=credential_type) self._creds[str(credential_type)] = credentials # Maintained until tests are ported LOG.info("Acquired dynamic creds:\n credentials: %s", credentials) if (self.neutron_available and self.create_networks): network, subnet, router = self._create_network_resources( credentials.tenant_id) credentials.set_resources(network=network, subnet=subnet, router=router) LOG.info("Created isolated network resources for : \n" + " credentials: %s", credentials) return credentials def get_primary_creds(self): return self.get_credentials('primary') def get_admin_creds(self): return self.get_credentials('admin') def get_alt_creds(self): return self.get_credentials('alt') def get_creds_by_roles(self, roles, force_new=False): roles = list(set(roles)) # The roles list as a str will become the index as the dict key for # the created credentials set in the dynamic_creds dict. exist_creds = self._creds.get(str(roles)) # If force_new flag is True 2 cred sets with the same roles are needed # handle this by creating a separate index for old one to store it # separately for cleanup if exist_creds and force_new: new_index = str(roles) + '-' + str(len(self._creds)) self._creds[new_index] = exist_creds del self._creds[str(roles)] return self.get_credentials(roles) def _clear_isolated_router(self, router_id, router_name): client = self.routers_admin_client try: client.delete_router(router_id) except lib_exc.NotFound: LOG.warning('router with name: %s not found for delete', router_name) def _clear_isolated_subnet(self, subnet_id, subnet_name): client = self.subnets_admin_client try: client.delete_subnet(subnet_id) except lib_exc.NotFound: LOG.warning('subnet with name: %s not found for delete', subnet_name) def _clear_isolated_network(self, network_id, network_name): net_client = self.networks_admin_client try: net_client.delete_network(network_id) except lib_exc.NotFound: LOG.warning('network with name: %s not found for delete', network_name) def _cleanup_default_secgroup(self, tenant): nsg_client = self.security_groups_admin_client resp_body = nsg_client.list_security_groups(tenant_id=tenant, name="default") secgroups_to_delete = resp_body['security_groups'] for secgroup in secgroups_to_delete: try: nsg_client.delete_security_group(secgroup['id']) except lib_exc.NotFound: LOG.warning('Security group %s, id %s not found for clean-up', secgroup['name'], secgroup['id']) def _clear_isolated_net_resources(self): 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, creds.subnet])): continue LOG.debug("Clearing network: %(network)s, " "subnet: %(subnet)s, router: %(router)s", {'network': creds.network, 'subnet': creds.subnet, 'router': creds.router}) if (not self.network_resources or (self.network_resources.get('router') and creds.subnet)): try: client.remove_router_interface( creds.router['id'], subnet_id=creds.subnet['id']) except lib_exc.NotFound: LOG.warning('router with name: %s not found for delete', creds.router['name']) self._clear_isolated_router(creds.router['id'], creds.router['name']) if (not self.network_resources or self.network_resources.get('subnet')): self._clear_isolated_subnet(creds.subnet['id'], creds.subnet['name']) if (not self.network_resources or self.network_resources.get('network')): self._clear_isolated_network(creds.network['id'], creds.network['name']) def clear_creds(self): if not self._creds: return self._clear_isolated_net_resources() for creds in six.itervalues(self._creds): try: self.creds_client.delete_user(creds.user_id) except lib_exc.NotFound: LOG.warning("user with name: %s not found for delete", creds.username) # NOTE(zhufl): Only when neutron's security_group ext is # enabled, _cleanup_default_secgroup will not raise error. But # here cannot use test_utils.is_extension_enabled for it will cause # "circular dependency". So here just use try...except to # ensure tenant deletion without big changes. try: if self.neutron_available: self._cleanup_default_secgroup(creds.tenant_id) except lib_exc.NotFound: LOG.warning("failed to cleanup tenant %s's secgroup", creds.tenant_name) try: self.creds_client.delete_project(creds.tenant_id) except lib_exc.NotFound: LOG.warning("tenant with name: %s not found for delete", creds.tenant_name) self._creds = {} def is_multi_user(self): return True def is_multi_tenant(self): return True def is_role_available(self, role): return True tempest-17.2.0/tempest/lib/common/cred_client.py0000666000175100017510000002033713207044712021661 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 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.services.identity.v2 import identity_client as v2_identity LOG = logging.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class CredsClient(object): """This class is a wrapper around the identity clients to provide a single interface for managing credentials in both v2 and v3 cases. It's not bound to created credentials, only to a specific set of admin credentials used for generating credentials. """ 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 self.roles_client = roles_client def create_user(self, username, password, project, email): params = {'name': username, 'password': password, self.project_id_param: project['id'], 'email': email} user = self.users_client.create_user(**params) if 'user' in user: user = user['user'] return user def delete_user(self, user_id): self.users_client.delete_user(user_id) @abc.abstractmethod def create_project(self, name, description): pass def _check_role_exists(self, role_name): try: roles = self._list_roles() lc_role_name = role_name.lower() role = next(r for r in roles if r['name'].lower() == lc_role_name) except StopIteration: return None return role def create_user_role(self, role_name): if not self._check_role_exists(role_name): self.roles_client.create_role(name=role_name) def assign_user_role(self, user, project, role_name): role = self._check_role_exists(role_name) if not role: msg = 'No "%s" role found' % role_name raise lib_exc.NotFound(msg) try: self.roles_client.create_user_role_on_project(project['id'], user['id'], role['id']) except lib_exc.Conflict: LOG.debug("Role %s already assigned on project %s for user %s", role['id'], project['id'], user['id']) @abc.abstractmethod def get_credentials(self, user, project, password): """Produces a Credentials object from the details provided :param user: a user dict :param project: a project dict :param password: the password as a string :return: a Credentials object with all the available credential details """ pass def _list_roles(self): roles = self.roles_client.list_roles()['roles'] return roles class V2CredsClient(CredsClient): project_id_param = 'tenantId' def __init__(self, identity_client, projects_client, users_client, roles_client): super(V2CredsClient, self).__init__(identity_client, projects_client, users_client, roles_client) def create_project(self, name, description): tenant = self.projects_client.create_tenant( name=name, description=description)['tenant'] return tenant def delete_project(self, project_id): self.projects_client.delete_tenant(project_id) def get_credentials(self, user, project, password): # User and project already include both ID and name here, # so there's no need to use the fill_in mode return auth.get_credentials( auth_url=None, fill_in=False, identity_version='v2', username=user['name'], user_id=user['id'], tenant_name=project['name'], tenant_id=project['id'], password=password) class V3CredsClient(CredsClient): project_id_param = 'project_id' 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.domains_client.list_domains( name=domain_name)['domains'][0] except lib_exc.NotFound: # TODO(andrea) we could probably create the domain on the fly msg = "Requested domain %s could not be found" % domain_name raise lib_exc.InvalidCredentials(msg) def create_project(self, name, description): project = self.projects_client.create_project( name=name, description=description, domain_id=self.creds_domain['id'])['project'] return project def delete_project(self, project_id): self.projects_client.delete_project(project_id) def get_credentials(self, user, project, password): # User, project and domain already include both ID and name here, # so there's no need to use the fill_in mode. # NOTE(andreaf) We need to set all fields in the returned credentials. # Scope is then used to pick only those relevant for the type of # token needed by each service client. return auth.get_credentials( auth_url=None, fill_in=False, identity_version='v3', username=user['name'], user_id=user['id'], project_name=project['name'], project_id=project['id'], password=password, project_domain_id=self.creds_domain['id'], project_domain_name=self.creds_domain['name'], domain_id=self.creds_domain['id'], domain_name=self.creds_domain['name']) def assign_user_role_on_domain(self, user, role_name, domain=None): """Assign the specified role on a domain :param user: a user dict :param role_name: name of the role to be assigned :param domain: (optional) The domain to assign the role on. If not specified the default domain of cred_client """ # NOTE(andreaf) This method is very specific to the v3 case, and # because of that it's not defined in the parent class. if domain is None: domain = self.creds_domain role = self._check_role_exists(role_name) if not role: msg = 'No "%s" role found' % role_name raise lib_exc.NotFound(msg) try: self.roles_client.create_user_role_on_domain( domain['id'], user['id'], role['id']) except lib_exc.Conflict: LOG.debug("Role %s already assigned on domain %s for user %s", role['id'], domain['id'], user['id']) def get_creds_client(identity_client, projects_client, users_client, roles_client, domains_client=None, project_domain_name=None): if isinstance(identity_client, v2_identity.IdentityClient): return V2CredsClient(identity_client, projects_client, users_client, roles_client) else: return V3CredsClient(identity_client, projects_client, users_client, roles_client, domains_client, project_domain_name) tempest-17.2.0/tempest/lib/common/cred_provider.py0000666000175100017510000000671113207044712022235 0ustar zuulzuul00000000000000# Copyright (c) 2014 Deutsche Telekom AG # Copyright (c) 2014 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 abc import six from tempest.lib import auth from tempest.lib import exceptions @six.add_metaclass(abc.ABCMeta) class CredentialProvider(object): def __init__(self, identity_version, name=None, network_resources=None, credentials_domain=None, admin_role=None, identity_uri=None): """A CredentialProvider supplies credentials to test classes. :param identity_version: Identity version of the credentials provided :param name: Name of the calling test. Included in provisioned credentials when credentials are provisioned on the fly :param network_resources: Network resources required for the credentials :param credentials_domain: Domain credentials belong to :param admin_role: Name of the role of the admin account :param identity_uri: Identity URI of the target cloud. This *must* be specified for anything to work. """ self.identity_version = identity_version self.identity_uri = identity_uri self.name = name or "test_creds" self.network_resources = network_resources self.credentials_domain = credentials_domain or 'Default' self.admin_role = admin_role if not auth.is_identity_version_supported(self.identity_version): raise exceptions.InvalidIdentityVersion( identity_version=self.identity_version) @abc.abstractmethod def get_primary_creds(self): return @abc.abstractmethod def get_admin_creds(self): return @abc.abstractmethod def get_alt_creds(self): return @abc.abstractmethod def clear_creds(self): return @abc.abstractmethod def is_multi_user(self): return @abc.abstractmethod def is_multi_tenant(self): return @abc.abstractmethod def get_creds_by_roles(self, roles, force_new=False): return @abc.abstractmethod def is_role_available(self, role): return class TestResources(object): """Readonly Credentials, with network resources added.""" def __init__(self, credentials): self._credentials = credentials self.network = None self.subnet = None self.router = None def __getattr__(self, item): return getattr(self._credentials, item) def __str__(self): _format = "Credentials: %s, Network: %s, Subnet: %s, Router: %s" return _format % (self._credentials, self.network, self.subnet, self.router) def set_resources(self, **kwargs): for key in kwargs: if hasattr(self, key): setattr(self, key, kwargs[key]) @property def credentials(self): return self._credentials tempest-17.2.0/tempest/lib/common/utils/0000775000175100017510000000000013207045130020160 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/common/utils/test_utils.py0000666000175100017510000001007413207044712022742 0ustar zuulzuul00000000000000# Copyright 2016 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 import time from oslo_log import log as logging from tempest.lib import exceptions LOG = logging.getLogger(__name__) 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 def call_and_ignore_notfound_exc(func, *args, **kwargs): """Call the given function and pass if a `NotFound` exception is raised.""" try: return func(*args, **kwargs) except exceptions.NotFound: pass def call_until_true(func, duration, sleep_for, *args, **kwargs): """Call the given function until it returns True (and return True) or until the specified duration (in seconds) elapses (and return False). :param func: A callable that returns True on success. :param duration: The number of seconds for which to attempt a successful call of the function. :param sleep_for: The number of seconds to sleep after an unsuccessful invocation of the function. :param args: args that are passed to func. :param kwargs: kwargs that are passed to func. """ now = time.time() begin_time = now timeout = now + duration while now < timeout: if func(*args, **kwargs): LOG.debug("Call %s returns true in %f seconds", getattr(func, '__name__'), time.time() - begin_time) return True time.sleep(sleep_for) now = time.time() LOG.debug("Call %s returns false in %f seconds", getattr(func, '__name__'), duration) return False tempest-17.2.0/tempest/lib/common/utils/__init__.py0000666000175100017510000000000013207044712022266 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/common/utils/misc.py0000666000175100017510000000241413207044712021475 0ustar zuulzuul00000000000000# 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_log import log as logging from tempest.lib.common.utils import test_utils 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(*args, **kwargs): LOG.warning("tempest.lib.common.utils.misc.find_test_caller is deprecated " "in favor of tempest.lib.common.utils.test_utils." "find_test_caller") test_utils.find_test_caller(*args, **kwargs) tempest-17.2.0/tempest/lib/common/utils/linux/0000775000175100017510000000000013207045130021317 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/common/utils/linux/remote_client.py0000666000175100017510000001237113207044712024535 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 sys import netaddr from oslo_log import log as logging import six from tempest.lib.common import ssh from tempest.lib.common.utils import test_utils import tempest.lib.exceptions LOG = logging.getLogger(__name__) def debug_ssh(function): """Decorator to generate extra debug info in case off SSH failure""" def wrapper(self, *args, **kwargs): try: return function(self, *args, **kwargs) except Exception as e: caller = test_utils.find_test_caller() or "not found" if not isinstance(e, tempest.lib.exceptions.SSHTimeout): message = ('Initializing SSH connection to %(ip)s failed. ' 'Error: %(error)s' % {'ip': self.ip_address, 'error': e}) message = '(%s) %s' % (caller, message) LOG.error(message) raise else: try: original_exception = sys.exc_info() if self.server: msg = 'Caller: %s. Timeout trying to ssh to server %s' LOG.debug(msg, caller, self.server) if self.console_output_enabled and self.servers_client: try: msg = 'Console log for server %s: %s' console_log = ( self.servers_client.get_console_output( self.server['id'])['output']) LOG.debug(msg, self.server['id'], console_log) except Exception: msg = 'Could not get console_log for server %s' LOG.debug(msg, self.server['id']) # re-raise the original ssh timeout exception six.reraise(*original_exception) finally: # Delete the traceback to avoid circular references _, _, trace = original_exception del trace return wrapper class RemoteClient(object): def __init__(self, ip_address, username, password=None, pkey=None, server=None, servers_client=None, ssh_timeout=300, connect_timeout=60, console_output_enabled=True, ssh_shell_prologue="set -eu -o pipefail; PATH=$PATH:/sbin;", ping_count=1, ping_size=56): """Executes commands in a VM over ssh :param ip_address: IP address to ssh to :param username: Ssh username :param password: Ssh password :param pkey: Ssh public key :param server: Server dict, used for debugging purposes :param servers_client: Servers client, used for debugging purposes :param ssh_timeout: Timeout in seconds to wait for the ssh banner :param connect_timeout: Timeout in seconds to wait for TCP connection :param console_output_enabled: Support serial console output? :param ssh_shell_prologue: Shell fragments to use before command :param ping_count: Number of ping packets :param ping_size: Packet size for ping packets """ self.server = server self.servers_client = servers_client self.ip_address = ip_address self.console_output_enabled = console_output_enabled self.ssh_shell_prologue = ssh_shell_prologue self.ping_count = ping_count self.ping_size = ping_size self.ssh_client = ssh.Client(ip_address, username, password, ssh_timeout, pkey=pkey, channel_timeout=connect_timeout) @debug_ssh def exec_command(self, cmd): # Shell options below add more clearness on failures, # path is extended for some non-cirros guest oses (centos7) cmd = self.ssh_shell_prologue + " " + cmd LOG.debug("Remote command: %s", cmd) return self.ssh_client.exec_command(cmd) @debug_ssh def validate_authentication(self): """Validate ssh connection and authentication This method raises an Exception when the validation fails. """ self.ssh_client.test_connection_auth() def ping_host(self, host, count=None, size=None, nic=None): if count is None: count = self.ping_count if size is None: size = self.ping_size addr = netaddr.IPAddress(host) cmd = 'ping6' if addr.version == 6 else 'ping' if nic: cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic) cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host) return self.exec_command(cmd) tempest-17.2.0/tempest/lib/common/utils/linux/__init__.py0000666000175100017510000000000013207044712023425 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/common/utils/data_utils.py0000666000175100017510000001301513207044712022672 0ustar zuulzuul00000000000000# 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 random import string import uuid from oslo_utils import uuidutils import six.moves def rand_uuid(): """Generate a random UUID string :return: a random UUID (e.g. '1dc12c7d-60eb-4b61-a7a2-17cf210155b6') :rtype: string """ return uuidutils.generate_uuid() 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='tempest'): """Generate a random name that includes 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 '--'. (e.g. 'prefixfoo-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 '-- -' (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 includes a random number :return: a random url. The format is 'https://url-.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 rand_infiniband_guid_address(): """Generate an Infiniband GUID address :return: an random Infiniband GUID address :rtype: string """ guid = [] for i in range(8): guid.append("%02x" % random.randint(0x00, 0xff)) return ':'.join(guid) 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 shrunk to the size. :param int size: a returning characters 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 b''.join([six.int2byte(random.randint(0, 255)) for i in range(size)]) # Courtesy of http://stackoverflow.com/a/312464 def chunkify(sequence, chunksize): """Yield successive chunks from `sequence`.""" for i in range(0, len(sequence), chunksize): yield sequence[i:i + chunksize] tempest-17.2.0/tempest/lib/common/validation_resources.py0000666000175100017510000005303013207044712023626 0ustar zuulzuul00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # Copyright (c) 2017 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 fixtures from oslo_log import log as logging from oslo_utils import excutils from tempest.lib.common.utils import data_utils from tempest.lib import exceptions as lib_exc LOG = logging.getLogger(__name__) def _network_service(clients, use_neutron): # Internal helper to select the right network clients if use_neutron: return clients.network else: return clients.compute def create_ssh_security_group(clients, add_rule=False, ethertype='IPv4', use_neutron=True): """Create a security group for ping/ssh testing Create a security group to be attached to a VM using the nova or neutron clients. If rules are added, the group can be attached to a VM to enable connectivity validation over ICMP and further testing over SSH. :param clients: Instance of `tempest.lib.services.clients.ServiceClients` or of a subclass of it. Resources are provisioned using clients from `clients`. :param add_rule: Whether security group rules are provisioned or not. Defaults to `False`. :param ethertype: 'IPv4' or 'IPv6'. Honoured only in case neutron is used. :param use_neutron: When True resources are provisioned via neutron, when False resources are provisioned via nova. :returns: A dictionary with the security group as returned by the API. Examples:: from tempest.common import validation_resources as vr from tempest.lib import auth from tempest.lib.services import clients creds = auth.get_credentials('http://mycloud/identity/v3', username='me', project_name='me', password='secret', domain_name='Default') osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3') # Security group for IPv4 tests sg4 = vr.create_ssh_security_group(osclients, add_rule=True) # Security group for IPv6 tests sg6 = vr.create_ssh_security_group(osclients, ethertype='IPv6', add_rule=True) """ network_service = _network_service(clients, use_neutron) security_groups_client = network_service.SecurityGroupsClient() security_group_rules_client = network_service.SecurityGroupRulesClient() # Security Group clients for nova and neutron behave the same sg_name = data_utils.rand_name('securitygroup-') sg_description = data_utils.rand_name('description-') security_group = security_groups_client.create_security_group( name=sg_name, description=sg_description)['security_group'] # Security Group Rules clients require different parameters depending on # the network service in use if add_rule: try: if use_neutron: security_group_rules_client.create_security_group_rule( security_group_id=security_group['id'], protocol='tcp', ethertype=ethertype, port_range_min=22, port_range_max=22, direction='ingress') security_group_rules_client.create_security_group_rule( security_group_id=security_group['id'], protocol='icmp', ethertype=ethertype, direction='ingress') else: security_group_rules_client.create_security_group_rule( parent_group_id=security_group['id'], ip_protocol='tcp', from_port=22, to_port=22) security_group_rules_client.create_security_group_rule( parent_group_id=security_group['id'], ip_protocol='icmp', from_port=-1, to_port=-1) except Exception as sgc_exc: # If adding security group rules fails, we cleanup the SG before # re-raising the failure up with excutils.save_and_reraise_exception(): try: msg = ('Error while provisioning security group rules in ' 'security group %s. Trying to cleanup.') # The exceptions logging is already handled, so using # debug here just to provide more context LOG.debug(msg, sgc_exc) clear_validation_resources( clients, keypair=None, floating_ip=None, security_group=security_group, use_neutron=use_neutron) except Exception as cleanup_exc: msg = ('Error during cleanup of a security group. ' 'The cleanup was triggered by an exception during ' 'the provisioning of security group rules.\n' 'Provisioning exception: %s\n' 'First cleanup exception: %s') LOG.exception(msg, sgc_exc, cleanup_exc) LOG.debug("SSH Validation resource security group with tcp and icmp " "rules %s created", sg_name) return security_group def create_validation_resources(clients, keypair=False, floating_ip=False, security_group=False, security_group_rules=False, ethertype='IPv4', use_neutron=True, floating_network_id=None, floating_network_name=None): """Provision resources for VM ping/ssh testing Create resources required to be able to ping / ssh a virtual machine: keypair, security group, security group rules and a floating IP. Which of those resources are required may depend on the cloud setup and on the specific test and it can be controlled via the corresponding arguments. Provisioned resources are returned in a dictionary. :param clients: Instance of `tempest.lib.services.clients.ServiceClients` or of a subclass of it. Resources are provisioned using clients from `clients`. :param keypair: Whether to provision a keypair. Defaults to False. :param floating_ip: Whether to provision a floating IP. Defaults to False. :param security_group: Whether to provision a security group. Defaults to False. :param security_group_rules: Whether to provision security group rules. Defaults to False. :param ethertype: 'IPv4' or 'IPv6'. Honoured only in case neutron is used. :param use_neutron: When True resources are provisioned via neutron, when False resources are provisioned via nova. :param floating_network_id: The id of the network used to provision a floating IP. Only used if a floating IP is requested and with neutron. :param floating_network_name: The name of the floating IP pool used to provision the floating IP. Only used if a floating IP is requested and with nova-net. :returns: A dictionary with the resources in the format they are returned by the API. Valid keys are 'keypair', 'floating_ip' and 'security_group'. Examples:: from tempest.common import validation_resources as vr from tempest.lib import auth from tempest.lib.services import clients creds = auth.get_credentials('http://mycloud/identity/v3', username='me', project_name='me', password='secret', domain_name='Default') osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3') # Request keypair and floating IP resources = dict(keypair=True, security_group=False, security_group_rules=False, floating_ip=True) resources = vr.create_validation_resources( osclients, use_neutron=True, floating_network_id='4240E68E-23DA-4C82-AC34-9FEFAA24521C', **resources) # The floating IP to be attached to the VM floating_ip = resources['floating_ip']['ip'] """ # Create and Return the validation resources required to validate a VM msg = ('Requested validation resources keypair %s, floating IP %s, ' 'security group %s') LOG.debug(msg, keypair, floating_ip, security_group) validation_data = {} try: if keypair: keypair_name = data_utils.rand_name('keypair') validation_data.update( clients.compute.KeyPairsClient().create_keypair( name=keypair_name)) LOG.debug("Validation resource key %s created", keypair_name) if security_group: validation_data['security_group'] = create_ssh_security_group( clients, add_rule=security_group_rules, use_neutron=use_neutron, ethertype=ethertype) if floating_ip: floating_ip_client = _network_service( clients, use_neutron).FloatingIPsClient() if use_neutron: floatingip = floating_ip_client.create_floatingip( floating_network_id=floating_network_id) # validation_resources['floating_ip'] has historically looked # like a compute API POST /os-floating-ips response, so we need # to mangle it a bit for a Neutron response with different # fields. validation_data['floating_ip'] = floatingip['floatingip'] validation_data['floating_ip']['ip'] = ( floatingip['floatingip']['floating_ip_address']) else: # NOTE(mriedem): The os-floating-ips compute API was deprecated # in the 2.36 microversion. Any tests for CRUD operations on # floating IPs using the compute API should be capped at 2.35. validation_data.update(floating_ip_client.create_floating_ip( pool=floating_network_name)) LOG.debug("Validation resource floating IP %s created", validation_data['floating_ip']) except Exception as prov_exc: # If something goes wrong, cleanup as much as possible before we # re-raise the exception with excutils.save_and_reraise_exception(): if validation_data: # Cleanup may fail as well try: msg = ('Error while provisioning validation resources %s. ' 'Trying to cleanup what we provisioned so far: %s') # The exceptions logging is already handled, so using # debug here just to provide more context LOG.debug(msg, prov_exc, str(validation_data)) clear_validation_resources( clients, keypair=validation_data.get('keypair', None), floating_ip=validation_data.get('floating_ip', None), security_group=validation_data.get('security_group', None), use_neutron=use_neutron) except Exception as cleanup_exc: msg = ('Error during cleanup of validation resources. ' 'The cleanup was triggered by an exception during ' 'the provisioning step.\n' 'Provisioning exception: %s\n' 'First cleanup exception: %s') LOG.exception(msg, prov_exc, cleanup_exc) return validation_data def clear_validation_resources(clients, keypair=None, floating_ip=None, security_group=None, use_neutron=True): """Cleanup resources for VM ping/ssh testing Cleanup a set of resources provisioned via `create_validation_resources`. In case of errors during cleanup, the exception is logged and the cleanup process is continued. The first exception that was raised is re-raised after the cleanup is complete. :param clients: Instance of `tempest.lib.services.clients.ServiceClients` or of a subclass of it. Resources are provisioned using clients from `clients`. :param keypair: A dictionary with the keypair to be deleted. Defaults to None. :param floating_ip: A dictionary with the floating_ip to be deleted. Defaults to None. :param security_group: A dictionary with the security_group to be deleted. Defaults to None. :param use_neutron: When True resources are provisioned via neutron, when False resources are provisioned via nova. Examples:: from tempest.common import validation_resources as vr from tempest.lib import auth from tempest.lib.services import clients creds = auth.get_credentials('http://mycloud/identity/v3', username='me', project_name='me', password='secret', domain_name='Default') osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3') # Request keypair and floating IP resources = dict(keypair=True, security_group=False, security_group_rules=False, floating_ip=True) resources = vr.create_validation_resources( osclients, validation_resources=resources, use_neutron=True, floating_network_id='4240E68E-23DA-4C82-AC34-9FEFAA24521C') # Now cleanup the resources try: vr.clear_validation_resources(osclients, use_neutron=True, **resources) except Exception as e: LOG.exception('Something went wrong during cleanup, ignoring') """ has_exception = None if keypair: keypair_client = clients.compute.KeyPairsClient() keypair_name = keypair['name'] try: keypair_client.delete_keypair(keypair_name) except lib_exc.NotFound: LOG.warning( "Keypair %s is not found when attempting to delete", keypair_name ) except Exception as exc: LOG.exception('Exception raised while deleting key %s', keypair_name) if not has_exception: has_exception = exc network_service = _network_service(clients, use_neutron) if security_group: security_group_client = network_service.SecurityGroupsClient() sec_id = security_group['id'] try: security_group_client.delete_security_group(sec_id) security_group_client.wait_for_resource_deletion(sec_id) except lib_exc.NotFound: LOG.warning("Security group %s is not found when attempting " "to delete", sec_id) except lib_exc.Conflict as exc: LOG.exception('Conflict while deleting security ' 'group %s VM might not be deleted', sec_id) if not has_exception: has_exception = exc except Exception as exc: LOG.exception('Exception raised while deleting security ' 'group %s', sec_id) if not has_exception: has_exception = exc if floating_ip: floating_ip_client = network_service.FloatingIPsClient() fip_id = floating_ip['id'] try: if use_neutron: floating_ip_client.delete_floatingip(fip_id) else: floating_ip_client.delete_floating_ip(fip_id) except lib_exc.NotFound: LOG.warning('Floating ip %s not found while attempting to ' 'delete', fip_id) except Exception as exc: LOG.exception('Exception raised while deleting ip %s', fip_id) if not has_exception: has_exception = exc if has_exception: raise has_exception class ValidationResourcesFixture(fixtures.Fixture): """Fixture to provision and cleanup validation resources""" DICT_KEYS = ['keypair', 'security_group', 'floating_ip'] def __init__(self, clients, keypair=False, floating_ip=False, security_group=False, security_group_rules=False, ethertype='IPv4', use_neutron=True, floating_network_id=None, floating_network_name=None): """Create a ValidationResourcesFixture Create a ValidationResourcesFixture fixtures, which provisions the resources required to be able to ping / ssh a virtual machine upon setUp and clears them out upon cleanup. Resources are keypair, security group, security group rules and a floating IP - depending on the params. The fixture exposes a dictionary that includes provisioned resources. :param clients: `tempest.lib.services.clients.ServiceClients` or of a subclass of it. Resources are provisioned using clients from `clients`. :param keypair: Whether to provision a keypair. Defaults to False. :param floating_ip: Whether to provision a floating IP. Defaults to False. :param security_group: Whether to provision a security group. Defaults to False. :param security_group_rules: Whether to provision security group rules. Defaults to False. :param ethertype: 'IPv4' or 'IPv6'. Honoured only if neutron is used. :param use_neutron: When True resources are provisioned via neutron, when False resources are provisioned via nova. :param floating_network_id: The id of the network used to provision a floating IP. Only used if a floating IP is requested in case neutron is used. :param floating_network_name: The name of the floating IP pool used to provision the floating IP. Only used if a floating IP is requested and with nova-net. :returns: A dictionary with the same keys as the input `validation_resources` and the resources for values in the format they are returned by the API. Examples:: from tempest.common import validation_resources as vr from tempest.lib import auth from tempest.lib.services import clients import testtools class TestWithVR(testtools.TestCase): def setUp(self): creds = auth.get_credentials( 'http://mycloud/identity/v3', username='me', project_name='me', password='secret', domain_name='Default') osclients = clients.ServiceClients( creds, 'http://mycloud/identity/v3') # Request keypair and floating IP resources = dict(keypair=True, security_group=False, security_group_rules=False, floating_ip=True) network_id = '4240E68E-23DA-4C82-AC34-9FEFAA24521C' self.vr = self.useFixture(vr.ValidationResourcesFixture( osclients, use_neutron=True, floating_network_id=network_id, **resources) def test_use_ip(self): # The floating IP to be attached to the VM floating_ip = self.vr['floating_ip']['ip'] """ self._clients = clients self._keypair = keypair self._floating_ip = floating_ip self._security_group = security_group self._security_group_rules = security_group_rules self._ethertype = ethertype self._use_neutron = use_neutron self._floating_network_id = floating_network_id self._floating_network_name = floating_network_name self._validation_resources = None def _setUp(self): msg = ('Requested setup of ValidationResources keypair %s, floating ' 'IP %s, security group %s') LOG.debug(msg, self._keypair, self._floating_ip, self._security_group) self._validation_resources = create_validation_resources( self._clients, keypair=self._keypair, floating_ip=self._floating_ip, security_group=self._security_group, security_group_rules=self._security_group_rules, ethertype=self._ethertype, use_neutron=self._use_neutron, floating_network_id=self._floating_network_id, floating_network_name=self._floating_network_name) # If provisioning raises an exception we won't have anything to # cleanup here, so we don't need a try-finally around provisioning vr = self._validation_resources self.addCleanup(clear_validation_resources, self._clients, keypair=vr.get('keypair', None), floating_ip=vr.get('floating_ip', None), security_group=vr.get('security_group', None), use_neutron=self._use_neutron) @property def resources(self): return self._validation_resources tempest-17.2.0/tempest/lib/common/__init__.py0000666000175100017510000000000013207044712021126 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/common/jsonschema_validator.py0000666000175100017510000000301113207044712023573 0ustar zuulzuul00000000000000# 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 jsonschema from oslo_utils import timeutils # JSON Schema validator and format checker used for JSON Schema validation JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator FORMAT_CHECKER = jsonschema.draft4_format_checker # NOTE(gmann): Add customized format checker for 'date-time' format because: # 1. jsonschema needs strict_rfc3339 or isodate module to be installed # for proper date-time checking as per rfc3339. # 2. Nova or other OpenStack components handle the date time format as # ISO 8601 which is defined in oslo_utils.timeutils # so this checker will validate the date-time as defined in # oslo_utils.timeutils @FORMAT_CHECKER.checks('iso8601-date-time') def _validate_datetime_format(instance): try: if isinstance(instance, jsonschema.compat.str_types): timeutils.parse_isotime(instance) except ValueError: return False else: return True tempest-17.2.0/tempest/lib/common/http.py0000666000175100017510000000755613207044712020375 0ustar zuulzuul00000000000000# Copyright 2016 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 six import urllib3 class ClosingProxyHttp(urllib3.ProxyManager): def __init__(self, proxy_url, disable_ssl_certificate_validation=False, ca_certs=None, timeout=None): kwargs = {} if disable_ssl_certificate_validation: urllib3.disable_warnings() kwargs['cert_reqs'] = 'CERT_NONE' elif ca_certs: kwargs['cert_reqs'] = 'CERT_REQUIRED' kwargs['ca_certs'] = ca_certs if timeout: kwargs['timeout'] = timeout super(ClosingProxyHttp, self).__init__(proxy_url, **kwargs) def request(self, url, method, *args, **kwargs): class Response(dict): def __init__(self, info): for key, value in info.getheaders().items(): self[key.lower()] = value self.status = info.status self['status'] = str(self.status) self.reason = info.reason self.version = info.version self['content-location'] = url original_headers = kwargs.get('headers', {}) new_headers = dict(original_headers, connection='close') new_kwargs = dict(kwargs, headers=new_headers) # Follow up to 5 redirections. Don't raise an exception if # it's exceeded but return the HTTP 3XX response instead. retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5) r = super(ClosingProxyHttp, self).request(method, url, retries=retry, *args, **new_kwargs) return Response(r), r.data class ClosingHttp(urllib3.poolmanager.PoolManager): def __init__(self, disable_ssl_certificate_validation=False, ca_certs=None, timeout=None): kwargs = {} if disable_ssl_certificate_validation: urllib3.disable_warnings() kwargs['cert_reqs'] = 'CERT_NONE' elif ca_certs: kwargs['cert_reqs'] = 'CERT_REQUIRED' kwargs['ca_certs'] = ca_certs if timeout: kwargs['timeout'] = timeout super(ClosingHttp, self).__init__(**kwargs) def request(self, url, method, *args, **kwargs): class Response(dict): def __init__(self, info): for key, value in info.getheaders().items(): # We assume HTTP header name to be string, not random # bytes, thus ensure we have string keys. self[six.u(key).lower()] = value self.status = info.status self['status'] = str(self.status) self.reason = info.reason self.version = info.version self['content-location'] = url original_headers = kwargs.get('headers', {}) new_headers = dict(original_headers, connection='close') new_kwargs = dict(kwargs, headers=new_headers) # Follow up to 5 redirections. Don't raise an exception if # it's exceeded but return the HTTP 3XX response instead. retry = urllib3.util.Retry(raise_on_redirect=False, redirect=5) r = super(ClosingHttp, self).request(method, url, retries=retry, *args, **new_kwargs) return Response(r), r.data tempest-17.2.0/tempest/lib/common/preprov_creds.py0000666000175100017510000004026013207044712022260 0ustar zuulzuul00000000000000# Copyright (c) 2014 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 hashlib import os from oslo_concurrency import lockutils from oslo_log import log as logging import six import yaml from tempest.lib import auth from tempest.lib.common import cred_provider from tempest.lib.common import fixed_network from tempest.lib import exceptions as lib_exc from tempest.lib.services import clients LOG = logging.getLogger(__name__) def read_accounts_yaml(path): try: with open(path, 'r') as yaml_file: accounts = yaml.safe_load(yaml_file) except IOError: raise lib_exc.InvalidConfiguration( 'The path for the test accounts file: %s ' 'could not be found' % path) return accounts class PreProvisionedCredentialProvider(cred_provider.CredentialProvider): """Credentials provider using pre-provisioned accounts This credentials provider loads the details of pre-provisioned accounts from a YAML file, in the format specified by ``etc/accounts.yaml.sample``. It locks accounts while in use, using the external locking mechanism, allowing for multiple python processes to share a single account file, and thus running tests in parallel. The accounts_lock_dir must be generated using `lockutils.get_lock_path` from the oslo.concurrency library. For instance:: accounts_lock_dir = os.path.join(lockutils.get_lock_path(CONF), 'test_accounts') Role names for object storage are optional as long as the `operator` and `reseller_admin` credential types are not used in the accounts file. :param identity_version: identity version of the credentials :param admin_role: name of the admin role :param test_accounts_file: path to the accounts YAML file :param accounts_lock_dir: the directory for external locking :param name: name of the hash file (optional) :param credentials_domain: name of the domain credentials belong to (if no domain is configured) :param object_storage_operator_role: name of the role :param object_storage_reseller_admin_role: name of the role :param identity_uri: Identity URI of the target cloud """ # Exclude from the hash fields specific to v2 or v3 identity API # i.e. only include user*, project*, tenant* and password HASH_CRED_FIELDS = (set(auth.KeystoneV2Credentials.ATTRIBUTES) & set(auth.KeystoneV3Credentials.ATTRIBUTES)) def __init__(self, identity_version, test_accounts_file, accounts_lock_dir, name=None, credentials_domain=None, admin_role=None, object_storage_operator_role=None, object_storage_reseller_admin_role=None, identity_uri=None): super(PreProvisionedCredentialProvider, self).__init__( identity_version=identity_version, name=name, admin_role=admin_role, credentials_domain=credentials_domain, identity_uri=identity_uri) self.test_accounts_file = test_accounts_file if test_accounts_file: accounts = read_accounts_yaml(self.test_accounts_file) else: raise lib_exc.InvalidCredentials("No accounts file specified") self.hash_dict = self.get_hash_dict( accounts, admin_role, object_storage_operator_role, object_storage_reseller_admin_role) self.accounts_dir = accounts_lock_dir self._creds = {} @classmethod def _append_role(cls, role, account_hash, hash_dict): if role in hash_dict['roles']: hash_dict['roles'][role].append(account_hash) else: hash_dict['roles'][role] = [account_hash] return hash_dict @classmethod def get_hash_dict(cls, accounts, admin_role, object_storage_operator_role=None, object_storage_reseller_admin_role=None): hash_dict = {'roles': {}, 'creds': {}, 'networks': {}} # Loop over the accounts read from the yaml file for account in accounts: roles = [] types = [] resources = [] if 'roles' in account: roles = account.pop('roles') if 'types' in account: types = account.pop('types') if 'resources' in account: resources = account.pop('resources') temp_hash = hashlib.md5() account_for_hash = dict((k, v) for (k, v) in account.items() if k in cls.HASH_CRED_FIELDS) temp_hash.update(six.text_type(account_for_hash).encode('utf-8')) temp_hash_key = temp_hash.hexdigest() hash_dict['creds'][temp_hash_key] = account for role in roles: hash_dict = cls._append_role(role, temp_hash_key, hash_dict) # If types are set for the account append the matching role # subdict with the hash for type in types: if type == 'admin': hash_dict = cls._append_role(admin_role, temp_hash_key, hash_dict) elif type == 'operator': if object_storage_operator_role: hash_dict = cls._append_role( object_storage_operator_role, temp_hash_key, hash_dict) else: msg = ("Type 'operator' configured, but no " "object_storage_operator_role specified") raise lib_exc.InvalidCredentials(msg) elif type == 'reseller_admin': if object_storage_reseller_admin_role: hash_dict = cls._append_role( object_storage_reseller_admin_role, temp_hash_key, hash_dict) else: msg = ("Type 'reseller_admin' configured, but no " "object_storage_reseller_admin_role specified") raise lib_exc.InvalidCredentials(msg) # Populate the network subdict for resource in resources: if resource == 'network': hash_dict['networks'][temp_hash_key] = resources[resource] else: LOG.warning( 'Unknown resource type %s, ignoring this field', resource ) return hash_dict def is_multi_user(self): return len(self.hash_dict['creds']) > 1 def is_multi_tenant(self): return self.is_multi_user() def _create_hash_file(self, hash_string): path = os.path.join(os.path.join(self.accounts_dir, hash_string)) if not os.path.isfile(path): with open(path, 'w') as fd: fd.write(self.name) return True return False @lockutils.synchronized('test_accounts_io', external=True) def _get_free_hash(self, hashes): # Cast as a list because in some edge cases a set will be passed in hashes = list(hashes) if not os.path.isdir(self.accounts_dir): os.mkdir(self.accounts_dir) # Create File from first hash (since none are in use) self._create_hash_file(hashes[0]) return hashes[0] names = [] for _hash in hashes: res = self._create_hash_file(_hash) if res: return _hash else: path = os.path.join(os.path.join(self.accounts_dir, _hash)) with open(path, 'r') as fd: names.append(fd.read()) msg = ('Insufficient number of users provided. %s have allocated all ' 'the credentials for this allocation request' % ','.join(names)) raise lib_exc.InvalidCredentials(msg) def _get_match_hash_list(self, roles=None): hashes = [] if roles: # Loop over all the creds for each role in the subdict and generate # a list of cred lists for each role for role in roles: temp_hashes = self.hash_dict['roles'].get(role, None) if not temp_hashes: raise lib_exc.InvalidCredentials( "No credentials with role: %s specified in the " "accounts ""file" % role) hashes.append(temp_hashes) # Take the list of lists and do a boolean and between each list to # find the creds which fall under all the specified roles temp_list = set(hashes[0]) for hash_list in hashes[1:]: temp_list = temp_list & set(hash_list) hashes = temp_list else: hashes = self.hash_dict['creds'].keys() # NOTE(mtreinish): admin is a special case because of the increased # privilege set which could potentially cause issues on tests where # that is not expected. So unless the admin role isn't specified do # not allocate admin. admin_hashes = self.hash_dict['roles'].get(self.admin_role, None) if ((not roles or self.admin_role not in roles) and admin_hashes): useable_hashes = [x for x in hashes if x not in admin_hashes] else: useable_hashes = hashes return useable_hashes def _sanitize_creds(self, creds): temp_creds = creds.copy() temp_creds.pop('password') return temp_creds def _get_creds(self, roles=None): useable_hashes = self._get_match_hash_list(roles) if not useable_hashes: msg = 'No users configured for type/roles %s' % roles raise lib_exc.InvalidCredentials(msg) free_hash = self._get_free_hash(useable_hashes) clean_creds = self._sanitize_creds( self.hash_dict['creds'][free_hash]) LOG.info('%s allocated creds:\n%s', self.name, clean_creds) return self._wrap_creds_with_network(free_hash) @lockutils.synchronized('test_accounts_io', external=True) def remove_hash(self, hash_string): hash_path = os.path.join(self.accounts_dir, hash_string) if not os.path.isfile(hash_path): LOG.warning('Expected an account lock file %s to remove, but ' 'one did not exist', hash_path) else: os.remove(hash_path) if not os.listdir(self.accounts_dir): os.rmdir(self.accounts_dir) def get_hash(self, creds): for _hash in self.hash_dict['creds']: # Comparing on the attributes that are expected in the YAML init_attributes = creds.get_init_attributes() # Only use the attributes initially used to calculate the hash init_attributes = [x for x in init_attributes if x in self.HASH_CRED_FIELDS] hash_attributes = self.hash_dict['creds'][_hash].copy() # NOTE(andreaf) Not all fields may be available on all credentials # so defaulting to None for that case. if all([getattr(creds, k, None) == hash_attributes.get(k, None) for k in init_attributes]): return _hash raise AttributeError('Invalid credentials %s' % creds) def remove_credentials(self, creds): _hash = self.get_hash(creds) clean_creds = self._sanitize_creds(self.hash_dict['creds'][_hash]) self.remove_hash(_hash) LOG.info("%s returned allocated creds:\n%s", self.name, clean_creds) def get_primary_creds(self): if self._creds.get('primary'): return self._creds.get('primary') net_creds = self._get_creds() self._creds['primary'] = net_creds return net_creds def get_alt_creds(self): if self._creds.get('alt'): return self._creds.get('alt') net_creds = self._get_creds() self._creds['alt'] = net_creds return net_creds def get_creds_by_roles(self, roles, force_new=False): roles = list(set(roles)) exist_creds = self._creds.get(six.text_type(roles).encode( 'utf-8'), None) # The force kwarg is used to allocate an additional set of creds with # the same role list. The index used for the previously allocation # in the _creds dict will be moved. if exist_creds and not force_new: return exist_creds elif exist_creds and force_new: # NOTE(andreaf) In py3.x encode returns bytes, and b'' is bytes # In py2.7 encode returns strings, and b'' is still string new_index = six.text_type(roles).encode('utf-8') + b'-' + \ six.text_type(len(self._creds)).encode('utf-8') self._creds[new_index] = exist_creds net_creds = self._get_creds(roles=roles) self._creds[six.text_type(roles).encode('utf-8')] = net_creds return net_creds def clear_creds(self): for creds in self._creds.values(): self.remove_credentials(creds) def get_admin_creds(self): return self.get_creds_by_roles([self.admin_role]) def is_role_available(self, role): if self.hash_dict['roles'].get(role): return True return False def admin_available(self): return self.is_role_available(self.admin_role) def _wrap_creds_with_network(self, hash): creds_dict = self.hash_dict['creds'][hash] # Make sure a domain scope if defined for users in case of V3 # Make sure a tenant is available in case of V2 creds_dict = self._extend_credentials(creds_dict) # This just builds a Credentials object, it does not validate # nor fill with missing fields. credential = auth.get_credentials( auth_url=None, fill_in=False, identity_version=self.identity_version, **creds_dict) net_creds = cred_provider.TestResources(credential) net_clients = clients.ServiceClients(credentials=credential, identity_uri=self.identity_uri) compute_network_client = net_clients.compute.NetworksClient() net_name = self.hash_dict['networks'].get(hash, None) try: network = fixed_network.get_network_from_name( net_name, compute_network_client) except lib_exc.InvalidTestResource: network = {} net_creds.set_resources(network=network) return net_creds def _extend_credentials(self, creds_dict): # Add or remove credential domain fields to fit the identity version domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES if 'domain' in x) msg = 'Assuming they are valid in the default domain.' if self.identity_version == 'v3': if not domain_fields.intersection(set(creds_dict.keys())): msg = 'Using credentials %s for v3 API calls. ' + msg LOG.warning(msg, self._sanitize_creds(creds_dict)) creds_dict['domain_name'] = self.credentials_domain if self.identity_version == 'v2': if domain_fields.intersection(set(creds_dict.keys())): msg = 'Using credentials %s for v2 API calls. ' + msg LOG.warning(msg, self._sanitize_creds(creds_dict)) # Remove all valid domain attributes for attr in domain_fields.intersection(set(creds_dict.keys())): creds_dict.pop(attr) return creds_dict tempest-17.2.0/tempest/lib/common/rest_client.py0000666000175100017510000012270113207044712021717 0ustar zuulzuul00000000000000# 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 email.utils import re import time import jsonschema from oslo_log import log as logging from oslo_serialization import jsonutils as json import six from six.moves import urllib from tempest.lib.common import http from tempest.lib.common import jsonschema_validator from tempest.lib.common.utils import test_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_validator.JSONSCHEMA_VALIDATOR FORMAT_CHECKER = jsonschema_validator.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 name: The endpoint name to use for the catalog lookup; this returns only if the service exists :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_requests: Regex to use for specifying logging the entirety of the request and response payload :param str http_timeout: Timeout in seconds to wait for the http request to return :param str proxy_url: http proxy url to use. """ # 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='', name=None, http_timeout=None, proxy_url=None): self.auth_provider = auth_provider self.service = service self.region = region self.name = name 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 if proxy_url: self.http_obj = http.ClosingProxyHttp( proxy_url, disable_ssl_certificate_validation=dscv, ca_certs=ca_certs, timeout=http_timeout) else: self.http_obj = http.ClosingHttp( disable_ssl_certificate_validation=dscv, ca_certs=ca_certs, timeout=http_timeout) 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 = 'json' if send_type is None: send_type = 'json' 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, name=self.name ) 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 """ if not isinstance(read_code, int): raise TypeError("'read_code' must be an int instead of (%s)" % type(read_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, chunked=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 bool extra_headers: Boolean value than indicates 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. :param bool chunked: sends the body with chunked encoding :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, chunked) 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 bool extra_headers: Boolean value than indicates 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 bool extra_headers: Boolean value than indicates 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 bool extra_headers: Boolean value than indicates 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, chunked=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 bool extra_headers: Boolean value than indicates 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. :param bool chunked: sends the body with chunked encoding :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, chunked) 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 bool extra_headers: Boolean value than indicates 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 bool extra_headers: Boolean value than indicates 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 "" if len(text) > maxlen: return text[:maxlen] else: return text def _log_request_start(self, method, req_url): caller_name = test_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, resp, req_headers=None, req_body=None, resp_body=None, extra=None): if 'X-Auth-Token' in req_headers: req_headers['X-Auth-Token'] = '' # A shallow copy is sufficient resp_log = resp.copy() if 'x-subject-token' in resp_log: resp_log['x-subject-token'] = '' 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_log), 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 = test_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(logging.DEBUG): self._log_request_full(resp, req_headers, req_body, resp_body, 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 not hasattr(body, "keys") or 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, chunked=False): """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, chunked=chunked ) 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, chunked=False): """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 send with the request :param bool chunked: sends the body with chunked encoding :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, chunked=chunked) def request(self, method, url, extra_headers=False, headers=None, body=None, chunked=False): """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 bool extra_headers: Boolean value than indicates 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. :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 send with the request :param bool chunked: sends the body with chunked encoding :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 PreconditionFailed: If a 412 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.update(self.get_headers()) except (ValueError, TypeError): headers = self.get_headers() resp, resp_body = self._request(method, url, headers=headers, body=body, chunked=chunked) 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 = self._get_retry_after_delay(resp) self.LOG.debug( "Sleeping %s seconds based on retry-after header", delay ) time.sleep(delay) resp, resp_body = self._request(method, url, headers=headers, body=body) self._error_checker(resp, resp_body) return resp, resp_body def _get_retry_after_delay(self, resp): """Extract the delay from the retry-after header. This supports both integer and HTTP date formatted retry-after headers per RFC 2616. :param resp: The response containing the retry-after headers :rtype: int :return: The delay in seconds, clamped to be at least 1 second :raises ValueError: On failing to parse the delay """ delay = None try: delay = int(resp['retry-after']) except (ValueError, KeyError): pass try: retry_timestamp = self._parse_http_date(resp['retry-after']) date_timestamp = self._parse_http_date(resp['date']) delay = int(retry_timestamp - date_timestamp) except (ValueError, OverflowError, KeyError): pass if delay is None: raise ValueError( "Failed to parse retry-after header %r as either int or " "HTTP-date." % resp.get('retry-after') ) # Retry-after headers do not have sub-second precision. Clients may # receive a delay of 0. After sleeping 0 seconds, we would (likely) hit # another 413. To avoid this, always sleep at least 1 second. return max(1, delay) def _parse_http_date(self, val): """Parse an HTTP date, like 'Fri, 31 Dec 1999 23:59:59 GMT'. Return an epoch timestamp (float), as returned by time.mktime(). """ parts = email.utils.parsedate(val) if not parts: raise ValueError("Failed to parse date %s" % val) return time.mktime(parts) def _error_checker(self, 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)) == "": 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 # NOTE(zhipengh): There is a purposefully duplicate of content-type # with the only difference is with or without spaces, as specified # in RFC7231. JSON_ENC = ['application/json', 'application/json; charset=utf-8', '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. # NOTE(zhipengh): There is a purposefully duplicate of content-type # with the only difference is with or without spaces, as specified # in RFC7231. TXT_ENC = ['text/plain', 'text/html', 'text/html; charset=utf-8', 'text/plain; charset=utf-8', '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 == 412: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.PreconditionFailed(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 return 'exceed' in resp_body.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 = test_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) def _get_base_version_url(self): # TODO(oomichi): This method can be used for auth's replace_version(). # So it is nice to have common logic for the maintenance. 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) 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) tempest-17.2.0/tempest/lib/common/fixed_network.py0000666000175100017510000001207213207044712022253 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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_log import log as logging from tempest.lib.common.utils import test_utils from tempest.lib import exceptions LOG = logging.getLogger(__name__) def get_network_from_name(name, compute_networks_client): """Get a full network dict from just a network name :param str name: the name of the network to use :param NetworksClient compute_networks_client: The network client object to use for making the network lists api request :return: The full dictionary for the network in question :rtype: dict :raises InvalidTestResource: If the name provided is invalid, the networks list returns a 404, there are no found networks, or the found network is invalid """ caller = test_utils.find_test_caller() if not name: raise exceptions.InvalidTestResource(type='network', name=name) networks = compute_networks_client.list_networks()['networks'] networks = [n for n in networks if n['label'] == name] # Check that a network exists, else raise an InvalidConfigurationException if len(networks) == 1: network = sorted(networks)[0] elif len(networks) > 1: msg = ("Network with name: %s had multiple matching networks in the " "list response: %s\n Unable to specify a single network" % ( name, networks)) if caller: msg = '(%s) %s' % (caller, msg) LOG.warning(msg) raise exceptions.InvalidTestResource(type='network', name=name) else: msg = "Network with name: %s not found" % name if caller: msg = '(%s) %s' % (caller, msg) LOG.warning(msg) raise exceptions.InvalidTestResource(type='network', name=name) # To be consistent between neutron and nova network always use name even # if label is used in the api response. If neither is present than then # the returned network is invalid. name = network.get('name') or network.get('label') if not name: msg = "Network found from list doesn't contain a valid name or label" if caller: msg = '(%s) %s' % (caller, msg) LOG.warning(msg) raise exceptions.InvalidTestResource(type='network', name=name) network['name'] = name return network def get_tenant_network(creds_provider, compute_networks_client, shared_network_name): """Get a network usable by the primary tenant :param creds_provider: instance of credential provider :param compute_networks_client: compute network client. We want to have the compute network client so we can have use a common approach for both neutron and nova-network cases. If this is not an admin network client, set_network_kwargs might fail in case fixed_network_name is the network to be used, and it's not visible to the tenant :param shared_network_name: name of the shared network to be used if no tenant network is available in the creds provider :returns: a dict with 'id' and 'name' of the network """ caller = test_utils.find_test_caller() net_creds = creds_provider.get_primary_creds() network = getattr(net_creds, 'network', None) if not network or not network.get('name'): if shared_network_name: msg = ('No valid network provided or created, defaulting to ' 'fixed_network_name') if caller: msg = '(%s) %s' % (caller, msg) LOG.debug(msg) try: network = get_network_from_name(shared_network_name, compute_networks_client) except exceptions.InvalidTestResource: network = {} msg = ('Found network %s available for tenant' % network) if caller: msg = '(%s) %s' % (caller, msg) LOG.info(msg) return network def set_networks_kwarg(network, kwargs=None): """Set 'networks' kwargs for a server create if missing :param network: dict of network to be used with 'id' and 'name' :param kwargs: server create kwargs to be enhanced :return: new dict of kwargs updated to include networks """ params = copy.copy(kwargs) or {} if kwargs and 'networks' in kwargs: return params if network: if 'id' in network.keys(): params.update({"networks": [{'uuid': network['id']}]}) else: LOG.warning('The provided network dict: %s was invalid and did ' 'not contain an id', network) return params tempest-17.2.0/tempest/lib/common/ssh.py0000666000175100017510000002165013207044712020202 0ustar zuulzuul00000000000000# 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, port=22, proxy_client=None): """SSH client. Many of parameters are just passed to the underlying implementation as it is. See the paramiko documentation for more details. http://docs.paramiko.org/en/2.1/api/client.html#paramiko.client.SSHClient.connect :param host: Host to login. :param username: SSH username. :param password: SSH password, or a password to unlock private key. :param timeout: Timeout in seconds, including retries. Default is 300 seconds. :param pkey: Private key. :param channel_timeout: Channel timeout in seconds, passed to the paramiko. Default is 10 seconds. :param look_for_keys: Whether or not to search for private keys in ``~/.ssh``. Default is False. :param key_filename: Filename for private key to use. :param port: SSH port number. :param proxy_client: Another SSH client to provide a transport for ssh-over-ssh. The default is None, which means not to use ssh-over-ssh. :type proxy_client: ``tempest.lib.common.ssh.Client`` object """ self.host = host self.username = username self.port = port 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 self.proxy_client = proxy_client self._proxy_conn = None 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:%d' as '%s'" " with public key authentication", self.host, self.port, self.username) else: LOG.info("Creating ssh connection to '%s:%d' as '%s'" " with password %s", self.host, self.port, self.username, str(self.password)) attempts = 0 while True: if self.proxy_client is not None: proxy_chan = self._get_proxy_channel() else: proxy_chan = None try: ssh.connect(self.host, port=self.port, 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, sock=proxy_chan) LOG.info("ssh connection to %s@%s successfully created", self.username, self.host) return ssh except (EOFError, socket.error, socket.timeout, paramiko.SSHException) as e: ssh.close() 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() with transport.open_session() as channel: channel.fileno() # Register event pipe channel.exec_command(cmd) channel.shutdown_write() # 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 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) exit_status = channel.recv_exit_status() 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() def _get_proxy_channel(self): conn = self.proxy_client._get_ssh_connection() # Keep a reference to avoid g/c # https://github.com/paramiko/paramiko/issues/440 self._proxy_conn = conn transport = conn.get_transport() chan = transport.open_session() cmd = 'nc %s %s' % (self.host, self.port) chan.exec_command(cmd) return chan tempest-17.2.0/tempest/lib/common/api_version_request.py0000666000175100017510000001322713207044712023474 0ustar zuulzuul00000000000000# Copyright 2014 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 re from tempest.lib import exceptions # Define the minimum and maximum version of the API across all of the # REST API. The format of the version is: # X.Y where: # # - X will only be changed if a significant backwards incompatible API # change is made which affects the API as whole. That is, something # that is only very very rarely incremented. # # - Y when you make any change to the API. Note that this includes # semantic changes which may not affect the input or output formats or # even originate in the API code layer. We are not distinguishing # between backwards compatible and backwards incompatible changes in # the versioning system. It must be made clear in the documentation as # to what is a backwards compatible change and what is a backwards # incompatible one. class APIVersionRequest(object): """This class represents an API Version Request. This class provides convenience methods for manipulation and comparison of version numbers that we need to do to implement microversions. :param version_string: String representation of APIVersionRequest. Correct format is 'X.Y', where 'X' and 'Y' are int values. None value should be used to create Null APIVersionRequest, which is equal to 0.0 """ # NOTE: This 'latest' version is a magic number, we assume any # projects(Nova, etc.) never achieve this number. latest_ver_major = 99999 latest_ver_minor = 99999 def __init__(self, version_string=None): """Create an API version request object.""" # NOTE(gmann): 'version_string' as String "None" will be considered as # invalid version string. self.ver_major = 0 self.ver_minor = 0 if version_string is not None: match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0)$", version_string) if match: self.ver_major = int(match.group(1)) self.ver_minor = int(match.group(2)) elif version_string == 'latest': self.ver_major = self.latest_ver_major self.ver_minor = self.latest_ver_minor else: raise exceptions.InvalidAPIVersionString( version=version_string) def __str__(self): """Debug/Logging representation of object.""" return ("API Version Request: %s" % self.get_string()) def is_null(self): """Checks whether version is null. Return True if version object is null otherwise False. :returns: boolean """ return self.ver_major == 0 and self.ver_minor == 0 def _format_type_error(self, other): return TypeError("'%(other)s' should be an instance of '%(cls)s'" % {"other": other, "cls": self.__class__}) def __lt__(self, other): if not isinstance(other, APIVersionRequest): raise self._format_type_error(other) return ((self.ver_major, self.ver_minor) < (other.ver_major, other.ver_minor)) def __eq__(self, other): if not isinstance(other, APIVersionRequest): raise self._format_type_error(other) return ((self.ver_major, self.ver_minor) == (other.ver_major, other.ver_minor)) def __gt__(self, other): if not isinstance(other, APIVersionRequest): raise self._format_type_error(other) return ((self.ver_major, self.ver_minor) > (other.ver_major, other.ver_minor)) def __le__(self, other): return self < other or self == other def __ne__(self, other): return not self.__eq__(other) def __ge__(self, other): return self > other or self == other def matches(self, min_version, max_version): """Matches the version object. Returns whether the version object represents a version greater than or equal to the minimum version and less than or equal to the maximum version. :param min_version: Minimum acceptable version. :param max_version: Maximum acceptable version. :returns: boolean If min_version is null then there is no minimum limit. If max_version is null then there is no maximum limit. If self is null then raise ValueError """ if self.is_null(): raise ValueError if max_version.is_null() and min_version.is_null(): return True elif max_version.is_null(): return min_version <= self elif min_version.is_null(): return self <= max_version else: return min_version <= self <= max_version def get_string(self): """Version string representation. Converts object to string representation which if used to create an APIVersionRequest object results in the same version request. """ if self.is_null(): return None if (self.ver_major == self.latest_ver_major and self.ver_minor == self.latest_ver_minor): return 'latest' return "%s.%s" % (self.ver_major, self.ver_minor) tempest-17.2.0/tempest/lib/decorators.py0000666000175100017510000001037713207044712020266 0ustar zuulzuul00000000000000# 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 debtcollector.removals from oslo_log import log as logging import six import testtools LOG = logging.getLogger(__name__) 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(*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(*func_args, **func_kwargs) return wrapper return decorator def related_bug(bug, status_code=None): """A decorator useful to know solutions from launchpad bug reports @param bug: The launchpad bug number causing the test @param status_code: The status code related to the bug report """ def decorator(f): @functools.wraps(f) def wrapper(self, *func_args, **func_kwargs): try: return f(self, *func_args, **func_kwargs) except Exception as exc: exc_status_code = getattr(exc, 'status_code', None) if status_code is None or status_code == exc_status_code: LOG.error('Hints: This test was made for the bug %s. ' 'The failure could be related to ' 'https://launchpad.net/bugs/%s', bug, bug) raise exc 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 @debtcollector.removals.remove(removal_version='Queen') 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): @functools.wraps(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) return _skipper def attr(**kwargs): """A decorator which applies the testtools attr decorator This decorator applies the testtools.testcase.attr if it is in the list of attributes to testtools we want to apply. """ def decorator(f): if 'type' in kwargs and isinstance(kwargs['type'], str): f = testtools.testcase.attr(kwargs['type'])(f) elif 'type' in kwargs and isinstance(kwargs['type'], list): for attr in kwargs['type']: f = testtools.testcase.attr(attr)(f) return f return decorator tempest-17.2.0/tempest/lib/__init__.py0000666000175100017510000000000013207044712017636 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/base.py0000666000175100017510000000553313207044712017031 0ustar zuulzuul00000000000000# 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 os import fixtures import testtools 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)) tempest-17.2.0/tempest/lib/exceptions.py0000666000175100017510000002011713207044725020277 0ustar zuulzuul00000000000000# 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 args: # 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 def __repr__(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 BadRequest(ClientRestClientException): status_code = 400 message = "Bad request" class Unauthorized(ClientRestClientException): status_code = 401 message = 'Unauthorized' class Forbidden(ClientRestClientException): status_code = 403 message = "Forbidden" class NotFound(ClientRestClientException): status_code = 404 message = "Object not found" class Conflict(ClientRestClientException): status_code = 409 message = "An object with that identifier already exists" class Gone(ClientRestClientException): status_code = 410 message = "The requested resource is no longer available" class PreconditionFailed(ClientRestClientException): status_code = 412 message = "Precondition Failed" class RateLimitExceeded(ClientRestClientException): status_code = 413 message = "Rate limit exceeded" class OverLimit(ClientRestClientException): status_code = 413 message = "Request entity is too large" class InvalidContentType(ClientRestClientException): status_code = 415 message = "Invalid content type provided" class UnprocessableEntity(ClientRestClientException): status_code = 422 message = "Unprocessable entity" class ServerFault(ServerRestClientException): status_code = 500 message = "Got server fault" class NotImplemented(ServerRestClientException): status_code = 501 message = "Got NotImplemented error" class TimeoutException(OtherRestClientException): message = "Request timed out" 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 UnexpectedContentType(OtherRestClientException): message = "Unexpected content type provided" class UnexpectedResponseCode(OtherRestClientException): message = "Unexpected response code received" class InvalidConfiguration(TempestException): message = "Invalid Configuration" class InvalidIdentityVersion(TempestException): message = "Invalid version %(identity_version)s of the identity service" class InvalidStructure(TempestException): message = "Invalid structure of table with details" class InvalidAPIVersionString(TempestException): message = ("API Version String %(version)s is of invalid format. Must " "be of format MajorNum.MinorNum or string 'latest'.") class JSONSchemaNotFound(TempestException): message = ("JSON Schema for %(version)s is not found in\n" " %(schema_versions_info)s") class InvalidAPIVersionRange(TempestException): message = ("The API version range is invalid.") 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 InvalidScope(TempestException): message = "Invalid Scope %(scope)s for %(auth_provider)s" 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") class UnknownServiceClient(TempestException): message = "Service clients named %(services)s are not known" class ServiceClientRegistrationException(TempestException): message = ("Error registering module %(name)s in path %(module_path)s, " "with service %(service_version)s and clients " "%(client_names)s: %(detailed_error)s") class PluginRegistrationException(TempestException): message = "Error registering plugin %(name)s: %(detailed_error)s" class VolumeBackupException(TempestException): message = "Volume backup %(backup_id)s failed and is in ERROR status" class DeleteErrorException(TempestException): message = ("Resource %(resource_id)s failed to delete " "and is in ERROR status") class InvalidTestResource(TempestException): message = "%(name)s is not a valid %(type)s, or the name is ambiguous" class InvalidParam(TempestException): message = ("Invalid Parameter passed: %(invalid_param)s") tempest-17.2.0/tempest/lib/services/0000775000175100017510000000000013207045130017353 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/image/0000775000175100017510000000000013207045130020435 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/image/v1/0000775000175100017510000000000013207045130020763 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/image/v1/image_members_client.py0000666000175100017510000000500313207044712025474 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 ImageMembersClient(rest_client.RestClient): api_version = "v1" def list_image_members(self, image_id): """List all members of an image.""" url = 'images/%s/members' % image_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_shared_images(self, tenant_id): """List image memberships for the given tenant. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v1/#list-shared-images """ url = 'shared-images/%s' % tenant_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_image_member(self, image_id, member_id, **kwargs): """Add a member to an image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v1/#add-member-to-image """ url = 'images/%s/members/%s' % (image_id, member_id) body = json.dumps({'member': kwargs}) resp, __ = self.put(url, body) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) def delete_image_member(self, image_id, member_id): """Removes a membership from the image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v1/#remove-member """ url = 'images/%s/members/%s' % (image_id, member_id) resp, __ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v1/__init__.py0000666000175100017510000000147313207044712023110 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.image.v1.image_members_client import \ ImageMembersClient from tempest.lib.services.image.v1.images_client import ImagesClient __all__ = ['ImageMembersClient', 'ImagesClient'] tempest-17.2.0/tempest/lib/services/image/v1/images_client.py0000666000175100017510000001252513207044712024154 0ustar zuulzuul00000000000000# 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 functools from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc CHUNKSIZE = 1024 * 64 # 64kB class ImagesClient(rest_client.RestClient): api_version = "v1" def _create_with_data(self, headers, data): # We are going to do chunked transfert, so split the input data # info fixed-sized chunks. headers['Content-Type'] = 'application/octet-stream' data = iter(functools.partial(data.read, CHUNKSIZE), b'') resp, body = self.request('POST', 'images', headers=headers, body=data, chunked=True) self._error_checker(resp, body) body = json.loads(body) return rest_client.ResponseBody(resp, body) def _update_with_data(self, image_id, headers, data): # We are going to do chunked transfert, so split the input data # info fixed-sized chunks. headers['Content-Type'] = 'application/octet-stream' data = iter(functools.partial(data.read, CHUNKSIZE), b'') url = 'images/%s' % image_id resp, body = self.request('PUT', url, headers=headers, body=data, chunked=True) self._error_checker(resp, body) body = json.loads(body) return rest_client.ResponseBody(resp, body) @property def http(self): if self._http is None: self._http = self._get_http() return self._http def create_image(self, data=None, headers=None): """Create an image. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/image/v1/index.html#create-image """ if headers is None: headers = {} if data is not None: return self._create_with_data(headers, data) resp, body = self.post('images', None, headers) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_image(self, image_id, data=None, headers=None): """Update an image. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/image/v1/index.html#update-image """ if headers is None: headers = {} if data is not None: return self._update_with_data(image_id, headers, data) url = 'images/%s' % image_id resp, body = self.put(url, None, headers) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_image(self, image_id): url = 'images/%s' % image_id resp, body = self.delete(url) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_images(self, detail=False, **kwargs): """Return a list of all images filtered by input parameters. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v1/#list-images Most parameters except the following are passed to the API without any changes. :param changes_since: The name is changed to changes-since """ url = 'images' if detail: url += '/detail' if 'changes_since' in kwargs: kwargs['changes-since'] = kwargs.pop('changes_since') if kwargs: url += '?%s' % urllib.urlencode(kwargs) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def check_image(self, image_id): """Check image metadata.""" url = 'images/%s' % image_id resp, body = self.head(url) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_image(self, image_id): """Get image details plus the image itself.""" url = 'images/%s' % image_id resp, body = self.get(url) self.expected_success(200, resp.status) return rest_client.ResponseBodyData(resp, body) def is_resource_deleted(self, id): try: resp = self.check_image(id) if resp.response["x-image-meta-status"] == 'deleted': return True except lib_exc.NotFound: return True return False @property def resource_type(self): """Returns the primary type of resource this client works with.""" return 'image_meta' tempest-17.2.0/tempest/lib/services/image/__init__.py0000666000175100017510000000133013207044712022552 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.image import v1 from tempest.lib.services.image import v2 __all__ = ['v1', 'v2'] tempest-17.2.0/tempest/lib/services/image/v2/0000775000175100017510000000000013207045130020764 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/image/v2/resource_types_client.py0000666000175100017510000000611713207044712025763 0ustar zuulzuul00000000000000# 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.common import rest_client class ResourceTypesClient(rest_client.RestClient): api_version = "v2" def list_resource_types(self): """Lists all resource types. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-resource-types """ url = 'metadefs/resource_types' resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_resource_type_association(self, namespace_id, **kwargs): """Creates a resource type association in given namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-resource-type-association """ url = 'metadefs/namespaces/%s/resource_types' % namespace_id data = json.dumps(kwargs) resp, body = self.post(url, data) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_resource_type_association(self, namespace_id): """Lists resource type associations in given namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-resource-type-associations """ url = 'metadefs/namespaces/%s/resource_types' % namespace_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_resource_type_association(self, namespace_id, resource_name): """Removes resource type association in given namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#remove-resource-type-association """ url = 'metadefs/namespaces/%s/resource_types/%s' % (namespace_id, resource_name) resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v2/image_members_client.py0000666000175100017510000000632113207044712025501 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 ImageMembersClient(rest_client.RestClient): api_version = "v2" def list_image_members(self, image_id): """List image members. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#list-image-members """ url = 'images/%s/members' % image_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_image_member(self, image_id, **kwargs): """Create an image member. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#create-image-member """ url = 'images/%s/members' % image_id data = json.dumps(kwargs) resp, body = self.post(url, data) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_image_member(self, image_id, member_id, **kwargs): """Update an image member. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#update-image-member """ url = 'images/%s/members/%s' % (image_id, member_id) data = json.dumps(kwargs) resp, body = self.put(url, data) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_image_member(self, image_id, member_id): """Show an image member. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#show-image-member-details """ url = 'images/%s/members/%s' % (image_id, member_id) resp, body = self.get(url) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, json.loads(body)) def delete_image_member(self, image_id, member_id): """Delete an image member. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#delete-image-member """ url = 'images/%s/members/%s' % (image_id, member_id) resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v2/namespaces_client.py0000666000175100017510000000660513207044712025031 0ustar zuulzuul00000000000000# 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.common import rest_client class NamespacesClient(rest_client.RestClient): api_version = "v2" def create_namespace(self, **kwargs): """Create a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-namespace """ data = json.dumps(kwargs) resp, body = self.post('metadefs/namespaces', data) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_namespaces(self): """List namespaces For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-namespaces """ url = 'metadefs/namespaces' resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_namespace(self, namespace): """Show namespace details. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#get-namespace-details """ url = 'metadefs/namespaces/%s' % namespace resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_namespace(self, namespace, **kwargs): """Update a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#update-namespace """ # NOTE: On Glance API, we need to pass namespace on both URI # and a request body. params = {'namespace': namespace} params.update(kwargs) data = json.dumps(params) url = 'metadefs/namespaces/%s' % namespace resp, body = self.put(url, body=data) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_namespace(self, namespace): """Delete a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#delete-namespace """ url = 'metadefs/namespaces/%s' % namespace resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v2/__init__.py0000666000175100017510000000311713207044712023106 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.image.v2.image_members_client import \ ImageMembersClient from tempest.lib.services.image.v2.images_client import ImagesClient from tempest.lib.services.image.v2.namespace_objects_client import \ NamespaceObjectsClient from tempest.lib.services.image.v2.namespace_properties_client import \ NamespacePropertiesClient from tempest.lib.services.image.v2.namespace_tags_client import \ NamespaceTagsClient from tempest.lib.services.image.v2.namespaces_client import NamespacesClient from tempest.lib.services.image.v2.resource_types_client import \ ResourceTypesClient from tempest.lib.services.image.v2.schemas_client import SchemasClient from tempest.lib.services.image.v2.versions_client import VersionsClient __all__ = ['ImageMembersClient', 'ImagesClient', 'NamespaceObjectsClient', 'NamespacePropertiesClient', 'NamespaceTagsClient', 'NamespacesClient', 'ResourceTypesClient', 'SchemasClient', 'VersionsClient'] tempest-17.2.0/tempest/lib/services/image/v2/versions_client.py0000666000175100017510000000245113207044712024555 0ustar zuulzuul00000000000000# Copyright 2017 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 time from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class VersionsClient(rest_client.RestClient): api_version = "v2" def list_versions(self): """List API versions""" version_url = self._get_base_version_url() start = time.time() resp, body = self.raw_request(version_url, 'GET') end = time.time() self._log_request('GET', version_url, resp, secs=(end - start), resp_body=body) self._error_checker(resp, body) self.expected_success(300, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/image/v2/namespace_properties_client.py0000666000175100017510000000743613207044712027125 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. # 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 NamespacePropertiesClient(rest_client.RestClient): api_version = "v2" def list_namespace_properties(self, namespace): """Lists property definitions in a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-properties """ url = 'metadefs/namespaces/%s/properties' % namespace resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_namespace_property(self, namespace, **kwargs): """Creates a property definition in a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-property """ url = 'metadefs/namespaces/%s/properties' % namespace data = json.dumps(kwargs) resp, body = self.post(url, data) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_namespace_properties(self, namespace, property_name): """Shows the definition for a property. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#show-property-definition """ url = 'metadefs/namespaces/%s/properties/%s' % (namespace, property_name) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_namespace_properties(self, namespace, property_name, **kwargs): """Updates a property definition. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#update-property-definition """ url = 'metadefs/namespaces/%s/properties/%s' % (namespace, property_name) data = json.dumps(kwargs) resp, body = self.put(url, data) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_namespace_property(self, namespace, property_name): """Removes a property definition from a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#remove-property-definition """ url = 'metadefs/namespaces/%s/properties/%s' % (namespace, property_name) resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v2/namespace_tags_client.py0000666000175100017510000001211213207044712025652 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. # 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.common import rest_client class NamespaceTagsClient(rest_client.RestClient): api_version = "v2" def create_namespace_tag(self, namespace, tag_name): """Adds a tag to the list of namespace tag definitions. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-tag-definition """ url = 'metadefs/namespaces/%s/tags/%s' % (namespace, tag_name) resp, body = self.post(url, None) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_namespace_tag(self, namespace, tag_name): """Gets a definition for a tag. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/image/v2/metadefs-index.html#get-tag-definition """ url = 'metadefs/namespaces/%s/tags/%s' % (namespace, tag_name) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_namespace_tag(self, namespace, tag_name, **kwargs): """Renames a tag definition. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#update-tag-definition """ url = 'metadefs/namespaces/%s/tags/%s' % (namespace, tag_name) data = json.dumps(kwargs) resp, body = self.put(url, data) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_namespace_tag(self, namespace, tag_name): """Deletes a tag definition within a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#delete-tag-definition """ url = 'metadefs/namespaces/%s/tags/%s' % (namespace, tag_name) resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) def create_namespace_tags(self, namespace, **kwargs): """Creates one or more tag definitions in a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-tags """ url = 'metadefs/namespaces/%s/tags' % namespace data = json.dumps(kwargs) resp, body = self.post(url, data) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_namespace_tags(self, namespace, **params): """Lists the tag definitions within a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-tags """ url = 'metadefs/namespaces/%s/tags' % namespace if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_namespace_tags(self, namespace): """Deletes all tag definitions within a namespace. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#delete-all-tag-definitions """ url = 'metadefs/namespaces/%s/tags' % namespace resp, _ = self.delete(url) # NOTE(rosmaita): Bug 1656183 fixed the success response code for # this call to make it consistent with the other metadefs delete # calls. Accept both codes in case tempest is being run against # an old Glance. self.expected_success([200, 204], resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v2/schemas_client.py0000666000175100017510000000200313207044712024321 0ustar zuulzuul00000000000000# 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.common import rest_client class SchemasClient(rest_client.RestClient): api_version = "v2" def show_schema(self, schema): url = 'schemas/%s' % schema resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/image/v2/namespace_objects_client.py0000666000175100017510000000712113207044712026351 0ustar zuulzuul00000000000000# Copyright 2016 EasyStack. # 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.common import rest_client class NamespaceObjectsClient(rest_client.RestClient): api_version = "v2" def list_namespace_objects(self, namespace, **kwargs): """Lists all namespace objects. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-objects """ url = 'metadefs/namespaces/%s/objects' % namespace if kwargs: url += '?%s' % urllib.urlencode(kwargs) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_namespace_object(self, namespace, **kwargs): """Create a namespace object For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-object """ url = 'metadefs/namespaces/%s/objects' % namespace data = json.dumps(kwargs) resp, body = self.post(url, data) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_namespace_object(self, namespace, object_name, **kwargs): """Update a namespace object For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#update-object """ url = 'metadefs/namespaces/%s/objects/%s' % (namespace, object_name) data = json.dumps(kwargs) resp, body = self.put(url, data) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_namespace_object(self, namespace, object_name): """Show a namespace object For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#show-object """ url = 'metadefs/namespaces/%s/objects/%s' % (namespace, object_name) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_namespace_object(self, namespace, object_name): """Delete a namespace object For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#delete-object """ url = 'metadefs/namespaces/%s/objects/%s' % (namespace, object_name) resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/image/v2/images_client.py0000666000175100017510000001521213207044712024151 0ustar zuulzuul00000000000000# 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 functools from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc CHUNKSIZE = 1024 * 64 # 64kB class ImagesClient(rest_client.RestClient): api_version = "v2" def update_image(self, image_id, patch): """Update an image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/index.html#update-an-image """ data = json.dumps(patch) headers = {"Content-Type": "application/openstack-images-v2.0" "-json-patch"} resp, body = self.patch('images/%s' % image_id, data, headers) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_image(self, **kwargs): """Create an image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/index.html#create-an-image """ data = json.dumps(kwargs) resp, body = self.post('images', data) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def deactivate_image(self, image_id): """Deactivate image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#deactivate-image """ url = 'images/%s/actions/deactivate' % image_id resp, body = self.post(url, None) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def reactivate_image(self, image_id): """Reactivate image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#reactivate-image """ url = 'images/%s/actions/reactivate' % image_id resp, body = self.post(url, None) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_image(self, image_id): """Delete image. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#delete-an-image """ url = 'images/%s' % image_id resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) def list_images(self, params=None): """List images. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#show-images """ url = 'images' 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_image(self, image_id): """Show image details. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#show-image-details """ url = 'images/%s' % image_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(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): """Returns the primary type of resource this client works with.""" return 'image' def store_image_file(self, image_id, data): """Upload binary image data. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#upload-binary-image-data """ url = 'images/%s/file' % image_id # We are going to do chunked transfert, so split the input data # info fixed-sized chunks. headers = {'Content-Type': 'application/octet-stream'} data = iter(functools.partial(data.read, CHUNKSIZE), b'') resp, body = self.request('PUT', url, headers=headers, body=data, chunked=True) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_image_file(self, image_id): """Download binary image data. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#download-binary-image-data """ url = 'images/%s/file' % image_id resp, body = self.get(url) self.expected_success(200, resp.status) return rest_client.ResponseBodyData(resp, body) def add_image_tag(self, image_id, tag): """Add an image tag. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#add-image-tag """ url = 'images/%s/tags/%s' % (image_id, tag) resp, body = self.put(url, body=None) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def delete_image_tag(self, image_id, tag): """Delete an image tag. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/image/v2/#delete-image-tag """ url = 'images/%s/tags/%s' % (image_id, tag) resp, _ = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/clients.py0000666000175100017510000005127113207044712021403 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright (c) 2016 Hewlett-Packard Enterprise 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 import importlib import inspect import sys import warnings from debtcollector import removals from oslo_log import log as logging import testtools from tempest.lib import auth from tempest.lib.common.utils import misc from tempest.lib import exceptions from tempest.lib.services import compute from tempest.lib.services import identity from tempest.lib.services import image from tempest.lib.services import network from tempest.lib.services import object_storage from tempest.lib.services import volume warnings.simplefilter("once") LOG = logging.getLogger(__name__) def tempest_modules(): """Dict of service client modules available in Tempest. Provides a dict of stable service modules available in Tempest, with ``service_version`` as key, and the module object as value. """ return { 'compute': compute, 'identity.v2': identity.v2, 'identity.v3': identity.v3, 'image.v1': image.v1, 'image.v2': image.v2, 'network': network, 'object-storage': object_storage, 'volume.v1': volume.v1, 'volume.v2': volume.v2, 'volume.v3': volume.v3 } def available_modules(): """Set of service client modules available in Tempest and plugins Set of stable service clients from Tempest and service clients exposed by plugins. This set of available modules can be used for automatic configuration. :raise PluginRegistrationException: if a plugin exposes a service_version already defined by Tempest or another plugin. Examples:: from tempest import config params = {} for service_version in available_modules(): service = service_version.split('.')[0] params[service] = config.service_client_config(service) service_clients = ServiceClients(creds, identity_uri, client_parameters=params) """ extra_service_versions = set([]) _tempest_modules = set(tempest_modules()) plugin_services = ClientsRegistry().get_service_clients() name_conflicts = [] for plugin_name in plugin_services: plug_service_versions = set([x['service_version'] for x in plugin_services[plugin_name]]) # If a plugin exposes a duplicate service_version raise an exception if plug_service_versions: if not plug_service_versions.isdisjoint(extra_service_versions): detailed_error = ( 'Plugin %s is trying to register a service %s already ' 'claimed by another one' % (plugin_name, extra_service_versions & plug_service_versions)) name_conflicts.append(exceptions.PluginRegistrationException( name=plugin_name, detailed_error=detailed_error)) extra_service_versions |= plug_service_versions if name_conflicts: LOG.error( 'Failed to list available modules due to name conflicts: %s', name_conflicts) raise testtools.MultipleExceptions(*name_conflicts) return _tempest_modules | extra_service_versions @misc.singleton class ClientsRegistry(object): """Registry of all service clients available from plugins""" def __init__(self): self._service_clients = {} def register_service_client(self, plugin_name, service_client_data): if plugin_name in self._service_clients: detailed_error = 'Clients for plugin %s already registered' raise exceptions.PluginRegistrationException( name=plugin_name, detailed_error=detailed_error % plugin_name) self._service_clients[plugin_name] = service_client_data LOG.debug("Successfully registered plugin %s in the service client " "registry with configuration: %s", plugin_name, service_client_data) def get_service_clients(self): return self._service_clients class ClientsFactory(object): """Builds service clients for a service client module This class implements the logic of feeding service client parameters to service clients from a specific module. It allows setting the parameters once and obtaining new instances of the clients without the need of passing any parameter. ClientsFactory can be used directly, or consumed via the `ServiceClients` class, which manages the authorization part. """ def __init__(self, module_path, client_names, auth_provider, **kwargs): """Initialises the client factory :param module_path: Path to module that includes all service clients. All service client classes must be exposed by a single module. If they are separated in different modules, defining __all__ in the root module can help, similar to what is done by service clients in tempest. :param client_names: List or set of names of the service client classes. :param auth_provider: The auth provider used to initialise client. :param kwargs: Parameters to be passed to all clients. Parameters values can be overwritten when clients are initialised, but parameters cannot be deleted. :raise ImportError: if the specified module_path cannot be imported Example:: # Get credentials and an auth_provider clients = ClientsFactory( module_path='my_service.my_service_clients', client_names=['ServiceClient1', 'ServiceClient2'], auth_provider=auth_provider, service='my_service', region='region1') my_api_client = clients.MyApiClient() my_api_client_region2 = clients.MyApiClient(region='region2') """ # Import the module. If it's not importable, the raised exception # provides good enough information about what happened _module = importlib.import_module(module_path) # If any of the classes is not in the module we fail for class_name in client_names: # TODO(andreaf) This always passes all parameters to all clients. # In future to allow clients to specify the list of parameters # that they accept based out of a list of standard ones. # Obtain the class klass = self._get_class(_module, class_name) final_kwargs = copy.copy(kwargs) # Set the function as an attribute of the factory setattr(self, class_name, self._get_partial_class( klass, auth_provider, final_kwargs)) def _get_partial_class(self, klass, auth_provider, kwargs): # Define a function that returns a new class instance by # combining default kwargs with extra ones def partial_class(alias=None, **later_kwargs): """Returns a callable the initialises a service client Builds a callable that accepts kwargs, which are passed through to the __init__ of the service client, along with a set of defaults set in factory at factory __init__ time. Original args in the service client can only be passed as kwargs. It accepts one extra parameter 'alias' compared to the original service client. When alias is provided, the returned callable will also set an attribute called with a name defined in 'alias', which contains the instance of the service client. :param alias: str Name of the attribute set on the factory once the callable is invoked which contains the initialised service client. If None, no attribute is set. :param later_kwargs: kwargs passed through to the service client __init__ on top of defaults set at factory level. """ kwargs.update(later_kwargs) _client = klass(auth_provider=auth_provider, **kwargs) if alias: setattr(self, alias, _client) return _client return partial_class @classmethod def _get_class(cls, module, class_name): klass = getattr(module, class_name, None) if not klass: msg = 'Invalid class name, %s is not found in %s' raise AttributeError(msg % (class_name, module)) if not inspect.isclass(klass): msg = 'Expected a class, got %s of type %s instead' raise TypeError(msg % (klass, type(klass))) return klass class ServiceClients(object): """Service client provider class The ServiceClients object provides a useful means for tests to access service clients configured for a specified set of credentials. It hides some of the complexity from the authorization and configuration layers. Examples:: # johndoe is a tempest.lib.auth.Credentials type instance johndoe_clients = clients.ServiceClients(johndoe, identity_uri) # List servers in default region johndoe_servers_client = johndoe_clients.compute.ServersClient() johndoe_servers = johndoe_servers_client.list_servers() # List servers in Region B johndoe_servers_client_B = johndoe_clients.compute.ServersClient( region='B') johndoe_servers = johndoe_servers_client_B.list_servers() """ # NOTE(andreaf) This class does not depend on tempest configuration # and its meant for direct consumption by external clients such as tempest # plugins. Tempest provides a wrapper class, `clients.Manager`, that # initialises this class using values from tempest CONF object. The wrapper # class should only be used by tests hosted in Tempest. @removals.removed_kwarg('client_parameters') def __init__(self, credentials, identity_uri, region=None, scope='project', disable_ssl_certificate_validation=True, ca_certs=None, trace_requests='', client_parameters=None, proxy_url=None): """Service Clients provider Instantiate a `ServiceClients` object, from a set of credentials and an identity URI. The identity version is inferred from the credentials object. Optionally auth scope can be provided. A few parameters can be given a value which is applied as default for all service clients: region, dscv, ca_certs, trace_requests. Parameters dscv, ca_certs and trace_requests all apply to the auth provider as well as any service clients provided by this manager. Any other client parameter should be set via ClientsRegistry. Client parameter used to be set via client_parameters, but this is deprecated, and it is actually already not honoured anymore: https://launchpad.net/bugs/1680915. The list of available parameters is defined in the service clients interfaces. For reference, most clients will accept 'region', 'service', 'endpoint_type', 'build_timeout' and 'build_interval', which are all inherited from RestClient. The `config` module in Tempest exposes an helper function `service_client_config` that can be used to extract from configuration a dictionary ready to be injected in kwargs. Exceptions are: - Token clients for 'identity' must be given an 'auth_url' parameter - Volume client for 'volume' accepts 'default_volume_size' - Servers client from 'compute' accepts 'enable_instance_password' If Tempest configuration is used, parameters will be loaded in the Registry automatically for all service client (Tempest stable ones and plugins). Examples:: identity_params = config.service_client_config('identity') params = { 'identity': identity_params, 'compute': {'region': 'region2'}} manager = lib_manager.Manager( my_creds, identity_uri, client_parameters=params) :param credentials: An instance of `auth.Credentials` :param identity_uri: URI of the identity API. This should be a mandatory parameter, and it will so soon. :param region: Default value of region for service clients. :param scope: default scope for tokens produced by the auth provider :param disable_ssl_certificate_validation: Applies to auth and to all service clients. :param ca_certs: Applies to auth and to all service clients. :param trace_requests: Applies to auth and to all service clients. :param client_parameters: Dictionary with parameters for service clients. Keys of the dictionary are the service client service name, as declared in `service_clients.available_modules()` except for the version. Values are dictionaries of parameters that are going to be passed to all clients in the service client module. :param proxy_url: Applies to auth and to all service clients, set a proxy url for the clients to use. """ self._registered_services = set([]) self.credentials = credentials self.identity_uri = identity_uri if not identity_uri: raise exceptions.InvalidCredentials( 'ServiceClients requires a non-empty identity_uri.') self.region = region # Check if passed or default credentials are valid if not self.credentials.is_valid(): raise exceptions.InvalidCredentials() # Get the identity classes matching the provided credentials # TODO(andreaf) Define a new interface in Credentials to get # the API version from an instance identity = [(k, auth.IDENTITY_VERSION[k][1]) for k in auth.IDENTITY_VERSION.keys() if isinstance(self.credentials, auth.IDENTITY_VERSION[k][0])] # Zero matches or more than one are both not valid. if len(identity) != 1: raise exceptions.InvalidCredentials() self.auth_version, auth_provider_class = identity[0] self.dscv = disable_ssl_certificate_validation self.ca_certs = ca_certs self.trace_requests = trace_requests self.proxy_url = proxy_url # Creates an auth provider for the credentials self.auth_provider = auth_provider_class( self.credentials, self.identity_uri, scope=scope, disable_ssl_certificate_validation=self.dscv, ca_certs=self.ca_certs, trace_requests=self.trace_requests, proxy_url=proxy_url) # Setup some defaults for client parameters of registered services client_parameters = client_parameters or {} self.parameters = {} # Parameters are provided for unversioned services all_modules = available_modules() unversioned_services = set( [x.split('.')[0] for x in all_modules]) for service in unversioned_services: self.parameters[service] = self._setup_parameters( client_parameters.pop(service, {})) # Check that no client parameters was supplied for unregistered clients if client_parameters: raise exceptions.UnknownServiceClient( services=list(client_parameters.keys())) # Register service clients from the registry (__tempest__ and plugins) clients_registry = ClientsRegistry() plugin_service_clients = clients_registry.get_service_clients() registration_errors = [] for plugin in plugin_service_clients: service_clients = plugin_service_clients[plugin] # Each plugin returns a list of service client parameters for service_client in service_clients: # NOTE(andreaf) If a plugin cannot register, stop the # registration process, log some details to help # troubleshooting, and re-raise try: self.register_service_client_module(**service_client) except Exception: registration_errors.append(sys.exc_info()) LOG.exception( 'Failed to register service client from plugin %s ' 'with parameters %s', plugin, service_client) if registration_errors: raise testtools.MultipleExceptions(*registration_errors) def register_service_client_module(self, name, service_version, module_path, client_names, **kwargs): """Register a service client module Initiates a client factory for the specified module, using this class auth_provider, and accessible via a `name` attribute in the service client. :param name: Name used to access the client :param service_version: Name of the service complete with version. Used to track registered services. When a plugin implements it, it can be used by other plugins to obtain their configuration. :param module_path: Path to module that includes all service clients. All service client classes must be exposed by a single module. If they are separated in different modules, defining __all__ in the root module can help, similar to what is done by service clients in tempest. :param client_names: List or set of names of service client classes. :param kwargs: Extra optional parameters to be passed to all clients. ServiceClient provides defaults for region, dscv, ca_certs, http proxies and trace_requests. :raise ServiceClientRegistrationException: if the provided name is already in use or if service_version is already registered. :raise ImportError: if module_path cannot be imported. """ if hasattr(self, name): using_name = getattr(self, name) detailed_error = 'Module name already in use: %s' % using_name raise exceptions.ServiceClientRegistrationException( name=name, service_version=service_version, module_path=module_path, client_names=client_names, detailed_error=detailed_error) if service_version in self.registered_services: detailed_error = 'Service %s already registered.' % service_version raise exceptions.ServiceClientRegistrationException( name=name, service_version=service_version, module_path=module_path, client_names=client_names, detailed_error=detailed_error) params = dict(region=self.region, disable_ssl_certificate_validation=self.dscv, ca_certs=self.ca_certs, trace_requests=self.trace_requests, proxy_url=self.proxy_url) params.update(kwargs) # Instantiate the client factory _factory = ClientsFactory(module_path=module_path, client_names=client_names, auth_provider=self.auth_provider, **params) # Adds the client factory to the service_client setattr(self, name, _factory) # Add the name of the new service in self.SERVICES for discovery self._registered_services.add(service_version) @property def registered_services(self): return self._registered_services def _setup_parameters(self, parameters): """Setup default values for client parameters Region by default is the region passed as an __init__ parameter. Checks that no parameter for an unknown service is provided. """ _parameters = {} # Use region from __init__ if self.region: _parameters['region'] = self.region # Update defaults with specified parameters _parameters.update(parameters) # If any parameter is left, parameters for an unknown service were # provided as input. Fail rather than ignore silently. return _parameters tempest-17.2.0/tempest/lib/services/network/0000775000175100017510000000000013207045130021044 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/network/tags_client.py0000666000175100017510000000666213207044712023733 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client from tempest.lib.services.network import base class TagsClient(base.BaseNetworkClient): def create_tag(self, resource_type, resource_id, tag): """Adds a tag on the resource. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#add-a-tag """ uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag) return self.update_resource( uri, json.dumps({}), expect_response_code=201, expect_empty_body=True) def check_tag_existence(self, resource_type, resource_id, tag): """Confirm that a given tag is set on the resource. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#confirm-a-tag """ # TODO(felipemonteiro): Use the "check_resource" method in # ``BaseNetworkClient`` once it has been implemented. uri = '%s/%s/%s/tags/%s' % ( self.uri_prefix, resource_type, resource_id, tag) resp, _ = self.get(uri) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) def update_all_tags(self, resource_type, resource_id, tags): """Replace all tags on the resource. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#replace-all-tags """ uri = '/%s/%s/tags' % (resource_type, resource_id) put_body = {"tags": tags} return self.update_resource(uri, put_body) def delete_tag(self, resource_type, resource_id, tag): """Removes a tag on the resource. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#remove-a-tag """ uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag) return self.delete_resource(uri) def delete_all_tags(self, resource_type, resource_id): """Removes all tags on the resource. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#remove-all-tags """ uri = '/%s/%s/tags' % (resource_type, resource_id) return self.delete_resource(uri) def list_tags(self, resource_type, resource_id): """Retrieves the tags for a resource. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#obtain-tag-list """ uri = '/%s/%s/tags' % (resource_type, resource_id) return self.list_resources(uri) tempest-17.2.0/tempest/lib/services/network/ports_client.py0000666000175100017510000000604113207044712024133 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.lib.services.network import base class PortsClient(base.BaseNetworkClient): def create_port(self, **kwargs): """Creates a port on a network. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-port """ uri = '/ports' post_data = {'port': kwargs} return self.create_resource(uri, post_data) def update_port(self, port_id, **kwargs): """Updates a port. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-port """ uri = '/ports/%s' % port_id post_data = {'port': kwargs} return self.update_resource(uri, post_data) def show_port(self, port_id, **fields): """Shows details for a port. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-port-details """ uri = '/ports/%s' % port_id return self.show_resource(uri, **fields) def delete_port(self, port_id): """Deletes a port. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#delete-port """ uri = '/ports/%s' % port_id return self.delete_resource(uri) def list_ports(self, **filters): """Lists ports to which the tenant has access. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-ports """ uri = '/ports' return self.list_resources(uri, **filters) def create_bulk_ports(self, **kwargs): """Create multiple ports in a single request. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#bulk-create-ports """ uri = '/ports' return self.create_resource(uri, kwargs) def is_resource_deleted(self, id): try: self.show_port(id) except lib_exc.NotFound: return True return False tempest-17.2.0/tempest/lib/services/network/routers_client.py0000666000175100017510000000635613207044712024500 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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, **kwargs): """Create a router. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-router """ post_body = {'router': kwargs} uri = '/routers' return self.create_resource(uri, post_body) def update_router(self, router_id, **kwargs): """Updates a logical router. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-router """ uri = '/routers/%s' % router_id update_body = {'router': kwargs} return self.update_resource(uri, update_body) def show_router(self, router_id, **fields): """Shows details for a router. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-router-details """ 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): """Lists logical routers. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-routers """ uri = '/routers' return self.list_resources(uri, **filters) def add_router_interface(self, router_id, **kwargs): """Add router interface. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#add-interface-to-router """ 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. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#remove-interface-from-router """ 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) tempest-17.2.0/tempest/lib/services/network/metering_labels_client.py0000666000175100017510000000451213207044712026121 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): """Creates an L3 metering label. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-metering-label """ 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): """Shows details for a metering label. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-metering-label-details """ uri = '/metering/metering-labels/%s' % metering_label_id return self.show_resource(uri, **fields) def delete_metering_label(self, metering_label_id): """Deletes an L3 metering label. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#delete-metering-label """ uri = '/metering/metering-labels/%s' % metering_label_id return self.delete_resource(uri) def list_metering_labels(self, **filters): """Lists all L3 metering labels that belong to the tenant. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-metering-labels """ uri = '/metering/metering-labels' return self.list_resources(uri, **filters) tempest-17.2.0/tempest/lib/services/network/subnets_client.py0000666000175100017510000000525513207044712024455 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): """Creates a subnet on a network. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-subnet """ uri = '/subnets' post_data = {'subnet': kwargs} return self.create_resource(uri, post_data) def update_subnet(self, subnet_id, **kwargs): """Updates a subnet. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-subnet """ uri = '/subnets/%s' % subnet_id post_data = {'subnet': kwargs} return self.update_resource(uri, post_data) def show_subnet(self, subnet_id, **fields): """Shows details for a subnet. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-subnet-details """ 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): """Lists subnets to which the tenant has access. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-subnets """ uri = '/subnets' return self.list_resources(uri, **filters) def create_bulk_subnets(self, **kwargs): """Create multiple subnets in a single request. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#bulk-create-subnet """ uri = '/subnets' return self.create_resource(uri, kwargs) tempest-17.2.0/tempest/lib/services/network/agents_client.py0000666000175100017510000000563613207044712024256 0ustar zuulzuul00000000000000# 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, expect_empty_body=True) 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, expect_empty_body=True) tempest-17.2.0/tempest/lib/services/network/__init__.py0000666000175100017510000000435413207044712023172 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.network.agents_client import AgentsClient from tempest.lib.services.network.extensions_client import ExtensionsClient 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 from tempest.lib.services.network.routers_client import RoutersClient from tempest.lib.services.network.security_group_rules_client import \ SecurityGroupRulesClient from tempest.lib.services.network.security_groups_client import \ SecurityGroupsClient from tempest.lib.services.network.service_providers_client import \ ServiceProvidersClient from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient from tempest.lib.services.network.subnets_client import SubnetsClient from tempest.lib.services.network.tags_client import TagsClient from tempest.lib.services.network.versions_client import NetworkVersionsClient __all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient', 'MeteringLabelRulesClient', 'MeteringLabelsClient', 'NetworksClient', 'NetworkVersionsClient', 'PortsClient', 'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient', 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient', 'TagsClient'] tempest-17.2.0/tempest/lib/services/network/base.py0000666000175100017510000000653413207044712022347 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class BaseNetworkClient(rest_client.RestClient): """Base class for Tempest REST clients for Neutron. Child classes use v2 of the Neutron API, since the V1 API has been removed from the code base. """ version = '2.0' uri_prefix = "v2.0" def list_resources(self, uri, **filters): req_uri = self.uri_prefix + uri if filters: req_uri += '?' + urllib.urlencode(filters, doseq=1) resp, body = self.get(req_uri) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_resource(self, uri): req_uri = self.uri_prefix + uri resp, body = self.delete(req_uri) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_resource(self, uri, **fields): # fields is a dict which key is 'fields' and value is a # list of field's name. An example: # {'fields': ['id', 'name']} req_uri = self.uri_prefix + uri if fields: req_uri += '?' + urllib.urlencode(fields, doseq=1) resp, body = self.get(req_uri) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_resource(self, uri, post_data, expect_empty_body=False, expect_response_code=201): req_uri = self.uri_prefix + uri req_post_data = json.dumps(post_data) resp, body = self.post(req_uri, req_post_data) # NOTE: RFC allows both a valid non-empty body and an empty body for # response of POST API. If a body is expected not empty, we decode the # body. Otherwise we returns the body as it is. if not expect_empty_body: body = json.loads(body) else: body = None self.expected_success(expect_response_code, resp.status) return rest_client.ResponseBody(resp, body) def update_resource(self, uri, post_data, expect_empty_body=False, expect_response_code=200): req_uri = self.uri_prefix + uri req_post_data = json.dumps(post_data) resp, body = self.put(req_uri, req_post_data) # NOTE: RFC allows both a valid non-empty body and an empty body for # response of PUT API. If a body is expected not empty, we decode the # body. Otherwise we returns the body as it is. if not expect_empty_body: body = json.loads(body) else: body = None self.expected_success(expect_response_code, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/network/versions_client.py0000666000175100017510000000311613207044712024634 0ustar zuulzuul00000000000000# Copyright 2016 VMware, 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 time from oslo_serialization import jsonutils as json from tempest.lib.services.network import base class NetworkVersionsClient(base.BaseNetworkClient): def list_versions(self): """Do a GET / to fetch available API version information.""" version_url = self._get_base_version_url() # Note: we do a raw_request here because we want to use # an unversioned URL, not "v2/$project_id/". # Since raw_request doesn't log anything, we do that too. start = time.time() self._log_request_start('GET', version_url) response, body = self.raw_request(version_url, 'GET') self._error_checker(response, body) end = time.time() self._log_request('GET', version_url, response, secs=(end - start), resp_body=body) self.response_checker('GET', response, body) self.expected_success(200, response.status) body = json.loads(body) return body tempest-17.2.0/tempest/lib/services/network/floating_ips_client.py0000666000175100017510000000500613207044712025442 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): """Creates a floating IP. If you specify port information, associates the floating IP with an internal port. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-floating-ip """ uri = '/floatingips' post_data = {'floatingip': kwargs} return self.create_resource(uri, post_data) def update_floatingip(self, floatingip_id, **kwargs): """Updates a floating IP and its association with an internal port. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-floating-ip """ uri = '/floatingips/%s' % floatingip_id post_data = {'floatingip': kwargs} return self.update_resource(uri, post_data) def show_floatingip(self, floatingip_id, **fields): """Shows details for a floating IP. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-floating-ip-details """ 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): """Lists floating IPs. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-floating-ips """ uri = '/floatingips' return self.list_resources(uri, **filters) tempest-17.2.0/tempest/lib/services/network/subnetpools_client.py0000666000175100017510000000471513207044712025347 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.network import base class SubnetpoolsClient(base.BaseNetworkClient): def list_subnetpools(self, **filters): """Lists subnet pools to which the tenant has access. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-subnet-pools """ uri = '/subnetpools' return self.list_resources(uri, **filters) def create_subnetpool(self, **kwargs): """Creates a subnet pool. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-subnet-pool """ uri = '/subnetpools' post_data = {'subnetpool': kwargs} return self.create_resource(uri, post_data) def show_subnetpool(self, subnetpool_id, **fields): """Shows information for a subnet pool. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-subnet-pool """ uri = '/subnetpools/%s' % subnetpool_id return self.show_resource(uri, **fields) def update_subnetpool(self, subnetpool_id, **kwargs): """Updates a subnet pool. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-subnet-pool """ uri = '/subnetpools/%s' % subnetpool_id post_data = {'subnetpool': kwargs} return self.update_resource(uri, post_data) def delete_subnetpool(self, subnetpool_id): uri = '/subnetpools/%s' % subnetpool_id return self.delete_resource(uri) tempest-17.2.0/tempest/lib/services/network/metering_label_rules_client.py0000666000175100017510000000260613207044712027152 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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) tempest-17.2.0/tempest/lib/services/network/service_providers_client.py0000666000175100017510000000152213207044712026520 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 ServiceProvidersClient(base.BaseNetworkClient): def list_service_providers(self, **filters): """Lists service providers.""" uri = '/service-providers' return self.list_resources(uri, **filters) tempest-17.2.0/tempest/lib/services/network/quotas_client.py0000666000175100017510000000257213207044712024305 0ustar zuulzuul00000000000000# 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): # noqa # NOTE: This noqa is for passing T111 check and we cannot rename # to keep backwards compatibility. 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) tempest-17.2.0/tempest/lib/services/network/security_group_rules_client.py0000666000175100017510000000425413207044712027265 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): """Creates an OpenStack Networking security group rule. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-security-group-rule """ 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): """Shows detailed information for a security group rule. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-security-group-rule """ 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): """Lists a summary of all OpenStack Networking security group rules. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-security-group-rules """ uri = '/security-group-rules' return self.list_resources(uri, **filters) tempest-17.2.0/tempest/lib/services/network/networks_client.py0000666000175100017510000000553613207044712024650 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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): """Creates a network. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-network """ uri = '/networks' post_data = {'network': kwargs} return self.create_resource(uri, post_data) def update_network(self, network_id, **kwargs): """Updates a network. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-network """ uri = '/networks/%s' % network_id post_data = {'network': kwargs} return self.update_resource(uri, post_data) def show_network(self, network_id, **fields): """Shows details for a network. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-network-details """ 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): """Lists networks to which the tenant has access. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-networks """ uri = '/networks' return self.list_resources(uri, **filters) def create_bulk_networks(self, **kwargs): """Create multiple networks in a single request. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#bulk-create-networks """ uri = '/networks' return self.create_resource(uri, kwargs) def list_dhcp_agents_on_hosting_network(self, network_id): uri = '/networks/%s/dhcp-agents' % network_id return self.list_resources(uri) tempest-17.2.0/tempest/lib/services/network/extensions_client.py0000666000175100017510000000164713207044712025172 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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) tempest-17.2.0/tempest/lib/services/network/security_groups_client.py0000666000175100017510000000574313207044712026242 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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.lib.services.network import base class SecurityGroupsClient(base.BaseNetworkClient): def create_security_group(self, **kwargs): """Creates an OpenStack Networking security group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#create-security-group """ uri = '/security-groups' post_data = {'security_group': kwargs} return self.create_resource(uri, post_data) def update_security_group(self, security_group_id, **kwargs): """Updates a security group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#update-security-group """ 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): """Shows details for a security group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#show-security-group """ uri = '/security-groups/%s' % security_group_id return self.show_resource(uri, **fields) def delete_security_group(self, security_group_id): """Deletes an OpenStack Networking security group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#delete-security-group """ uri = '/security-groups/%s' % security_group_id return self.delete_resource(uri) def list_security_groups(self, **filters): """Lists OpenStack Networking security groups. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/networking/v2/index.html#list-security-groups """ uri = '/security-groups' return self.list_resources(uri, **filters) def is_resource_deleted(self, id): try: self.show_security_group(id) except lib_exc.NotFound: return True return False tempest-17.2.0/tempest/lib/services/identity/0000775000175100017510000000000013207045130021204 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/identity/__init__.py0000666000175100017510000000133613207044712023327 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.identity import v2 from tempest.lib.services.identity import v3 __all__ = ['v2', 'v3'] tempest-17.2.0/tempest/lib/services/identity/v3/0000775000175100017510000000000013207045130021534 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/identity/v3/credentials_client.py0000666000175100017510000000665313207044712025762 0ustar zuulzuul00000000000000# 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. """ http://developer.openstack.org/api-ref/identity/v3/index.html#credentials """ from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class CredentialsClient(rest_client.RestClient): api_version = "v3" def create_credential(self, **kwargs): """Creates a credential. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-credential """ post_body = json.dumps({'credential': kwargs}) resp, body = self.post('credentials', post_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_credential(self, credential_id, **kwargs): """Updates a credential. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-credential """ post_body = json.dumps({'credential': kwargs}) resp, body = self.patch('credentials/%s' % credential_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_credential(self, credential_id): """To GET Details of a credential. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-credential-details """ resp, body = self.get('credentials/%s' % credential_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_credentials(self, **params): """Lists out all the available credentials. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-credentials """ url = 'credentials' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_credential(self, credential_id): """Deletes a credential. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#delete-credential """ resp, body = self.delete('credentials/%s' % credential_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/regions_client.py0000666000175100017510000000565713207044712025136 0ustar zuulzuul00000000000000# 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. """ https://developer.openstack.org/api-ref/identity/v3/index.html#regions """ from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class RegionsClient(rest_client.RestClient): api_version = "v3" def create_region(self, region_id=None, **kwargs): """Create region. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-region """ if region_id is not None: method = self.put url = 'regions/%s' % region_id else: method = self.post url = 'regions' req_body = json.dumps({'region': kwargs}) resp, body = method(url, req_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_region(self, region_id, **kwargs): """Updates a region. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-region """ post_body = json.dumps({'region': kwargs}) resp, body = self.patch('regions/%s' % region_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_region(self, region_id): """Get region.""" url = 'regions/%s' % region_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_regions(self, params=None): """List regions.""" url = 'regions' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_region(self, region_id): """Delete region.""" resp, body = self.delete('regions/%s' % region_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/endpoint_filter_client.py0000666000175100017510000000527613207044712026652 0ustar zuulzuul00000000000000# Copyright 2017 AT&T 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. """ https://developer.openstack.org/api-ref/identity/v3-ext/#os-ep-filter-api """ from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class EndPointsFilterClient(rest_client.RestClient): api_version = "v3" ep_filter = "OS-EP-FILTER" def list_projects_for_endpoint(self, endpoint_id): """List all projects that are associated with the endpoint.""" resp, body = self.get(self.ep_filter + '/endpoints/%s/projects' % endpoint_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def add_endpoint_to_project(self, project_id, endpoint_id): """Add association between project and endpoint. """ body = None resp, body = self.put( self.ep_filter + '/projects/%s/endpoints/%s' % (project_id, endpoint_id), body) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def check_endpoint_in_project(self, project_id, endpoint_id): """Check association of Project with Endpoint.""" resp, body = self.head( self.ep_filter + '/projects/%s/endpoints/%s' % (project_id, endpoint_id), None) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def list_endpoints_in_project(self, project_id): """List Endpoints associated with Project.""" resp, body = self.get(self.ep_filter + '/projects/%s/endpoints' % project_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_endpoint_from_project(self, project_id, endpoint_id): """Delete association between project and endpoint.""" resp, body = self.delete( self.ep_filter + '/projects/%s/endpoints/%s' % (project_id, endpoint_id)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/identity_client.py0000666000175100017510000000466613207044712025320 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class IdentityClient(rest_client.RestClient): api_version = "v3" def show_api_description(self): """Retrieves info about the v3 Identity API""" url = '' resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_token(self, resp_token): """Get token details.""" headers = {'X-Subject-Token': resp_token} resp, body = self.get("auth/tokens", headers=headers) self.expected_success(200, resp.status) body = json.loads(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 rest_client.ResponseBody(resp, body) def check_token_existence(self, resp_token): """Validates a token.""" headers = {'X-Subject-Token': resp_token} resp, body = self.head("auth/tokens", headers=headers) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_auth_projects(self): """Get available project scopes.""" resp, body = self.get("auth/projects") self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_auth_domains(self): """Get available domain scopes.""" resp, body = self.get("auth/domains") self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/oauth_token_client.py0000666000175100017510000002323213207044712025775 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import binascii import hashlib import hmac import random import time import six from six.moves.urllib import parse as urlparse from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class OAUTHTokenClient(rest_client.RestClient): api_version = "v3" def _escape(self, s): """Escape a unicode string in an OAuth-compatible fashion.""" safe = b'~' s = s.encode('utf-8') if isinstance(s, six.text_type) else s s = urlparse.quote(s, safe) if isinstance(s, six.binary_type): s = s.decode('utf-8') return s def _generate_params_with_signature(self, client_key, uri, client_secret=None, resource_owner_key=None, resource_owner_secret=None, callback_uri=None, verifier=None, http_method='GET'): """Generate OAUTH params along with signature.""" timestamp = six.text_type(int(time.time())) nonce = six.text_type(random.getrandbits(64)) + timestamp oauth_params = [ ('oauth_nonce', nonce), ('oauth_timestamp', timestamp), ('oauth_version', '1.0'), ('oauth_signature_method', 'HMAC-SHA1'), ('oauth_consumer_key', client_key), ] if resource_owner_key: oauth_params.append(('oauth_token', resource_owner_key)) if callback_uri: oauth_params.append(('oauth_callback', callback_uri)) if verifier: oauth_params.append(('oauth_verifier', verifier)) # normalize_params key_values = [(self._escape(k), self._escape(v)) for k, v in oauth_params] key_values.sort() parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values] normalized_params = '&'.join(parameter_parts) # normalize_uri scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) scheme = scheme.lower() netloc = netloc.lower() normalized_uri = urlparse.urlunparse((scheme, netloc, path, params, '', '')) # construct base string base_string = self._escape(http_method.upper()) base_string += '&' base_string += self._escape(normalized_uri) base_string += '&' base_string += self._escape(normalized_params) # sign using hmac-sha1 key = self._escape(client_secret or '') key += '&' key += self._escape(resource_owner_secret or '') key_utf8 = key.encode('utf-8') text_utf8 = base_string.encode('utf-8') signature = hmac.new(key_utf8, text_utf8, hashlib.sha1) sig = binascii.b2a_base64(signature.digest())[:-1].decode('utf-8') oauth_params.append(('oauth_signature', sig)) return oauth_params def _generate_oauth_header(self, oauth_params): authorization_header = {} authorization_header_parameters_parts = [] for oauth_parameter_name, value in oauth_params: escaped_name = self._escape(oauth_parameter_name) escaped_value = self._escape(value) part = '{0}="{1}"'.format(escaped_name, escaped_value) authorization_header_parameters_parts.append(part) authorization_header_parameters = ', '.join( authorization_header_parameters_parts) oauth_string = 'OAuth %s' % authorization_header_parameters authorization_header['Authorization'] = oauth_string return authorization_header def create_request_token(self, consumer_key, consumer_secret, project_id): """Create request token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#create-request-token """ endpoint = 'OS-OAUTH1/request_token' headers = {'Requested-Project-Id': project_id} oauth_params = self._generate_params_with_signature( consumer_key, self.base_url + '/' + endpoint, client_secret=consumer_secret, callback_uri='oob', http_method='POST') oauth_header = self._generate_oauth_header(oauth_params) headers.update(oauth_header) resp, body = self.post(endpoint, body=None, headers=headers) self.expected_success(201, resp.status) if not isinstance(body, str): body = body.decode('utf-8') body = dict(item.split("=") for item in body.split("&")) return rest_client.ResponseBody(resp, body) def authorize_request_token(self, request_token_id, role_ids): """Authorize request token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#authorize-request-token """ roles = [{'id': role_id} for role_id in role_ids] body = {'roles': roles} post_body = json.dumps(body) resp, body = self.put("OS-OAUTH1/authorize/%s" % request_token_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_access_token(self, consumer_key, consumer_secret, request_key, request_secret, oauth_verifier): """Create access token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#create-access-token """ endpoint = 'OS-OAUTH1/access_token' oauth_params = self._generate_params_with_signature( consumer_key, self.base_url + '/' + endpoint, client_secret=consumer_secret, resource_owner_key=request_key, resource_owner_secret=request_secret, verifier=oauth_verifier, http_method='POST') headers = self._generate_oauth_header(oauth_params) resp, body = self.post(endpoint, body=None, headers=headers) self.expected_success(201, resp.status) if not isinstance(body, str): body = body.decode('utf-8') body = dict(item.split("=") for item in body.split("&")) return rest_client.ResponseBody(resp, body) def get_access_token(self, user_id, access_token_id): """Get access token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#get-access-token """ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s" % (user_id, access_token_id)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def revoke_access_token(self, user_id, access_token_id): """Revoke access token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#revoke-access-token """ resp, body = self.delete("users/%s/OS-OAUTH1/access_tokens/%s" % (user_id, access_token_id)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def list_access_tokens(self, user_id): """List access tokens. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#list-access-tokens """ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens" % (user_id)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_access_token_roles(self, user_id, access_token_id): """List roles for an access token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#list-roles-for-an-access-token """ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles" % (user_id, access_token_id)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def get_access_token_role(self, user_id, access_token_id, role_id): """Show role details for an access token. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#show-role-details-for-an-access-token """ resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles/%s" % (user_id, access_token_id, role_id)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/trusts_client.py0000666000175100017510000000631613207044712025025 0ustar zuulzuul00000000000000# 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 TrustsClient(rest_client.RestClient): api_version = "v3" def create_trust(self, **kwargs): """Creates a trust. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/index.html#create-trust """ 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, **params): """Returns trusts For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/index.html#list-trusts """ url = "OS-TRUST/trusts/" 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_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) tempest-17.2.0/tempest/lib/services/identity/v3/services_client.py0000666000175100017510000000566513207044712025312 0ustar zuulzuul00000000000000# 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. """ https://developer.openstack.org/api-ref/identity/v3/index.html#service-catalog-and-endpoints """ from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class ServicesClient(rest_client.RestClient): api_version = "v3" def update_service(self, service_id, **kwargs): """Updates a service. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-service """ patch_body = json.dumps({'service': kwargs}) resp, body = self.patch('services/%s' % service_id, patch_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_service(self, service_id): """Get Service.""" url = 'services/%s' % service_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_service(self, **kwargs): """Creates a service. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-service """ body = json.dumps({'service': kwargs}) resp, body = self.post("services", body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_service(self, service_id): url = "services/" + service_id resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def list_services(self, **params): """List services. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-services """ url = 'services' 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) tempest-17.2.0/tempest/lib/services/identity/v3/oauth_consumers_client.py0000666000175100017510000000713313207044712026675 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class OAUTHConsumerClient(rest_client.RestClient): api_version = "v3" def create_consumer(self, description=None): """Creates a consumer. :param str description: Optional field to add notes about the consumer For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#create-consumer """ post_body = {"description": description} post_body = json.dumps({'consumer': post_body}) resp, body = self.post('OS-OAUTH1/consumers', post_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_consumer(self, consumer_id): """Deletes a consumer. :param str consumer_id: The ID of the consumer that will be deleted For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#delete-consumer """ resp, body = self.delete('OS-OAUTH1/consumers/%s' % consumer_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def update_consumer(self, consumer_id, description=None): """Updates a consumer. :param str consumer_id: The ID of the consumer that will be updated :param str description: Optional field to add notes about the consumer For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v3-ext/#update-consumer """ post_body = {"description": description} post_body = json.dumps({'consumer': post_body}) resp, body = self.patch('OS-OAUTH1/consumers/%s' % consumer_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_consumer(self, consumer_id): """Show consumer details. :param str consumer_id: The ID of the consumer that will be shown For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#show-consumer-details """ resp, body = self.get('OS-OAUTH1/consumers/%s' % consumer_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_consumers(self): """List all consumers. For more information, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3-ext/#list-consumers """ resp, body = self.get('OS-OAUTH1/consumers') self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/inherited_roles_client.py0000666000175100017510000001532113207044712026634 0ustar zuulzuul00000000000000# 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 InheritedRolesClient(rest_client.RestClient): api_version = "v3" def create_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 delete_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 create_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 delete_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 create_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 delete_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 create_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 delete_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) tempest-17.2.0/tempest/lib/services/identity/v3/__init__.py0000666000175100017510000000557213207044712023665 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.identity.v3.catalog_client import \ CatalogClient from tempest.lib.services.identity.v3.credentials_client import \ CredentialsClient from tempest.lib.services.identity.v3.domain_configuration_client \ import DomainConfigurationClient from tempest.lib.services.identity.v3.domains_client import DomainsClient from tempest.lib.services.identity.v3.endpoint_filter_client import \ EndPointsFilterClient from tempest.lib.services.identity.v3.endpoint_groups_client import \ EndPointGroupsClient from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient from tempest.lib.services.identity.v3.groups_client import GroupsClient from tempest.lib.services.identity.v3.identity_client import IdentityClient from tempest.lib.services.identity.v3.inherited_roles_client import \ InheritedRolesClient from tempest.lib.services.identity.v3.oauth_consumers_client import \ OAUTHConsumerClient from tempest.lib.services.identity.v3.oauth_token_client import \ OAUTHTokenClient from tempest.lib.services.identity.v3.policies_client import PoliciesClient from tempest.lib.services.identity.v3.projects_client import ProjectsClient from tempest.lib.services.identity.v3.regions_client import RegionsClient from tempest.lib.services.identity.v3.role_assignments_client import \ RoleAssignmentsClient from tempest.lib.services.identity.v3.roles_client import RolesClient from tempest.lib.services.identity.v3.services_client import ServicesClient from tempest.lib.services.identity.v3.token_client import V3TokenClient from tempest.lib.services.identity.v3.trusts_client import TrustsClient from tempest.lib.services.identity.v3.users_client import UsersClient from tempest.lib.services.identity.v3.versions_client import VersionsClient __all__ = ['CatalogClient', 'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient', 'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient', 'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient', 'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient', 'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient'] tempest-17.2.0/tempest/lib/services/identity/v3/roles_client.py0000666000175100017510000002463013207044712024604 0ustar zuulzuul00000000000000# 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 RolesClient(rest_client.RestClient): api_version = "v3" def create_role(self, **kwargs): """Create a Role. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-role """ 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' % role_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_roles(self, **params): """Get the list of Roles.""" url = 'roles' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_role(self, role_id, **kwargs): """Update a Role. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-role """ post_body = json.dumps({'role': kwargs}) resp, body = self.patch('roles/%s' % 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' % role_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def create_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 create_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 create_group_role_on_project(self, project_id, group_id, role_id): """Add roles to a group 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 create_group_role_on_domain(self, domain_id, group_id, role_id): """Add roles to a group 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 group 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 group 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 group 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 group 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 group 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 group 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 create_role_inference_rule(self, prior_role, implies_role): """Create a role inference rule.""" resp, body = self.put('roles/%s/implies/%s' % (prior_role, implies_role), None) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_role_inference_rule(self, prior_role, implies_role): """Get a role inference rule.""" resp, body = self.get('roles/%s/implies/%s' % (prior_role, implies_role)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_role_inferences_rules(self, prior_role): """List the inferences rules from a role.""" resp, body = self.get('roles/%s/implies' % prior_role) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_all_role_inference_rules(self): """Lists all role inference rules. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#list-all-role-inference-rules """ resp, body = self.get('role_inferences') self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def check_role_inference_rule(self, prior_role, implies_role): """Check a role inference rule.""" resp, body = self.head('roles/%s/implies/%s' % (prior_role, implies_role)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) def delete_role_inference_rule(self, prior_role, implies_role): """Delete a role inference rule.""" resp, body = self.delete('roles/%s/implies/%s' % (prior_role, implies_role)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/identity/v3/versions_client.py0000666000175100017510000000245113207044712025325 0ustar zuulzuul00000000000000# Copyright 2017 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 time from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class VersionsClient(rest_client.RestClient): api_version = "v3" def list_versions(self): """List API versions""" version_url = self._get_base_version_url() start = time.time() resp, body = self.raw_request(version_url, 'GET') end = time.time() self._log_request('GET', version_url, resp, secs=(end - start), resp_body=body) self._error_checker(resp, body) self.expected_success(300, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/catalog_client.py0000666000175100017510000000201313207044712025061 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ https://developer.openstack.org/api-ref/identity/v3/index.html#get-service-catalog """ from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class CatalogClient(rest_client.RestClient): api_version = "v3" def show_catalog(self): resp, body = self.get('auth/catalog') self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/token_client.py0000666000175100017510000001704313207044712024600 0ustar zuulzuul00000000000000# 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, **kwargs): """Initialises the Token client :param auth_url: URL to which the token request is sent :param disable_ssl_certificate_validation: pass-through to rest client :param ca_certs: pass-through to rest client :param trace_requests: pass-through to rest client :param kwargs: any extra parameter to pass through the rest client. Three kwargs are forbidden: region, service and auth_provider as they are not meaningful for token client """ dscv = disable_ssl_certificate_validation for unwanted_kwargs in ['region', 'service', 'auth_provider']: kwargs.pop(unwanted_kwargs, None) super(V3TokenClient, self).__init__( None, None, None, disable_ssl_certificate_validation=dscv, ca_certs=ca_certs, trace_requests=trace_requests, **kwargs) 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, chunked=False): """A simple HTTP request interface. Note: this overloads the `request` method from the parent class and thus must implement the same method signature. """ 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='', 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) tempest-17.2.0/tempest/lib/services/identity/v3/projects_client.py0000666000175100017510000000534413207044712025312 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class ProjectsClient(rest_client.RestClient): api_version = "v3" def create_project(self, name, **kwargs): """Create a Project. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-project """ # Include the project name to the kwargs parameters kwargs['name'] = name post_body = json.dumps({'project': kwargs}) resp, body = self.post('projects', post_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_projects(self, params=None): url = "projects" 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_project(self, project_id, **kwargs): """Update a Project. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-project """ post_body = json.dumps({'project': kwargs}) resp, body = self.patch('projects/%s' % project_id, post_body) self.expected_success(200, resp.status) body = json.loads(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 rest_client.ResponseBody(resp, body) def delete_project(self, project_id): """Delete a project.""" resp, body = self.delete('projects/%s' % project_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/groups_client.py0000666000175100017510000001034613207044712024776 0ustar zuulzuul00000000000000# 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. """ https://developer.openstack.org/api-ref/identity/v3/index.html#groups """ from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class GroupsClient(rest_client.RestClient): api_version = "v3" def create_group(self, **kwargs): """Creates a group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-group """ post_body = json.dumps({'group': kwargs}) resp, body = self.post('groups', post_body) self.expected_success(201, resp.status) body = json.loads(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 rest_client.ResponseBody(resp, body) def list_groups(self, **params): """Lists the groups. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-groups """ url = 'groups' 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_group(self, group_id, **kwargs): """Updates a group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-group """ post_body = json.dumps({'group': kwargs}) resp, body = self.patch('groups/%s' % group_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_group(self, group_id): """Delete a group.""" resp, body = self.delete('groups/%s' % group_id) self.expected_success(204, resp.status) 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 rest_client.ResponseBody(resp, body) def list_group_users(self, group_id, **params): """List users in group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-users-in-group """ url = 'groups/%s/users' % group_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_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 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 rest_client.ResponseBody(resp) tempest-17.2.0/tempest/lib/services/identity/v3/domains_client.py0000666000175100017510000000613513207044712025112 0ustar zuulzuul00000000000000# 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, **kwargs): """Creates a domain. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-domain """ post_body = json.dumps({'domain': kwargs}) 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. For APi details, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain """ resp, body = self.delete('domains/%s' % domain_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def list_domains(self, **params): """List Domains. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#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. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain """ post_body = json.dumps({'domain': kwargs}) 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. For API details, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-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) tempest-17.2.0/tempest/lib/services/identity/v3/policies_client.py0000666000175100017510000000523113207044712025263 0ustar zuulzuul00000000000000# 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. """ https://developer.openstack.org/api-ref/identity/v3/index.html#policies """ from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class PoliciesClient(rest_client.RestClient): api_version = "v3" def create_policy(self, **kwargs): """Creates a Policy. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-policy """ post_body = json.dumps({'policy': kwargs}) resp, body = self.post('policies', post_body) self.expected_success(201, resp.status) body = json.loads(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 rest_client.ResponseBody(resp, body) def show_policy(self, policy_id): """Lists out the given policy.""" url = 'policies/%s' % policy_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_policy(self, policy_id, **kwargs): """Updates a policy. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-policy """ post_body = json.dumps({'policy': kwargs}) url = 'policies/%s' % policy_id resp, body = self.patch(url, post_body) self.expected_success(200, resp.status) body = json.loads(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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/users_client.py0000666000175100017510000001065613207044712024624 0ustar zuulzuul00000000000000# 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, **kwargs): """Creates a user. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#create-user """ post_body = json.dumps({'user': kwargs}) 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, **kwargs): """Updates a user. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#update-user """ if 'id' not in kwargs: kwargs['id'] = user_id post_body = json.dumps({'user': kwargs}) 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 For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#change-password-for-user """ 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, **params): """Lists the projects on which a user has roles assigned. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-projects-for-user """ url = 'users/%s/projects' % user_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_users(self, **params): """Get the list of users. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-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, **params): """Lists groups which a user belongs to. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-groups-to-which-a-user-belongs """ url = 'users/%s/groups' % user_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/role_assignments_client.py0000666000175100017510000000341613207044712027033 0ustar zuulzuul00000000000000# 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 RoleAssignmentsClient(rest_client.RestClient): api_version = "v3" def list_role_assignments(self, effective=False, **kwargs): """List role assignments. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-role-assignments :param effective: If True, returns the effective assignments, including any assignments gained by virtue of group membership or inherited roles. """ url = 'role_assignments' if kwargs: # NOTE(rodrigods): "effective" is a key-only query parameter and # is treated below. if 'effective' in kwargs: del kwargs['effective'] url += '?%s' % urllib.urlencode(kwargs) if effective: url += '&effective' resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v3/endpoint_groups_client.py0000666000175100017510000000630613207044712026677 0ustar zuulzuul00000000000000# Copyright 2017 AT&T Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class EndPointGroupsClient(rest_client.RestClient): api_version = "v3" def create_endpoint_group(self, **kwargs): """Create endpoint group. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v3-ext/#create-endpoint-group """ post_body = json.dumps({'endpoint_group': kwargs}) resp, body = self.post('OS-EP-FILTER/endpoint_groups', post_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_endpoint_group(self, endpoint_group_id, **kwargs): """Update endpoint group. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v3-ext/#update-endpoint-group """ post_body = json.dumps({'endpoint_group': kwargs}) resp, body = self.patch( 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_endpoint_group(self, endpoint_group_id): """Delete endpoint group.""" resp_header, resp_body = self.delete( 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id) self.expected_success(204, resp_header.status) return rest_client.ResponseBody(resp_header, resp_body) def show_endpoint_group(self, endpoint_group_id): """Get endpoint group.""" resp_header, resp_body = self.get( 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id) self.expected_success(200, resp_header.status) resp_body = json.loads(resp_body) return rest_client.ResponseBody(resp_header, resp_body) def check_endpoint_group(self, endpoint_group_id): """Check endpoint group.""" resp_header, resp_body = self.head( 'OS-EP-FILTER/endpoint_groups/%s' % endpoint_group_id) self.expected_success(200, resp_header.status) return rest_client.ResponseBody(resp_header, resp_body) def list_endpoint_groups(self): """Get endpoint groups.""" resp_header, resp_body = self.get('OS-EP-FILTER/endpoint_groups') self.expected_success(200, resp_header.status) resp_body = json.loads(resp_body) return rest_client.ResponseBody(resp_header, resp_body) tempest-17.2.0/tempest/lib/services/identity/v3/endpoints_client.py0000666000175100017510000000606013207044712025460 0ustar zuulzuul00000000000000# 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. """ https://developer.openstack.org/api-ref/identity/v3/index.html#service-catalog-and-endpoints """ from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class EndPointsClient(rest_client.RestClient): api_version = "v3" def list_endpoints(self, **params): """List endpoints. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/#list-endpoints """ url = 'endpoints' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_endpoint(self, **kwargs): """Create endpoint. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-endpoint """ post_body = json.dumps({'endpoint': kwargs}) resp, body = self.post('endpoints', post_body) self.expected_success(201, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_endpoint(self, endpoint_id, **kwargs): """Updates an endpoint with given parameters. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-endpoint """ post_body = json.dumps({'endpoint': kwargs}) resp, body = self.patch('endpoints/%s' % endpoint_id, post_body) self.expected_success(200, resp.status) body = json.loads(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 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 rest_client.ResponseBody(resp_header, resp_body) tempest-17.2.0/tempest/lib/services/identity/v3/domain_configuration_client.py0000666000175100017510000001743513207044712027663 0ustar zuulzuul00000000000000# Copyright 2017 AT&T 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 tempest.lib.common import rest_client class DomainConfigurationClient(rest_client.RestClient): api_version = "v3" def show_default_config_settings(self): """Show default configuration settings. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-default-configuration-settings """ url = 'domains/config/default' resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_default_group_config(self, group): """Show default configuration for a group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-default-configuration-for-a-group """ url = 'domains/config/%s/default' % group resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_default_group_option(self, group, option): """Show default option for a group. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-default-option-for-a-group """ url = 'domains/config/%s/%s/default' % (group, option) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_domain_group_option_config(self, domain_id, group, option): """Show domain group option configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-group-option-configuration """ url = 'domains/%s/config/%s/%s' % (domain_id, group, option) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_domain_group_option_config(self, domain_id, group, option, **kwargs): """Update domain group option configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain-group-option-configuration """ url = 'domains/%s/config/%s/%s' % (domain_id, group, option) resp, body = self.patch(url, json.dumps({'config': kwargs})) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_domain_group_option_config(self, domain_id, group, option): """Delete domain group option configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain-group-option-configuration """ url = 'domains/%s/config/%s/%s' % (domain_id, group, option) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_domain_group_config(self, domain_id, group): """Shows details for a domain group configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-group-configuration """ url = 'domains/%s/config/%s' % (domain_id, group) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_domain_group_config(self, domain_id, group, **kwargs): """Update domain group configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain-group-configuration """ url = 'domains/%s/config/%s' % (domain_id, group) resp, body = self.patch(url, json.dumps({'config': kwargs})) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_domain_group_config(self, domain_id, group): """Delete domain group configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain-group-configuration """ url = 'domains/%s/config/%s' % (domain_id, group) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def create_domain_config(self, domain_id, **kwargs): """Create domain configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#create-domain-configuration """ url = 'domains/%s/config' % domain_id resp, body = self.put(url, json.dumps({'config': kwargs})) self.expected_success([200, 201], resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_domain_config(self, domain_id): """Show domain configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-configuration """ url = 'domains/%s/config' % domain_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_domain_config(self, domain_id, **kwargs): """Update domain configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain-configuration """ url = 'domains/%s/config' % domain_id resp, body = self.patch(url, json.dumps({'config': kwargs})) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_domain_config(self, domain_id): """Delete domain configuration. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain-configuration """ url = 'domains/%s/config' % domain_id resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v2/0000775000175100017510000000000013207045130021533 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/identity/v2/identity_client.py0000666000175100017510000000514113207044712025304 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 IdentityClient(rest_client.RestClient): api_version = "v2.0" def show_api_description(self): """Retrieves info about the v2.0 Identity API""" url = '' resp, body = self.get(url) self.expected_success([200, 203], resp.status) body = json.loads(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 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 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 rest_client.ResponseBody(resp, body) def list_endpoints_for_token(self, token_id): """List endpoints for a token """ resp, body = self.get("tokens/%s/endpoints" % token_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def check_token_existence(self, token_id, **params): """Validates a token and confirms that it belongs to a tenant. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-admin/#validate-token """ url = "tokens/%s" % token_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.head(url) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v2/services_client.py0000666000175100017510000000460413207044712025301 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class ServicesClient(rest_client.RestClient): api_version = "v2.0" def create_service(self, **kwargs): """Create a service. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-ext/#create-service-admin-extension """ post_body = json.dumps({'OS-KSADM:service': kwargs}) resp, body = self.post('/OS-KSADM/services', post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_service(self, service_id): """Get Service.""" url = '/OS-KSADM/services/%s' % service_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_services(self, **params): """List Service - Returns Services. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-ext/#list-services-admin-extension """ url = '/OS-KSADM/services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v2/__init__.py0000666000175100017510000000237213207044712023657 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient from tempest.lib.services.identity.v2.identity_client import IdentityClient from tempest.lib.services.identity.v2.roles_client import RolesClient from tempest.lib.services.identity.v2.services_client import ServicesClient from tempest.lib.services.identity.v2.tenants_client import TenantsClient from tempest.lib.services.identity.v2.token_client import TokenClient from tempest.lib.services.identity.v2.users_client import UsersClient __all__ = ['EndpointsClient', 'IdentityClient', 'RolesClient', 'ServicesClient', 'TenantsClient', 'TokenClient', 'UsersClient'] tempest-17.2.0/tempest/lib/services/identity/v2/roles_client.py0000666000175100017510000001073713207044712024606 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class RolesClient(rest_client.RestClient): api_version = "v2.0" def create_role(self, **kwargs): """Create a role. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#create-a-role """ post_body = json.dumps({'role': kwargs}) resp, body = self.post('OS-KSADM/roles', post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_role(self, role_id_or_name): """Get a role by its id or name. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#show-a-role OR https://developer.openstack.org/api-ref/identity/v2-ext/index.html#show-role-information-by-name """ resp, body = self.get('OS-KSADM/roles/%s' % role_id_or_name) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_roles(self, **params): """Returns roles. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#list-all-roles """ url = 'OS-KSADM/roles' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_role(self, role_id): """Delete a role. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#delete-a-role """ resp, body = self.delete('OS-KSADM/roles/%s' % role_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def create_user_role_on_project(self, tenant_id, user_id, role_id): """Add roles to a user on a tenant. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#grant-roles-to-user-on-tenant """ resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id), "") self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_user_roles_on_project(self, tenant_id, user_id, **params): """Returns a list of roles assigned to a user for a tenant.""" # TODO(gmann): Need to write API-ref link, Bug# 1592711 url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id) if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_role_from_user_on_project(self, tenant_id, user_id, role_id): """Removes a role assignment for a user on a tenant. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#revoke-role-from-user-on-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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v2/tenants_client.py0000666000175100017510000000755513207044712025142 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class TenantsClient(rest_client.RestClient): api_version = "v2.0" def create_tenant(self, **kwargs): """Create a tenant For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#create-tenant """ post_body = json.dumps({'tenant': kwargs}) resp, body = self.post('tenants', post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_tenant(self, tenant_id): """Delete a tenant. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-admin/index.html#delete-tenant """ resp, body = self.delete('tenants/%s' % str(tenant_id)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def show_tenant(self, tenant_id): """Get tenant details. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-admin/index.html#show-tenant-details-by-id """ resp, body = self.get('tenants/%s' % str(tenant_id)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_tenants(self, **params): """Returns tenants. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#list-tenants-admin-endpoint """ url = 'tenants' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_tenant(self, tenant_id, **kwargs): """Updates a tenant. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#update-tenant """ if 'id' not in kwargs: kwargs['id'] = tenant_id post_body = json.dumps({'tenant': kwargs}) resp, body = self.post('tenants/%s' % tenant_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def list_tenant_users(self, tenant_id, **params): """List users for a Tenant. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#list-users-on-a-tenant """ url = '/tenants/%s/users' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v2/token_client.py0000666000175100017510000001216313207044712024575 0ustar zuulzuul00000000000000# 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, **kwargs): """Initialises the Token client :param auth_url: URL to which the token request is sent :param disable_ssl_certificate_validation: pass-through to rest client :param ca_certs: pass-through to rest client :param trace_requests: pass-through to rest client :param kwargs: any extra parameter to pass through the rest client. region, service and auth_provider will be ignored, if passed, as they are not meaningful for token client """ dscv = disable_ssl_certificate_validation # NOTE(andreaf) region, service and auth_provider are passed # positionally with None. Having them in kwargs would raise a # "multiple values for keyword arguments" error for unwanted_kwargs in ['region', 'service', 'auth_provider']: kwargs.pop(unwanted_kwargs, None) super(TokenClient, self).__init__( None, None, None, disable_ssl_certificate_validation=dscv, ca_certs=ca_certs, trace_requests=trace_requests, **kwargs) 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, chunked=False): """A simple HTTP request interface. Note: this overloads the `request` method from the parent class and thus must implement the same method signature. """ 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='', 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) tempest-17.2.0/tempest/lib/services/identity/v2/users_client.py0000666000175100017510000001534413207044712024622 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class UsersClient(rest_client.RestClient): api_version = "v2.0" def create_user(self, **kwargs): """Create a user. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#create-user-admin-endpoint """ post_body = json.dumps({'user': kwargs}) resp, body = self.post('users', post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_user(self, user_id, **kwargs): """Updates a user. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#update-user-admin-endpoint """ put_body = json.dumps({'user': kwargs}) resp, body = self.put('users/%s' % user_id, put_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_user(self, user_id): """GET a user. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-admin/index.html#show-user-details-admin-endpoint """ resp, body = self.get("users/%s" % user_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_user(self, user_id): """Delete a user. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-admin/index.html#delete-user-admin-endpoint """ resp, body = self.delete("users/%s" % user_id) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def list_users(self, **params): """Get the list of users. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/identity/v2-admin/index.html#list-users-admin-endpoint """ url = "users" if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_user_enabled(self, user_id, **kwargs): """Enables or disables a user. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-ext/index.html#enable-disable-user """ # NOTE: The URL (users//enabled) is different from the api-site # one (users//OS-KSADM/enabled) , but they are the same API # because of the fact that in keystone/contrib/admin_crud/core.py # both api use same action='set_user_enabled' put_body = json.dumps({'user': kwargs}) resp, body = self.put('users/%s/enabled' % user_id, put_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_user_password(self, user_id, **kwargs): """Update User Password.""" # TODO(piyush): Current api-site doesn't contain this API description. # After fixing the api-site, we need to fix here also for putting the # link to api-site. # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524147 put_body = json.dumps({'user': kwargs}) resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_user_own_password(self, user_id, **kwargs): """User updates own password""" # TODO(piyush): Current api-site doesn't contain this API description. # After fixing the api-site, we need to fix here also for putting the # link to api-site. # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1524153 # NOTE: This API is used for updating user password by itself. # Ref: http://lists.openstack.org/pipermail/openstack-dev/2015-December # /081803.html patch_body = json.dumps({'user': kwargs}) resp, body = self.patch('OS-KSCRUD/users/%s' % user_id, patch_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def create_user_ec2_credential(self, user_id, **kwargs): # TODO(piyush): Current api-site doesn't contain this API description. # After fixing the api-site, we need to fix here also for putting the # link to api-site. post_body = json.dumps(kwargs) resp, body = self.post('/users/%s/credentials/OS-EC2' % user_id, post_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def delete_user_ec2_credential(self, user_id, access): resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' % (user_id, access)) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def list_user_ec2_credentials(self, user_id): resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def show_user_ec2_credential(self, user_id, access): resp, body = self.get('/users/%s/credentials/OS-EC2/%s' % (user_id, access)) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/identity/v2/endpoints_client.py0000666000175100017510000000341413207044712025457 0ustar zuulzuul00000000000000# 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 EndpointsClient(rest_client.RestClient): api_version = "v2.0" def create_endpoint(self, **kwargs): """Create an endpoint for service. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/identity/v2-admin/index.html#create-endpoint-template """ post_body = json.dumps({'endpoint': kwargs}) resp, body = self.post('/endpoints', post_body) self.expected_success(200, resp.status) body = json.loads(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 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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/__init__.py0000666000175100017510000000000013207044712021461 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/volume/0000775000175100017510000000000013207045130020662 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/volume/v1/0000775000175100017510000000000013207045130021210 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/volume/v1/availability_zone_client.py0000666000175100017510000000203513207044712026634 0ustar zuulzuul00000000000000# 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.common import rest_client class AvailabilityZoneClient(rest_client.RestClient): """Volume V1 availability zone client.""" def list_availability_zones(self): resp, body = self.get('os-availability-zone') body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/backups_client.py0000666000175100017510000001003113207044712024552 0ustar zuulzuul00000000000000# Copyright 2014 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 from tempest.lib import exceptions as lib_exc class BackupsClient(rest_client.RestClient): """Volume V1 Backups client""" api_version = "v1" def create_backup(self, **kwargs): """Creates a backup of volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-backup """ post_body = json.dumps({'backup': kwargs}) resp, body = self.post('backups', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def restore_backup(self, backup_id, **kwargs): """Restore volume from backup. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#restore-backup """ post_body = json.dumps({'restore': kwargs}) resp, body = self.post('backups/%s/restore' % (backup_id), post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def delete_backup(self, backup_id): """Delete a backup of volume.""" resp, body = self.delete('backups/%s' % backup_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_backup(self, backup_id): """Returns the details of a single backup.""" url = "backups/%s" % backup_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_backups(self, detail=False): """Information for all the tenant's backups.""" url = "backups" if detail: url += "/detail" resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def export_backup(self, backup_id): """Export backup metadata record.""" url = "backups/%s/export_record" % backup_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def import_backup(self, **kwargs): """Import backup metadata record.""" post_body = json.dumps({'backup-record': kwargs}) resp, body = self.post("backups/import_record", post_body) body = json.loads(body) self.expected_success(201, resp.status) return rest_client.ResponseBody(resp, body) def reset_backup_status(self, backup_id, status): """Reset the specified backup's status.""" post_body = json.dumps({'os-reset_status': {"status": status}}) resp, body = self.post('backups/%s/action' % backup_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): try: self.show_backup(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 'backup' tempest-17.2.0/tempest/lib/services/volume/v1/qos_client.py0000666000175100017510000001175113207044712023736 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc class QosSpecsClient(rest_client.RestClient): """Volume V1 QoS client. Client class to send CRUD QoS API requests """ api_version = "v1" def is_resource_deleted(self, qos_id): try: self.show_qos(qos_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 'qos' def create_qos(self, **kwargs): """Create a QoS Specification. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-qos-specification """ post_body = json.dumps({'qos_specs': kwargs}) resp, body = self.post('qos-specs', post_body) self.expected_success(200, resp.status) body = json.loads(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" % (qos_id, force)) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_qos(self): """List all the QoS specifications created.""" url = 'qos-specs' resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_qos(self, qos_id): """Get the specified QoS specification.""" url = "qos-specs/%s" % qos_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def set_qos_key(self, qos_id, **kwargs): """Set the specified keys/values of QoS specification. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#set-keys-in-qos-specification """ put_body = json.dumps({"qos_specs": kwargs}) resp, body = self.put('qos-specs/%s' % qos_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def unset_qos_key(self, qos_id, keys): """Unset the specified keys of QoS specification. :param keys: keys to delete from the QoS specification. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#unset-keys-in-qos-specification """ 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 rest_client.ResponseBody(resp, body) def associate_qos(self, qos_id, vol_type_id): """Associate the specified QoS with specified volume-type.""" url = "qos-specs/%s/associate" % qos_id url += "?vol_type_id=%s" % vol_type_id resp, body = self.get(url) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_association_qos(self, qos_id): """Get the association of the specified QoS specification.""" url = "qos-specs/%s/associations" % qos_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def disassociate_qos(self, qos_id, vol_type_id): """Disassociate the specified QoS with specified volume-type.""" url = "qos-specs/%s/disassociate" % qos_id url += "?vol_type_id=%s" % vol_type_id resp, body = self.get(url) self.expected_success(202, resp.status) 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" % qos_id resp, body = self.get(url) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/snapshots_client.py0000666000175100017510000001675113207044712025163 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from tempest.lib import exceptions as lib_exc class SnapshotsClient(rest_client.RestClient): """Client class to send CRUD Volume V1 API requests.""" create_resp = 200 def list_snapshots(self, detail=False, **params): """List all the snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots https://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details """ url = 'snapshots' if detail: url += '/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_snapshot(self, snapshot_id): """Returns the details of a single snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details """ url = "snapshots/%s" % snapshot_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_snapshot(self, **kwargs): """Creates a new snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot """ post_body = json.dumps({'snapshot': kwargs}) resp, body = self.post('snapshots', post_body) body = json.loads(body) self.expected_success(self.create_resp, resp.status) return rest_client.ResponseBody(resp, body) def delete_snapshot(self, snapshot_id): """Delete Snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot """ resp, body = self.delete("snapshots/%s" % snapshot_id) self.expected_success(202, resp.status) 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): """Returns the primary type of resource this client works with.""" return 'volume-snapshot' def reset_snapshot_status(self, snapshot_id, status): """Reset the specified snapshot's status.""" 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 rest_client.ResponseBody(resp, body) def update_snapshot_status(self, snapshot_id, **kwargs): """Update the specified snapshot's status.""" # TODO(gmann): api-site doesn't contain doc ref # for this API. After fixing the api-site, we need to # add the link here. # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645 post_body = json.dumps({'os-update_snapshot_status': kwargs}) url = 'snapshots/%s/action' % snapshot_id resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def create_snapshot_metadata(self, snapshot_id, metadata): """Create metadata for the snapshot.""" put_body = json.dumps({'metadata': metadata}) url = "snapshots/%s/metadata" % snapshot_id resp, body = self.post(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_snapshot(self, snapshot_id, **kwargs): """Updates a snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot """ put_body = json.dumps({'snapshot': kwargs}) resp, body = self.put('snapshots/%s' % snapshot_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_snapshot_metadata(self, snapshot_id): """Get metadata of the snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata """ url = "snapshots/%s/metadata" % snapshot_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_snapshot_metadata(self, snapshot_id, **kwargs): """Update metadata for the snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata """ put_body = json.dumps(kwargs) url = "snapshots/%s/metadata" % snapshot_id resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs): """Update metadata item for the snapshot.""" # TODO(piyush): Current api-site doesn't contain this API description. # 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/1529064 put_body = json.dumps(kwargs) url = "snapshots/%s/metadata/%s" % (snapshot_id, id) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) 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" % (snapshot_id, id) resp, body = self.delete(url) self.expected_success(200, resp.status) 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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/services_client.py0000666000175100017510000000221713207044712024754 0ustar zuulzuul00000000000000# 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.common import rest_client class ServicesClient(rest_client.RestClient): """Volume V1 volume services client""" 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.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/volumes_client.py0000666000175100017510000003012113207044712024616 0ustar zuulzuul00000000000000# 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 import six from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc class VolumesClient(rest_client.RestClient): """Client class to send CRUD Volume V1 API requests""" def _prepare_params(self, params): """Prepares params for use in get or _ext_get methods. If params is a string it will be left as it is, but if it's not it will be urlencoded. """ if isinstance(params, six.string_types): return params return urllib.urlencode(params) def list_volumes(self, detail=False, params=None): """List all the volumes created. Params can be a string (must be urlencoded) or a dictionary. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-volumes https://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details """ url = 'volumes' if detail: url += '/detail' if params: url += '?%s' % self._prepare_params(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % volume_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_volume(self, **kwargs): """Creates a new Volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-volume """ post_body = json.dumps({'volume': kwargs}) resp, body = self.post('volumes', post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume(self, volume_id, **kwargs): """Updates the Specified Volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-volume """ put_body = json.dumps({'volume': kwargs}) resp, body = self.put('volumes/%s' % volume_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume(self, volume_id): """Deletes the Specified Volume.""" resp, body = self.delete("volumes/%s" % volume_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def upload_volume(self, volume_id, **kwargs): """Uploads a volume in Glance.""" post_body = json.dumps({'os-volume_upload_image': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def attach_volume(self, volume_id, **kwargs): """Attaches a volume to a given instance on a given mountpoint. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server """ post_body = json.dumps({'os-attach': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def set_bootable_volume(self, volume_id, **kwargs): """set a bootable flag for a volume - true or false.""" post_body = json.dumps({'os-set_bootable': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = json.dumps({'os-detach': {}}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def reserve_volume(self, volume_id): """Reserves a volume.""" post_body = json.dumps({'os-reserve': {}}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def unreserve_volume(self, volume_id): """Restore a reserved volume .""" post_body = json.dumps({'os-unreserve': {}}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) 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): """Returns the primary type of resource this client works with.""" return 'volume' def extend_volume(self, volume_id, **kwargs): """Extend a volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size """ post_body = json.dumps({'os-extend': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def reset_volume_status(self, volume_id, **kwargs): """Reset the Specified Volume's Status. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses """ 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 rest_client.ResponseBody(resp, body) def create_volume_transfer(self, **kwargs): """Create a volume transfer. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer """ post_body = json.dumps({'transfer': kwargs}) resp, body = self.post('os-volume-transfer', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_transfer(self, transfer_id): """Returns the details of a volume transfer.""" url = "os-volume-transfer/%s" % transfer_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_volume_transfers(self, **params): """List all the volume transfers created. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers """ url = 'os-volume-transfer' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) 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" % transfer_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def accept_volume_transfer(self, transfer_id, **kwargs): """Accept a volume transfer. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer """ url = 'os-volume-transfer/%s/accept' % transfer_id post_body = json.dumps({'accept': kwargs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_readonly(self, volume_id, **kwargs): """Update the Specified Volume readonly.""" post_body = json.dumps({'os-update_readonly_flag': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) 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 rest_client.ResponseBody(resp, body) def create_volume_metadata(self, volume_id, metadata): """Create metadata for the volume.""" put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % volume_id resp, body = self.post(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_metadata(self, volume_id): """Get metadata of the volume.""" url = "volumes/%s/metadata" % volume_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_metadata(self, volume_id, metadata): """Update metadata for the volume.""" put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % volume_id resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_metadata_item(self, volume_id, id, meta_item): """Update metadata item for the volume.""" put_body = json.dumps({'meta': meta_item}) url = "volumes/%s/metadata/%s" % (volume_id, id) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) 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" % (volume_id, id) resp, body = self.delete(url) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def retype_volume(self, volume_id, **kwargs): """Updates volume with new volume type.""" post_body = json.dumps({'os-retype': kwargs}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) self.expected_success(202, resp.status) tempest-17.2.0/tempest/lib/services/volume/v1/__init__.py0000666000175100017510000000340413207044712023331 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.volume.v1.availability_zone_client \ import AvailabilityZoneClient from tempest.lib.services.volume.v1.backups_client import BackupsClient from tempest.lib.services.volume.v1.encryption_types_client import \ EncryptionTypesClient from tempest.lib.services.volume.v1.extensions_client import ExtensionsClient from tempest.lib.services.volume.v1.hosts_client import HostsClient from tempest.lib.services.volume.v1.limits_client import LimitsClient from tempest.lib.services.volume.v1.qos_client import QosSpecsClient from tempest.lib.services.volume.v1.quotas_client import QuotasClient from tempest.lib.services.volume.v1.services_client import ServicesClient from tempest.lib.services.volume.v1.snapshots_client import SnapshotsClient from tempest.lib.services.volume.v1.types_client import TypesClient from tempest.lib.services.volume.v1.volumes_client import VolumesClient __all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient', 'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient', 'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient', 'LimitsClient'] tempest-17.2.0/tempest/lib/services/volume/v1/encryption_types_client.py0000666000175100017510000000475613207044712026561 0ustar zuulzuul00000000000000# 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 from tempest.lib import exceptions as lib_exc class EncryptionTypesClient(rest_client.RestClient): def is_resource_deleted(self, id): try: body = self.show_encryption_type(id) if not body: return True except lib_exc.NotFound: return True return False @property def resource_type(self): """Returns the primary type of resource this client works with.""" return 'encryption-type' def show_encryption_type(self, volume_type_id): """Get the volume encryption type for the specified volume type. volume_type_id: Id of volume_type. """ url = "/types/%s/encryption" % volume_type_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_encryption_type(self, volume_type_id, **kwargs): """Create encryption type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2 """ url = "/types/%s/encryption" % volume_type_id post_body = json.dumps({'encryption': kwargs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_encryption_type(self, volume_type_id): """Delete the encryption type for the specified volume-type.""" resp, body = self.delete( "/types/%s/encryption/provider" % volume_type_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/limits_client.py0000666000175100017510000000212413207044712024427 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class LimitsClient(rest_client.RestClient): """Volume V1 limits client.""" api_version = "v1" def show_limits(self): """Returns the details of a volume absolute limits.""" url = "limits" resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/hosts_client.py0000666000175100017510000000257713207044712024302 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class HostsClient(rest_client.RestClient): """Client class to send CRUD Volume Host API V1 requests""" def list_hosts(self, **params): """Lists all hosts. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts """ url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/types_client.py0000666000175100017510000001504313207044712024276 0ustar zuulzuul00000000000000# 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.common import rest_client from tempest.lib import exceptions as lib_exc class TypesClient(rest_client.RestClient): """Client class to send CRUD Volume Types API requests""" def is_resource_deleted(self, id): try: self.show_volume_type(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 'volume-type' def list_volume_types(self, **params): """List all the volume_types created. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2 """ url = 'types' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_type(self, volume_type_id): """Returns the details of a single volume_type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2 """ url = "types/%s" % volume_type_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_volume_type(self, **kwargs): """Create volume type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2 """ post_body = json.dumps({'volume_type': kwargs}) resp, body = self.post('types', post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume_type(self, volume_type_id): """Deletes the Specified Volume_type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type """ resp, body = self.delete("types/%s" % volume_type_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_volume_types_extra_specs(self, volume_type_id, **params): """List all the volume_types extra specs created. TODO: 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. """ url = 'types/%s/extra_specs' % volume_type_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name): """Returns the details of a single volume_type extra spec.""" url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_volume_type_extra_specs(self, volume_type_id, extra_specs): """Creates a new Volume_type extra spec. volume_type_id: Id of volume_type. extra_specs: A dictionary of values to be used as extra_specs. """ url = "types/%s/extra_specs" % volume_type_id post_body = json.dumps({'extra_specs': extra_specs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name): """Deletes the Specified Volume_type extra spec.""" resp, body = self.delete("types/%s/extra_specs/%s" % ( volume_type_id, extra_spec_name)) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_type(self, volume_type_id, **kwargs): """Updates volume type name, description, and/or is_public. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type """ put_body = json.dumps({'volume_type': kwargs}) resp, body = self.put('types/%s' % volume_type_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name, extra_specs): """Update a volume_type extra spec. volume_type_id: Id of volume_type. extra_spec_name: Name of the extra spec to be updated. extra_spec: A dictionary of with key as extra_spec_name and the updated value. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type """ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name) put_body = json.dumps(extra_specs) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/quotas_client.py0000666000175100017510000000454513207044712024453 0ustar zuulzuul00000000000000# Copyright (C) 2014 eNovance SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class QuotasClient(rest_client.RestClient): """Client class to send CRUD Volume Quotas API V1 requests""" def show_default_quota_set(self, tenant_id): """List the default volume quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % tenant_id resp, body = self.get(url) self.expected_success(200, resp.status) body = jsonutils.loads(body) return rest_client.ResponseBody(resp, body) def show_quota_set(self, tenant_id, params=None): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = jsonutils.loads(body) return rest_client.ResponseBody(resp, body) def update_quota_set(self, tenant_id, **kwargs): """Updates quota set For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-quotas """ put_body = jsonutils.dumps({'quota_set': kwargs}) resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body) self.expected_success(200, resp.status) body = jsonutils.loads(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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v1/extensions_client.py0000666000175100017510000000202413207044712025324 0ustar zuulzuul00000000000000# 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 ExtensionsClient(rest_client.RestClient): """Volume V1 extensions client.""" def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/base_client.py0000666000175100017510000000352113207044712023514 0ustar zuulzuul00000000000000# Copyright 2016 Andrew Kerr # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.common import api_version_utils from tempest.lib.common import rest_client VOLUME_MICROVERSION = None class BaseClient(rest_client.RestClient): """Base volume service clients class to support microversion.""" api_microversion_header_name = 'Openstack-Api-Version' def get_headers(self, accept_type=None, send_type=None): headers = super(BaseClient, self).get_headers( accept_type=accept_type, send_type=send_type) if VOLUME_MICROVERSION: headers[self.api_microversion_header_name] = ('volume %s' % VOLUME_MICROVERSION) return headers def request(self, method, url, extra_headers=False, headers=None, body=None, chunked=False): resp, resp_body = super(BaseClient, self).request( method, url, extra_headers, headers, body, chunked) if (VOLUME_MICROVERSION and VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION): api_version_utils.assert_version_header_matches_request( self.api_microversion_header_name, 'volume %s' % VOLUME_MICROVERSION, resp) return resp, resp_body tempest-17.2.0/tempest/lib/services/volume/__init__.py0000666000175100017510000000141313207044712023001 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.volume import v1 from tempest.lib.services.volume import v2 from tempest.lib.services.volume import v3 __all__ = ['v1', 'v2', 'v3'] tempest-17.2.0/tempest/lib/services/volume/v3/0000775000175100017510000000000013207045130021212 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/volume/v3/backups_client.py0000666000175100017510000000267513207044712024573 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client from tempest.lib.services.volume.v2 import backups_client class BackupsClient(backups_client.BackupsClient): """Volume V3 Backups client""" api_version = "v3" def update_backup(self, backup_id, **kwargs): """Updates the specified volume backup. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#update-a-backup """ put_body = json.dumps({'backup': kwargs}) resp, body = self.put('backups/%s' % backup_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v3/messages_client.py0000666000175100017510000000404113207044712024737 0ustar zuulzuul00000000000000# Copyright 2016 Andrew Kerr # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc from tempest.lib.services.volume import base_client class MessagesClient(base_client.BaseClient): """Client class to send user messages API requests.""" api_version = 'v3' def show_message(self, message_id): """Show details for a single message.""" url = 'messages/%s' % str(message_id) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_messages(self): """List all messages.""" url = 'messages' resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_message(self, message_id): """Delete a single message.""" url = 'messages/%s' % str(message_id) resp, body = self.delete(url) self.expected_success(204, resp.status) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): try: self.show_message(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 'message' tempest-17.2.0/tempest/lib/services/volume/v3/snapshots_client.py0000666000175100017510000000153513207044712025157 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.services.volume.v2 import snapshots_client class SnapshotsClient(snapshots_client.SnapshotsClient): """Client class to send CRUD Volume Snapshot V3 API requests.""" api_version = "v3" tempest-17.2.0/tempest/lib/services/volume/v3/base_client.py0000666000175100017510000000157313207044712024051 0ustar zuulzuul00000000000000# Copyright 2016 Andrew Kerr # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from debtcollector import moves from tempest.lib.services.volume import base_client BaseClient = moves.moved_class(base_client.BaseClient, 'BaseClient', __name__, version="Pike", removal_version='?') BaseClient.api_version = 'v3' tempest-17.2.0/tempest/lib/services/volume/v3/volumes_client.py0000666000175100017510000000300713207044712024623 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client from tempest.lib.services.volume.v2 import volumes_client class VolumesClient(volumes_client.VolumesClient): """Client class to send CRUD Volume V3 API requests""" api_version = "v3" def show_volume_summary(self, **params): """Get volumes summary. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#get-volumes-summary """ url = 'volumes/summary' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v3/group_types_client.py0000666000175100017510000000561413207044712025517 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # 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.common import rest_client from tempest.lib.services.volume import base_client class GroupTypesClient(base_client.BaseClient): """Client class to send CRUD Volume V3 Group Types API requests""" api_version = 'v3' @property def resource_type(self): """Returns the primary type of resource this client works with.""" return 'group-type' def create_group_type(self, **kwargs): """Create group_type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#create-group-type """ post_body = json.dumps({'group_type': kwargs}) resp, body = self.post('group_types', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def delete_group_type(self, group_type_id): """Deletes the specified group_type.""" resp, body = self.delete("group_types/%s" % group_type_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_group_types(self, **params): """List all the group_types created. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#list-group-types """ url = 'group_types' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_group_type(self, group_type_id): """Returns the details of a single group_type. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#show-group-type-details """ url = "group_types/%s" % group_type_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v3/__init__.py0000666000175100017510000000274013207044712023335 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.volume.v3.backups_client import BackupsClient from tempest.lib.services.volume.v3.base_client import BaseClient from tempest.lib.services.volume.v3.group_snapshots_client import \ GroupSnapshotsClient from tempest.lib.services.volume.v3.group_types_client import GroupTypesClient from tempest.lib.services.volume.v3.groups_client import GroupsClient from tempest.lib.services.volume.v3.messages_client import MessagesClient from tempest.lib.services.volume.v3.snapshots_client import SnapshotsClient from tempest.lib.services.volume.v3.versions_client import VersionsClient from tempest.lib.services.volume.v3.volumes_client import VolumesClient __all__ = ['BackupsClient', 'BaseClient', 'GroupsClient', 'GroupSnapshotsClient', 'GroupTypesClient', 'MessagesClient', 'SnapshotsClient', 'VersionsClient', 'VolumesClient'] tempest-17.2.0/tempest/lib/services/volume/v3/versions_client.py0000666000175100017510000000350413207044712025003 0ustar zuulzuul00000000000000# Copyright 2017 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 time from oslo_serialization import jsonutils as json from tempest.lib.api_schema.response.volume import versions as schema from tempest.lib.common import rest_client from tempest.lib.services.volume import base_client class VersionsClient(base_client.BaseClient): api_version = 'v3' def list_versions(self): """List API versions For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#list-all-api-versions """ version_url = self._get_base_version_url() start = time.time() resp, body = self.raw_request(version_url, 'GET') end = time.time() # NOTE: We need a raw_request() here instead of request() call because # "list API versions" API doesn't require an authentication and we can # skip it with raw_request() call. self._log_request('GET', version_url, resp, secs=(end - start), resp_body=body) self._error_checker(resp, body) body = json.loads(body) self.validate_response(schema.list_versions, resp, body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v3/groups_client.py0000666000175100017510000001216113207044712024451 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # 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.common import rest_client from tempest.lib import exceptions as lib_exc from tempest.lib.services.volume import base_client class GroupsClient(base_client.BaseClient): """Client class to send CRUD Volume Group API requests""" api_version = 'v3' def create_group(self, **kwargs): """Creates a group. group_type and volume_types are required parameters in kwargs. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#create-group """ post_body = json.dumps({'group': kwargs}) resp, body = self.post('groups', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def delete_group(self, group_id, delete_volumes=True): """Deletes a group. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#delete-group """ post_body = {'delete-volumes': delete_volumes} post_body = json.dumps({'delete': post_body}) resp, body = self.post('groups/%s/action' % group_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_group(self, group_id): """Returns the details of a single group. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#show-group-details """ url = "groups/%s" % str(group_id) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_groups(self, detail=False, **params): """Lists information for all the tenant's groups. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#list-groups https://developer.openstack.org/api-ref/block-storage/v3/#list-groups-with-details """ url = "groups" if detail: url += "/detail" if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_group_from_source(self, **kwargs): """Creates a group from source. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#create-group-from-source """ post_body = json.dumps({'create-from-src': kwargs}) resp, body = self.post('groups/action', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_group(self, group_id, **kwargs): """Updates the specified group. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#update-group """ put_body = json.dumps({'group': kwargs}) resp, body = self.put('groups/%s' % group_id, put_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def reset_group_status(self, group_id, status_to_set): """Resets group status. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#reset-group-status """ post_body = json.dumps({'reset_status': {'status': status_to_set}}) resp, body = self.post('groups/%s/action' % group_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): try: self.show_group(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 'group' tempest-17.2.0/tempest/lib/services/volume/v3/group_snapshots_client.py0000666000175100017510000001011613207044712026366 0ustar zuulzuul00000000000000# Copyright (C) 2017 Dell Inc. or its subsidiaries. # 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.common import rest_client from tempest.lib import exceptions as lib_exc from tempest.lib.services.volume import base_client class GroupSnapshotsClient(base_client.BaseClient): """Client class to send CRUD Volume Group Snapshot API requests""" api_version = 'v3' def create_group_snapshot(self, **kwargs): """Creates a group snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#create-group-snapshot """ post_body = json.dumps({'group_snapshot': kwargs}) resp, body = self.post('group_snapshots', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def delete_group_snapshot(self, group_snapshot_id): """Deletes a group snapshot. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#delete-group-snapshot """ resp, body = self.delete('group_snapshots/%s' % group_snapshot_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_group_snapshot(self, group_snapshot_id): """Returns the details of a single group snapshot. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#show-group-snapshot-details """ url = "group_snapshots/%s" % str(group_snapshot_id) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_group_snapshots(self, detail=False, **params): """Information for all the tenant's group snapshots. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#list-group-snapshots https://developer.openstack.org/api-ref/block-storage/v3/#list-group-snapshots-with-details """ url = "group_snapshots" if detail: url += "/detail" if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def reset_group_snapshot_status(self, group_snapshot_id, status_to_set): """Resets group snapshot status. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v3/#reset-group-snapshot-status """ post_body = json.dumps({'reset_status': {'status': status_to_set}}) resp, body = self.post('group_snapshots/%s/action' % group_snapshot_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): try: self.show_group_snapshot(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 'group-snapshot' tempest-17.2.0/tempest/lib/services/volume/v2/0000775000175100017510000000000013207045130021211 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/volume/v2/availability_zone_client.py0000666000175100017510000000177713207044712026651 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class AvailabilityZoneClient(rest_client.RestClient): api_version = "v2" def list_availability_zones(self): resp, body = self.get('os-availability-zone') body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/backups_client.py0000666000175100017510000001075613207044712024571 0ustar zuulzuul00000000000000# Copyright 2014 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.common import rest_client from tempest.lib import exceptions as lib_exc from tempest.lib.services.volume import base_client class BackupsClient(base_client.BaseClient): """Volume V2 Backups client""" api_version = "v2" def create_backup(self, **kwargs): """Creates a backup of volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/index.html#create-backup """ post_body = json.dumps({'backup': kwargs}) resp, body = self.post('backups', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def restore_backup(self, backup_id, **kwargs): """Restore volume from backup. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/index.html#restore-backup """ post_body = json.dumps({'restore': kwargs}) resp, body = self.post('backups/%s/restore' % (backup_id), post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def delete_backup(self, backup_id): """Delete a backup of volume.""" resp, body = self.delete('backups/%s' % backup_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_backup(self, backup_id): """Returns the details of a single backup.""" url = "backups/%s" % backup_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_backups(self, detail=False, **params): """List all the tenant's backups. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#list-backups http://developer.openstack.org/api-ref/block-storage/v2/#list-backups-with-details """ url = "backups" if detail: url += "/detail" if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def export_backup(self, backup_id): """Export backup metadata record.""" url = "backups/%s/export_record" % backup_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def import_backup(self, **kwargs): """Import backup metadata record.""" post_body = json.dumps({'backup-record': kwargs}) resp, body = self.post("backups/import_record", post_body) body = json.loads(body) self.expected_success(201, resp.status) return rest_client.ResponseBody(resp, body) def reset_backup_status(self, backup_id, status): """Reset the specified backup's status.""" post_body = json.dumps({'os-reset_status': {"status": status}}) resp, body = self.post('backups/%s/action' % backup_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): try: self.show_backup(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 'backup' tempest-17.2.0/tempest/lib/services/volume/v2/quota_classes_client.py0000666000175100017510000000351613207044712026003 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class QuotaClassesClient(rest_client.RestClient): """Volume quota class V2 client.""" api_version = "v2" def show_quota_class_set(self, quota_class_id): """List quotas for a quota class. TODO: Current api-site doesn't contain this API description. LP: https://bugs.launchpad.net/nova/+bug/1602400 """ url = 'os-quota-class-sets/%s' % quota_class_id resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) def update_quota_class_set(self, quota_class_id, **kwargs): """Update quotas for a quota class. TODO: Current api-site doesn't contain this API description. LP: https://bugs.launchpad.net/nova/+bug/1602400 """ url = 'os-quota-class-sets/%s' % quota_class_id put_body = json.dumps({'quota_class_set': kwargs}) resp, body = self.put(url, put_body) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/qos_client.py0000666000175100017510000001175013207044712023736 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc class QosSpecsClient(rest_client.RestClient): """Volume V2 QoS client. Client class to send CRUD QoS API requests """ api_version = "v2" def is_resource_deleted(self, qos_id): try: self.show_qos(qos_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 'qos' def create_qos(self, **kwargs): """Create a QoS Specification. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-qos-specification """ post_body = json.dumps({'qos_specs': kwargs}) resp, body = self.post('qos-specs', post_body) self.expected_success(200, resp.status) body = json.loads(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" % (qos_id, force)) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_qos(self): """List all the QoS specifications created.""" url = 'qos-specs' resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_qos(self, qos_id): """Get the specified QoS specification.""" url = "qos-specs/%s" % qos_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def set_qos_key(self, qos_id, **kwargs): """Set the specified keys/values of QoS specification. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#set-keys-in-qos-specification """ put_body = json.dumps({"qos_specs": kwargs}) resp, body = self.put('qos-specs/%s' % qos_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def unset_qos_key(self, qos_id, keys): """Unset the specified keys of QoS specification. :param keys: keys to delete from the QoS specification. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#unset-keys-in-qos-specification """ 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 rest_client.ResponseBody(resp, body) def associate_qos(self, qos_id, vol_type_id): """Associate the specified QoS with specified volume-type.""" url = "qos-specs/%s/associate" % qos_id url += "?vol_type_id=%s" % vol_type_id resp, body = self.get(url) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_association_qos(self, qos_id): """Get the association of the specified QoS specification.""" url = "qos-specs/%s/associations" % qos_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def disassociate_qos(self, qos_id, vol_type_id): """Disassociate the specified QoS with specified volume-type.""" url = "qos-specs/%s/disassociate" % qos_id url += "?vol_type_id=%s" % vol_type_id resp, body = self.get(url) self.expected_success(202, resp.status) 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" % qos_id resp, body = self.get(url) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/snapshots_client.py0000666000175100017510000002054113207044712025154 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 from tempest.lib import exceptions as lib_exc class SnapshotsClient(rest_client.RestClient): """Client class to send CRUD Volume V2 API requests.""" api_version = "v2" create_resp = 202 def list_snapshots(self, detail=False, **params): """List all the snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots """ url = 'snapshots' if detail: url += '/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_snapshot(self, snapshot_id): """Returns the details of a single snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details """ url = "snapshots/%s" % snapshot_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_snapshot(self, **kwargs): """Creates a new snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot """ post_body = json.dumps({'snapshot': kwargs}) resp, body = self.post('snapshots', post_body) body = json.loads(body) self.expected_success(self.create_resp, resp.status) return rest_client.ResponseBody(resp, body) def update_snapshot(self, snapshot_id, **kwargs): """Updates a snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot """ put_body = json.dumps({'snapshot': kwargs}) resp, body = self.put('snapshots/%s' % snapshot_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_snapshot(self, snapshot_id): """Delete Snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot """ resp, body = self.delete("snapshots/%s" % snapshot_id) self.expected_success(202, resp.status) 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): """Returns the primary type of resource this client works with.""" return 'volume-snapshot' def reset_snapshot_status(self, snapshot_id, status): """Reset the specified snapshot's status.""" 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 rest_client.ResponseBody(resp, body) def update_snapshot_status(self, snapshot_id, **kwargs): """Update the specified snapshot's status.""" # TODO(gmann): api-site doesn't contain doc ref # for this API. After fixing the api-site, we need to # add the link here. # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645 post_body = json.dumps({'os-update_snapshot_status': kwargs}) url = 'snapshots/%s/action' % snapshot_id resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def create_snapshot_metadata(self, snapshot_id, metadata): """Create metadata for the snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot-metadata """ put_body = json.dumps({'metadata': metadata}) url = "snapshots/%s/metadata" % snapshot_id resp, body = self.post(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_snapshot_metadata(self, snapshot_id): """Get metadata of the snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata """ url = "snapshots/%s/metadata" % snapshot_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_snapshot_metadata(self, snapshot_id, **kwargs): """Update metadata for the snapshot. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata """ put_body = json.dumps(kwargs) url = "snapshots/%s/metadata" % snapshot_id resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_snapshot_metadata_item(self, snapshot_id, id): """Show metadata item for the snapshot.""" url = "snapshots/%s/metadata/%s" % (snapshot_id, id) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs): """Update metadata item for the snapshot.""" # TODO(piyush): Current api-site doesn't contain this API description. # 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/1529064 put_body = json.dumps(kwargs) url = "snapshots/%s/metadata/%s" % (snapshot_id, id) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) 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" % (snapshot_id, id) resp, body = self.delete(url) self.expected_success(200, resp.status) 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 rest_client.ResponseBody(resp, body) def unmanage_snapshot(self, snapshot_id): """Unmanage a snapshot.""" post_body = json.dumps({'os-unmanage': {}}) url = 'snapshots/%s/action' % (snapshot_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/services_client.py0000666000175100017510000000227313207044712024757 0ustar zuulzuul00000000000000# Copyright 2014 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.common import rest_client class ServicesClient(rest_client.RestClient): """Client class to send CRUD Volume V2 API requests""" api_version = "v2" 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.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/capabilities_client.py0000666000175100017510000000245213207044712025564 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class CapabilitiesClient(rest_client.RestClient): api_version = "v2" def show_backend_capabilities(self, host): """Shows capabilities for a storage back end. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-back-end-capabilities """ url = 'capabilities/%s' % host resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/volumes_client.py0000666000175100017510000003414213207044712024626 0ustar zuulzuul00000000000000# 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 import six from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client from tempest.lib import exceptions as lib_exc from tempest.lib.services.volume import base_client class VolumesClient(base_client.BaseClient): """Client class to send CRUD Volume V2 API requests""" api_version = "v2" def _prepare_params(self, params): """Prepares params for use in get or _ext_get methods. If params is a string it will be left as it is, but if it's not it will be urlencoded. """ if isinstance(params, six.string_types): return params return urllib.urlencode(params) def list_volumes(self, detail=False, params=None): """List all the volumes created. Params can be a string (must be urlencoded) or a dictionary. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes """ url = 'volumes' if detail: url += '/detail' if params: url += '?%s' % self._prepare_params(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % volume_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_volume(self, **kwargs): """Creates a new Volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-volume """ post_body = json.dumps({'volume': kwargs}) resp, body = self.post('volumes', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_volume(self, volume_id, **kwargs): """Updates the Specified Volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-volume """ put_body = json.dumps({'volume': kwargs}) resp, body = self.put('volumes/%s' % volume_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume(self, volume_id, **params): """Deletes the Specified Volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume """ url = 'volumes/%s' % volume_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.delete(url) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def upload_volume(self, volume_id, **kwargs): """Uploads a volume in Glance.""" post_body = json.dumps({'os-volume_upload_image': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def attach_volume(self, volume_id, **kwargs): """Attaches a volume to a given instance on a given mountpoint. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server """ post_body = json.dumps({'os-attach': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def set_bootable_volume(self, volume_id, **kwargs): """Set a bootable flag for a volume - true or false. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-bootable-status """ post_body = json.dumps({'os-set_bootable': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = json.dumps({'os-detach': {}}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def reserve_volume(self, volume_id): """Reserves a volume.""" post_body = json.dumps({'os-reserve': {}}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def unreserve_volume(self, volume_id): """Restore a reserved volume .""" post_body = json.dumps({'os-unreserve': {}}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def is_resource_deleted(self, id): """Check the specified resource is deleted or not. :param id: A checked resource id :raises lib_exc.DeleteErrorException: If the specified resource is on the status the delete was failed. """ try: volume = self.show_volume(id) except lib_exc.NotFound: return True if volume["volume"]["status"] == "error_deleting": raise lib_exc.DeleteErrorException(resource_id=id) return False @property def resource_type(self): """Returns the primary type of resource this client works with.""" return 'volume' def extend_volume(self, volume_id, **kwargs): """Extend a volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size """ post_body = json.dumps({'os-extend': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def reset_volume_status(self, volume_id, **kwargs): """Reset the Specified Volume's Status. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses """ 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 rest_client.ResponseBody(resp, body) def update_volume_readonly(self, volume_id, **kwargs): """Update the Specified Volume readonly.""" post_body = json.dumps({'os-update_readonly_flag': kwargs}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) self.expected_success(202, resp.status) 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 rest_client.ResponseBody(resp, body) def create_volume_metadata(self, volume_id, metadata): """Create metadata for the volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-metadata """ put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % volume_id resp, body = self.post(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_metadata(self, volume_id): """Get metadata of the volume.""" url = "volumes/%s/metadata" % volume_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_metadata(self, volume_id, metadata): """Update metadata for the volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-metadata """ put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % volume_id resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_metadata_item(self, volume_id, id): """Show metadata item for the volume.""" url = "volumes/%s/metadata/%s" % (volume_id, id) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_metadata_item(self, volume_id, id, meta_item): """Update metadata item for the volume.""" put_body = json.dumps({'meta': meta_item}) url = "volumes/%s/metadata/%s" % (volume_id, id) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) 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" % (volume_id, id) resp, body = self.delete(url) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def retype_volume(self, volume_id, **kwargs): """Updates volume with new volume type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#retype-volume """ post_body = json.dumps({'os-retype': kwargs}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def force_detach_volume(self, volume_id, **kwargs): """Force detach a volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#force-detach-volume """ post_body = json.dumps({'os-force_detach': kwargs}) url = 'volumes/%s/action' % volume_id resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_image_metadata(self, volume_id, **kwargs): """Update image metadata for the volume. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#set-image-metadata-for-volume """ post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}}) url = "volumes/%s/action" % (volume_id) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume_image_metadata(self, volume_id, key_name): """Delete image metadata item for the volume.""" post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}}) url = "volumes/%s/action" % (volume_id) resp, body = self.post(url, post_body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_image_metadata(self, volume_id): """Show image metadata for the volume.""" post_body = json.dumps({'os-show_image_metadata': {}}) url = "volumes/%s/action" % volume_id resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def unmanage_volume(self, volume_id): """Unmanage volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#unmanage-volume """ post_body = json.dumps({'os-unmanage': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/__init__.py0000666000175100017510000000465713207044712023345 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.volume.v2.availability_zone_client \ import AvailabilityZoneClient from tempest.lib.services.volume.v2.backups_client import BackupsClient from tempest.lib.services.volume.v2.capabilities_client import \ CapabilitiesClient from tempest.lib.services.volume.v2.encryption_types_client import \ EncryptionTypesClient from tempest.lib.services.volume.v2.extensions_client import ExtensionsClient from tempest.lib.services.volume.v2.hosts_client import HostsClient from tempest.lib.services.volume.v2.limits_client import LimitsClient from tempest.lib.services.volume.v2.qos_client import QosSpecsClient from tempest.lib.services.volume.v2.quota_classes_client import \ QuotaClassesClient from tempest.lib.services.volume.v2.quotas_client import QuotasClient from tempest.lib.services.volume.v2.scheduler_stats_client import \ SchedulerStatsClient from tempest.lib.services.volume.v2.services_client import ServicesClient from tempest.lib.services.volume.v2.snapshot_manage_client import \ SnapshotManageClient from tempest.lib.services.volume.v2.snapshots_client import SnapshotsClient from tempest.lib.services.volume.v2.transfers_client import TransfersClient from tempest.lib.services.volume.v2.types_client import TypesClient from tempest.lib.services.volume.v2.volume_manage_client import \ VolumeManageClient from tempest.lib.services.volume.v2.volumes_client import VolumesClient __all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient', 'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient', 'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient', 'LimitsClient', 'CapabilitiesClient', 'SchedulerStatsClient', 'SnapshotManageClient', 'VolumeManageClient', 'TransfersClient', 'QuotaClassesClient'] tempest-17.2.0/tempest/lib/services/volume/v2/volume_manage_client.py0000666000175100017510000000255713207044712025760 0ustar zuulzuul00000000000000# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class VolumeManageClient(rest_client.RestClient): """Volume manage V2 client.""" api_version = "v2" def manage_volume(self, **kwargs): """Manage existing volume. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#manage-existing-volume """ post_body = json.dumps({'volume': kwargs}) resp, body = self.post('os-volume-manage', post_body) self.expected_success(202, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/encryption_types_client.py0000666000175100017510000000620313207044712026547 0ustar zuulzuul00000000000000# 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 from tempest.lib import exceptions as lib_exc class EncryptionTypesClient(rest_client.RestClient): api_version = "v2" def is_resource_deleted(self, id): try: body = self.show_encryption_type(id) if not body: return True except lib_exc.NotFound: return True return False @property def resource_type(self): """Returns the primary type of resource this client works with.""" return 'encryption-type' def show_encryption_type(self, volume_type_id): """Get the volume encryption type for the specified volume type. volume_type_id: Id of volume_type. """ url = "/types/%s/encryption" % volume_type_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_encryption_type(self, volume_type_id, **kwargs): """Create encryption type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2 """ url = "/types/%s/encryption" % volume_type_id post_body = json.dumps({'encryption': kwargs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_encryption_type(self, volume_type_id): """Delete the encryption type for the specified volume-type.""" resp, body = self.delete( "/types/%s/encryption/provider" % volume_type_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_encryption_type(self, volume_type_id, **kwargs): """Update an encryption type for an existing volume type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#update-an-encryption-type-for-v2 """ url = "/types/%s/encryption/provider" % volume_type_id put_body = json.dumps({'encryption': kwargs}) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/scheduler_stats_client.py0000666000175100017510000000252713207044712026332 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class SchedulerStatsClient(rest_client.RestClient): api_version = "v2" def list_pools(self, detail=False): """List all the volumes pools (hosts). For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/index.html#list-back-end-storage-pools """ url = 'scheduler-stats/get_pools' if detail: url += '?detail=True' resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/limits_client.py0000666000175100017510000000212413207044712024430 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class LimitsClient(rest_client.RestClient): """Volume V2 limits client.""" api_version = "v2" def show_limits(self): """Returns the details of a volume absolute limits.""" url = "limits" resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/hosts_client.py0000666000175100017510000000324613207044712024275 0ustar zuulzuul00000000000000# Copyright 2014 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.common import rest_client class HostsClient(rest_client.RestClient): """Client class to send CRUD Volume V2 API requests""" api_version = "v2" def list_hosts(self, **params): """Lists all hosts. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts """ url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_host(self, host_name): """Show host details.""" url = 'os-hosts/%s' % host_name resp, body = self.get(url) self.expected_success(200, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/types_client.py0000666000175100017510000002040213207044712024272 0ustar zuulzuul00000000000000# 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.common import rest_client from tempest.lib import exceptions as lib_exc class TypesClient(rest_client.RestClient): """Client class to send CRUD Volume V2 API requests""" api_version = "v2" def is_resource_deleted(self, id): try: self.show_volume_type(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 'volume-type' def list_volume_types(self, **params): """List all the volume_types created. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2 """ url = 'types' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_type(self, volume_type_id): """Returns the details of a single volume_type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2 """ url = "types/%s" % volume_type_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_volume_type(self, **kwargs): """Create volume type. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2 """ post_body = json.dumps({'volume_type': kwargs}) resp, body = self.post('types', post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume_type(self, volume_type_id): """Deletes the Specified Volume_type. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type """ resp, body = self.delete("types/%s" % volume_type_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_volume_types_extra_specs(self, volume_type_id, **params): """List all the volume_types extra specs created. TODO: 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. """ url = 'types/%s/extra_specs' % volume_type_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name): """Returns the details of a single volume_type extra spec.""" url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def create_volume_type_extra_specs(self, volume_type_id, extra_specs): """Creates a new Volume_type extra spec. volume_type_id: Id of volume_type. extra_specs: A dictionary of values to be used as extra_specs. """ url = "types/%s/extra_specs" % volume_type_id post_body = json.dumps({'extra_specs': extra_specs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name): """Deletes the Specified Volume_type extra spec.""" resp, body = self.delete("types/%s/extra_specs/%s" % ( volume_type_id, extra_spec_name)) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_type(self, volume_type_id, **kwargs): """Updates volume type name, description, and/or is_public. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type """ put_body = json.dumps({'volume_type': kwargs}) resp, body = self.put('types/%s' % volume_type_id, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name, extra_specs): """Update a volume_type extra spec. volume_type_id: Id of volume_type. extra_spec_name: Name of the extra spec to be updated. extra_spec: A dictionary of with key as extra_spec_name and the updated value. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type """ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name) put_body = json.dumps(extra_specs) resp, body = self.put(url, put_body) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def add_type_access(self, volume_type_id, **kwargs): """Adds volume type access for the given project. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#add-private-volume-type-access """ post_body = json.dumps({'addProjectAccess': kwargs}) url = 'types/%s/action' % volume_type_id resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def remove_type_access(self, volume_type_id, **kwargs): """Removes volume type access for the given project. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#remove-private-volume-type-access """ post_body = json.dumps({'removeProjectAccess': kwargs}) url = 'types/%s/action' % volume_type_id resp, body = self.post(url, post_body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def list_type_access(self, volume_type_id): """Print access information about the given volume type. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/block-storage/v2/#list-private-volume-type-access-details """ url = 'types/%s/os-volume-type-access' % volume_type_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/quotas_client.py0000666000175100017510000000461013207044712024445 0ustar zuulzuul00000000000000# Copyright 2014 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 from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class QuotasClient(rest_client.RestClient): """Client class to send CRUD Volume Quotas API V2 requests""" api_version = "v2" def show_default_quota_set(self, tenant_id): """List the default volume quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % tenant_id resp, body = self.get(url) self.expected_success(200, resp.status) body = jsonutils.loads(body) return rest_client.ResponseBody(resp, body) def show_quota_set(self, tenant_id, params=None): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) self.expected_success(200, resp.status) body = jsonutils.loads(body) return rest_client.ResponseBody(resp, body) def update_quota_set(self, tenant_id, **kwargs): """Updates quota set For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/index.html#update-quotas """ put_body = jsonutils.dumps({'quota_set': kwargs}) resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body) self.expected_success(200, resp.status) body = jsonutils.loads(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 rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/transfers_client.py0000666000175100017510000000645013207044712025144 0ustar zuulzuul00000000000000# 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.common import rest_client class TransfersClient(rest_client.RestClient): """Client class to send CRUD Volume Transfer V2 API requests""" api_version = "v2" def create_volume_transfer(self, **kwargs): """Create a volume transfer. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer """ post_body = json.dumps({'transfer': kwargs}) resp, body = self.post('os-volume-transfer', post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def show_volume_transfer(self, transfer_id): """Returns the details of a volume transfer.""" url = "os-volume-transfer/%s" % transfer_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) def list_volume_transfers(self, detail=False, **params): """List all the volume transfers created. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers-with-details """ url = 'os-volume-transfer' if detail: url += '/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) 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" % transfer_id) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) def accept_volume_transfer(self, transfer_id, **kwargs): """Accept a volume transfer. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer """ url = 'os-volume-transfer/%s/accept' % transfer_id post_body = json.dumps({'accept': kwargs}) resp, body = self.post(url, post_body) body = json.loads(body) self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/extensions_client.py0000666000175100017510000000204013207044712025323 0ustar zuulzuul00000000000000# 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. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class ExtensionsClient(rest_client.RestClient): """Volume V2 extensions client.""" api_version = "v2" def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/volume/v2/snapshot_manage_client.py0000666000175100017510000000223313207044712026277 0ustar zuulzuul00000000000000# Copyright 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils as json from tempest.lib.common import rest_client class SnapshotManageClient(rest_client.RestClient): """Snapshot manage V2 client.""" api_version = "v2" def manage_snapshot(self, **kwargs): """Manage a snapshot.""" post_body = json.dumps({'snapshot': kwargs}) url = 'os-snapshot-manage' resp, body = self.post(url, post_body) self.expected_success(202, resp.status) body = json.loads(body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/object_storage/0000775000175100017510000000000013207045130022345 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/object_storage/bulk_middleware_client.py0000666000175100017510000000466313207044712027427 0ustar zuulzuul00000000000000# 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 tempest.lib.common import rest_client class BulkMiddlewareClient(rest_client.RestClient): def upload_archive(self, upload_path, data, archive_file_format='tar', headers=None): """Expand tar files into a Swift cluster. To extract containers and objects on Swift cluster from uploaded archived file. For More information please check: https://docs.openstack.org/swift/latest/middleware.html#module-swift.common.middleware.bulk """ url = '%s?extract-archive=%s' % (upload_path, archive_file_format) if headers is None: headers = {} resp, body = self.put(url, data, headers) self.expected_success(200, resp.status) return rest_client.ResponseBodyData(resp, body) def delete_bulk_data(self, data=None, headers=None): """Delete multiple objects or containers from their account. For More information please check: https://docs.openstack.org/swift/latest/middleware.html#module-swift.common.middleware.bulk """ url = '?bulk-delete' if headers is None: headers = {} resp, body = self.delete(url, headers, data) self.expected_success(200, resp.status) return rest_client.ResponseBodyData(resp, body) def delete_bulk_data_with_post(self, data=None, headers=None): """Delete multiple objects or containers with POST request. For More information please check: https://docs.openstack.org/swift/latest/middleware.html#module-swift.common.middleware.bulk """ url = '?bulk-delete' if headers is None: headers = {} resp, body = self.post(url, data, headers) self.expected_success([200, 204], resp.status) return rest_client.ResponseBodyData(resp, body) tempest-17.2.0/tempest/lib/services/object_storage/capabilities_client.py0000666000175100017510000000206213207044712026715 0ustar zuulzuul00000000000000# 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 CapabilitiesClient(rest_client.RestClient): def list_capabilities(self): self.skip_path() try: resp, body = self.get('info') finally: self.reset_path() body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/object_storage/__init__.py0000666000175100017510000000222313207044712024464 0ustar zuulzuul00000000000000# 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. from tempest.lib.services.object_storage.account_client import AccountClient from tempest.lib.services.object_storage.bulk_middleware_client import \ BulkMiddlewareClient from tempest.lib.services.object_storage.capabilities_client import \ CapabilitiesClient from tempest.lib.services.object_storage.container_client import \ ContainerClient from tempest.lib.services.object_storage.object_client import ObjectClient __all__ = ['AccountClient', 'BulkMiddlewareClient', 'CapabilitiesClient', 'ContainerClient', 'ObjectClient'] tempest-17.2.0/tempest/lib/services/object_storage/object_client.py0000666000175100017510000001357013207044712025540 0ustar zuulzuul00000000000000# 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 six.moves import http_client as httplib from six.moves.urllib import parse as urlparse from tempest.lib.common import rest_client from tempest.lib import exceptions class ObjectClient(rest_client.RestClient): def create_object(self, container, object_name, data, params=None, metadata=None, headers=None, chunked=False): """Create storage object.""" if headers is None: headers = self.get_headers() if not data: headers['content-length'] = '0' if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "%s/%s" % (str(container), str(object_name)) if params: url += '?%s' % urlparse.urlencode(params) resp, body = self.put(url, data, headers, chunked=chunked) self.expected_success(201, resp.status) return resp, body def delete_object(self, container, object_name, params=None): """Delete storage object.""" url = "%s/%s" % (str(container), str(object_name)) if params: url += '?%s' % urlparse.urlencode(params) resp, body = self.delete(url, headers={}) self.expected_success([200, 204], resp.status) return resp, body def create_or_update_object_metadata(self, container, object_name, headers=None): """Add, remove, or change X-Object-Meta metadata for storage object.""" url = "%s/%s" % (str(container), str(object_name)) resp, body = self.post(url, None, headers=headers) self.expected_success(202, resp.status) return resp, body def list_object_metadata(self, container, object_name, params=None, headers=None): """List all storage object X-Object-Meta- metadata.""" url = "%s/%s" % (str(container), str(object_name)) if params: url += '?%s' % urlparse.urlencode(params) resp, body = self.head(url, headers=headers) self.expected_success(200, resp.status) return resp, body def get_object(self, container, object_name, metadata=None, params=None): """Retrieve object's data.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "{0}/{1}".format(container, object_name) if params: url += '?%s' % urlparse.urlencode(params) resp, body = self.get(url, headers=headers) self.expected_success([200, 206], resp.status) return resp, body def copy_object_2d_way(self, container, src_object_name, dest_object_name, metadata=None): """Copy storage object's data to the new object using COPY.""" url = "{0}/{1}".format(container, src_object_name) headers = {} headers['Destination'] = "%s/%s" % (str(container), str(dest_object_name)) if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.copy(url, headers=headers) self.expected_success(201, resp.status) return resp, body def create_object_continue(self, container, object_name, data, metadata=None): """Put an object using Expect:100-continue""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] headers['X-Auth-Token'] = self.token headers['content-length'] = 0 if data is None else len(data) headers['Expect'] = '100-continue' parsed = urlparse.urlparse(self.base_url) path = str(parsed.path) + "/" path += "%s/%s" % (str(container), str(object_name)) conn = _create_connection(parsed) # Send the PUT request and the headers including the "Expect" header conn.putrequest('PUT', path) for header, value in headers.items(): conn.putheader(header, value) conn.endheaders() # Read the 100 status prior to sending the data response = conn.response_class(conn.sock, method=conn._method) _, status, _ = response._read_status() # toss the CRLF at the end of the response response._safe_read(2) # Expecting a 100 here, if not close and throw an exception if status != 100: conn.close() pattern = "%s %s" % ( """Unexpected http success status code {0}.""", """The expected status code is {1}""") details = pattern.format(status, 100) raise exceptions.UnexpectedResponseCode(details) # If a continue was received go ahead and send the data # and get the final response conn.send(data) resp = conn.getresponse() return resp.status, resp.reason def _create_connection(parsed_url): """Helper function to create connection with httplib :param parsed_url: parsed url of the remote location """ if parsed_url.scheme == 'https': conn = httplib.HTTPSConnection(parsed_url.netloc) else: conn = httplib.HTTPConnection(parsed_url.netloc) return conn tempest-17.2.0/tempest/lib/services/object_storage/account_client.py0000666000175100017510000000602013207044712025716 0ustar zuulzuul00000000000000# 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 xml.etree import ElementTree as etree from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class AccountClient(rest_client.RestClient): def create_update_or_delete_account_metadata( self, create_update_metadata=None, delete_metadata=None, create_update_metadata_prefix='X-Account-Meta-', delete_metadata_prefix='X-Remove-Account-Meta-'): """Creates, Updates or deletes an account metadata entry. Account Metadata can be created, updated or deleted based on metadata header or value. For detailed info, please refer to the official API reference: http://developer.openstack.org/api-ref/object-storage/?expanded=create-update-or-delete-account-metadata-detail """ headers = {} if create_update_metadata: for key in create_update_metadata: metadata_header_name = create_update_metadata_prefix + key headers[metadata_header_name] = create_update_metadata[key] if delete_metadata: for key in delete_metadata: headers[delete_metadata_prefix + key] = delete_metadata[key] resp, body = self.post('', headers=headers, body=None) self.expected_success([200, 204], resp.status) return resp, body def list_account_metadata(self): """List all account metadata.""" resp, body = self.head('') self.expected_success(204, resp.status) return resp, body def list_account_containers(self, params=None): """List all containers for the account. Given valid X-Auth-Token, returns a list of all containers for the account. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/object-store/#show-account-details-and-list-containers """ url = '?%s' % urllib.urlencode(params) if params else '' resp, body = self.get(url, headers={}) if params and params.get('format') == 'json': body = json.loads(body) elif params and params.get('format') == 'xml': body = etree.fromstring(body) else: body = body.strip().splitlines() self.expected_success([200, 204], resp.status) return resp, body tempest-17.2.0/tempest/lib/services/object_storage/container_client.py0000666000175100017510000001145513207044712026254 0ustar zuulzuul00000000000000# 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 xml.etree import ElementTree as etree import debtcollector.moves from oslo_serialization import jsonutils as json from six.moves.urllib import parse as urllib from tempest.lib.common import rest_client class ContainerClient(rest_client.RestClient): def update_container(self, container_name, **headers): """Creates or Updates a container with optional metadata passed in as a dictionary. Full list of allowed headers or value, please refer to the official API reference: https://developer.openstack.org/api-ref/object-store/#create-container """ url = str(container_name) resp, body = self.put(url, body=None, headers=headers) self.expected_success([201, 202], resp.status) return resp, body # NOTE: This alias is for the usability because PUT can be used for both # updating/creating a resource and this PUT is mainly used for creating # on Swift container API. create_container = update_container def delete_container(self, container_name): """Deletes the container (if it's empty).""" url = str(container_name) resp, body = self.delete(url) self.expected_success(204, resp.status) return resp, body def create_update_or_delete_container_metadata( self, container_name, create_update_metadata=None, delete_metadata=None, create_update_metadata_prefix='X-Container-Meta-', delete_metadata_prefix='X-Remove-Container-Meta-'): """Creates, Updates or deletes an containter metadata entry. Container Metadata can be created, updated or deleted based on metadata header or value. For detailed info, please refer to the official API reference: https://developer.openstack.org/api-ref/object-store/#create-update-or-delete-container-metadata """ url = str(container_name) headers = {} if create_update_metadata: for key in create_update_metadata: metadata_header_name = create_update_metadata_prefix + key headers[metadata_header_name] = create_update_metadata[key] if delete_metadata: for key in delete_metadata: headers[delete_metadata_prefix + key] = delete_metadata[key] resp, body = self.post(url, headers=headers, body=None) self.expected_success(204, resp.status) return resp, body update_container_metadata = debtcollector.moves.moved_function( create_update_or_delete_container_metadata, 'update_container_metadata', __name__, version='Queens', removal_version='Rocky') def list_container_metadata(self, container_name): """List all container metadata.""" url = str(container_name) resp, body = self.head(url) self.expected_success(204, resp.status) return resp, body def list_container_objects(self, container_name, params=None): """List the objects in a container, given the container name Returns the container object listing as a plain text list, or as xml or json if that option is specified via the 'format' argument. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/object-storage/?expanded=show-container-details-and-list-objects-detail """ url = str(container_name) if params: url += '?' url += '&%s' % urllib.urlencode(params) resp, body = self.get(url, headers={}) if params and params.get('format') == 'json': body = json.loads(body) elif params and params.get('format') == 'xml': body = etree.fromstring(body) # Else the content-type is plain/text else: body = [ obj_name for obj_name in body.decode().split('\n') if obj_name ] self.expected_success([200, 204], resp.status) return resp, body list_container_contents = debtcollector.moves.moved_function( list_container_objects, 'list_container_contents', __name__, version='Queens', removal_version='Rocky') tempest-17.2.0/tempest/lib/services/compute/0000775000175100017510000000000013207045130021027 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/services/compute/availability_zone_client.py0000666000175100017510000000255413207044712026461 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class AvailabilityZoneClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/interfaces_client.py0000666000175100017510000000471013207044712025073 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class InterfacesClient(base_compute_client.BaseComputeClient): 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-interface """ 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) tempest-17.2.0/tempest/lib/services/compute/quota_classes_client.py0000666000175100017510000000364413207044712025623 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class QuotaClassesClient(base_compute_client.BaseComputeClient): 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. # NOTE: Current api-site doesn't contain this API description. # LP: https://bugs.launchpad.net/nova/+bug/1602400 """ 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) tempest-17.2.0/tempest/lib/services/compute/floating_ip_pools_client.py0000666000175100017510000000255213207044712026461 0ustar zuulzuul00000000000000# 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.services.compute import base_compute_client class FloatingIPPoolsClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/tenant_networks_client.py0000666000175100017510000000272213207044712026176 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class TenantNetworksClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/snapshots_client.py0000666000175100017510000000602213207044712024770 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class SnapshotsClient(base_compute_client.BaseComputeClient): def create_snapshot(self, volume_id, **kwargs): """Create a snapshot. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-snapshot """ 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): """List snapshots. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-snapshots """ 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' tempest-17.2.0/tempest/lib/services/compute/tenant_usages_client.py0000666000175100017510000000423213207044712025607 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class TenantUsagesClient(base_compute_client.BaseComputeClient): def list_tenant_usages(self, **params): """List Tenant Usage For All Tenants. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-tenant-usage-statistics-for-all-tenants """ 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): """Show Usage Details For Tenant. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-usage-statistics-for-tenant """ 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) tempest-17.2.0/tempest/lib/services/compute/services_client.py0000666000175100017510000001026413207044712024574 0ustar zuulzuul00000000000000# 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.api_schema.response.compute.v2_11 import services \ as schemav211 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class ServicesClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.10', 'schema': schema}, {'min': '2.11', 'max': None, 'schema': schemav211}] def list_services(self, **params): """Lists all running Compute services for a tenant. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-compute-services """ url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) _schema = self.get_schema(self.schema_versions_info) self.validate_response(_schema.list_services, resp, body) return rest_client.ResponseBody(resp, body) def enable_service(self, **kwargs): """Enable service on a host. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#enable-scheduling-for-a-compute-service """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service """ 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) def disable_log_reason(self, **kwargs): """Disables scheduling for a Compute service and logs reason. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason """ post_body = json.dumps(kwargs) resp, body = self.put('os-services/disable-log-reason', post_body) body = json.loads(body) self.validate_response(schema.disable_log_reason, resp, body) return rest_client.ResponseBody(resp, body) def update_forced_down(self, **kwargs): """Set or unset ``forced_down`` flag for the service. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-forced-down """ post_body = json.dumps(kwargs) resp, body = self.put('os-services/force-down', post_body) body = json.loads(body) # NOTE: Use schemav211.update_forced_down directly because there is no # update_forced_down schema for <2.11. self.validate_response(schemav211.update_forced_down, resp, body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/compute/agents_client.py0000666000175100017510000000573013207044712024234 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class AgentsClient(base_compute_client.BaseComputeClient): """Tests Agents API""" def list_agents(self, **params): """List all agent builds. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-agent-build """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-agent-build """ 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) tempest-17.2.0/tempest/lib/services/compute/migrations_client.py0000666000175100017510000000340313207044712025122 0ustar zuulzuul00000000000000# 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.api_schema.response.compute.v2_23 import migrations \ as schemav223 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class MigrationsClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.22', 'schema': schema}, {'min': '2.23', 'max': None, 'schema': schemav223}] def list_migrations(self, **params): """List all migrations. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-migrations """ url = 'os-migrations' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.list_migrations, resp, body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/compute/keypairs_client.py0000666000175100017510000000720713207044712024603 0ustar zuulzuul00000000000000# 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 keypairs as schemav21 from tempest.lib.api_schema.response.compute.v2_2 import keypairs as schemav22 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class KeyPairsClient(base_compute_client.BaseComputeClient): schema_versions_info = [{'min': None, 'max': '2.1', 'schema': schemav21}, {'min': '2.2', 'max': None, 'schema': schemav22}] def list_keypairs(self, **params): """Lists keypairs that are associated with the account. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-keypairs """ url = 'os-keypairs' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.list_keypairs, resp, body) return rest_client.ResponseBody(resp, body) def show_keypair(self, keypair_name, **params): """Shows details for a keypair that is associated with the account. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-keypair-details """ url = "os-keypairs/%s" % keypair_name if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.get_keypair, resp, body) return rest_client.ResponseBody(resp, body) def create_keypair(self, **kwargs): """Create a keypair. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-or-import-keypair """ post_body = json.dumps({'keypair': kwargs}) resp, body = self.post("os-keypairs", body=post_body) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.create_keypair, resp, body) return rest_client.ResponseBody(resp, body) def delete_keypair(self, keypair_name, **params): """Deletes a keypair. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-keypair """ url = "os-keypairs/%s" % keypair_name if params: url += '?%s' % urllib.urlencode(params) resp, body = self.delete(url) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.delete_keypair, resp, body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/compute/volumes_client.py0000666000175100017510000000664013207044712024446 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class VolumesClient(base_compute_client.BaseComputeClient): def list_volumes(self, detail=False, **params): """List all the volumes created. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-volumes https://developer.openstack.org/api-ref/compute/#list-volumes-with-details """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-volume-details """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-volume """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-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' tempest-17.2.0/tempest/lib/services/compute/__init__.py0000666000175100017510000001005013207044712023143 0ustar zuulzuul00000000000000# 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. 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 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 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.keypairs_client import KeyPairsClient 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 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 from tempest.lib.services.compute.security_groups_client import \ SecurityGroupsClient 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 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 __all__ = ['AgentsClient', 'AggregatesClient', 'AvailabilityZoneClient', 'BaremetalNodesClient', 'CertificatesClient', 'ExtensionsClient', 'FixedIPsClient', 'FlavorsClient', 'FloatingIPPoolsClient', 'FloatingIPsBulkClient', 'FloatingIPsClient', 'HostsClient', 'HypervisorClient', 'ImagesClient', 'InstanceUsagesAuditLogClient', 'InterfacesClient', 'KeyPairsClient', 'LimitsClient', 'MigrationsClient', 'NetworksClient', 'QuotaClassesClient', 'QuotasClient', 'SecurityGroupDefaultRulesClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient', 'ServerGroupsClient', 'ServersClient', 'ServicesClient', 'SnapshotsClient', 'TenantNetworksClient', 'TenantUsagesClient', 'VersionsClient', 'VolumesClient'] tempest-17.2.0/tempest/lib/services/compute/versions_client.py0000666000175100017510000000412413207044712024617 0ustar zuulzuul00000000000000# 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 time from oslo_serialization import jsonutils as json from tempest.lib.api_schema.response.compute.v2_1 import versions as schema from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class VersionsClient(base_compute_client.BaseComputeClient): def list_versions(self): version_url = self._get_base_version_url() start = time.time() resp, body = self.raw_request(version_url, 'GET') end = time.time() self._log_request('GET', version_url, resp, secs=(end - start), resp_body=body) self._error_checker(resp, body) 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}) self._error_checker(resp, body) body = json.loads(body) self.validate_response(schema.get_one_version, resp, body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/compute/server_groups_client.py0000666000175100017510000000554513207044712025664 0ustar zuulzuul00000000000000# 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.api_schema.response.compute.v2_13 import servers as schemav213 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class ServerGroupsClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.12', 'schema': schema}, {'min': '2.13', 'max': None, 'schema': schemav213}] def create_server_group(self, **kwargs): """Create the server group. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-server-group """ post_body = json.dumps({'server_group': kwargs}) resp, body = self.post('os-server-groups', post_body) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) 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) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.create_show_server_group, resp, body) return rest_client.ResponseBody(resp, body) tempest-17.2.0/tempest/lib/services/compute/floating_ips_client.py0000666000175100017510000001107413207044712025427 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class FloatingIPsClient(base_compute_client.BaseComputeClient): def list_floating_ips(self, **params): """Returns a list of all floating IPs filtered by any parameters. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-floating-ip-addresses """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-floating-ip-address-details """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-allocate-floating-ip-address """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address """ 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' tempest-17.2.0/tempest/lib/services/compute/limits_client.py0000666000175100017510000000216213207044712024250 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class LimitsClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/hosts_client.py0000666000175100017510000001007713207044712024113 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class HostsClient(base_compute_client.BaseComputeClient): def list_hosts(self, **params): """List all hosts. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-host-status """ 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): # noqa # NOTE: This noqa is for passing T110 check and we cannot rename # to keep backwards compatibility. Actually, the root problem # of this is a wrong API design. GET operation should not change # resource status, but current API does that. """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): # noqa # NOTE: This noqa is for passing T110 check and we cannot rename # to keep backwards compatibility. Actually, the root problem # of this is a wrong API design. GET operation should not change # resource status, but current API does that. """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): # noqa # NOTE: This noqa is for passing T110 check and we cannot rename # to keep backwards compatibility. Actually, the root problem # of this is a wrong API design. GET operation should not change # resource status, but current API does that. """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) tempest-17.2.0/tempest/lib/services/compute/floating_ips_bulk_client.py0000666000175100017510000000417113207044712026444 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class FloatingIPsBulkClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/aggregates_client.py0000666000175100017510000001176713207044712025073 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class AggregatesClient(base_compute_client.BaseComputeClient): 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-aggregate """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-aggregate """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#add-host """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#remove-host """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-or-update-aggregate-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) tempest-17.2.0/tempest/lib/services/compute/flavors_client.py0000666000175100017510000002261313207044712024426 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class FlavorsClient(base_compute_client.BaseComputeClient): def list_flavors(self, detail=False, **params): """Lists flavors. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-flavors https://developer.openstack.org/api-ref/compute/#list-flavors-with-details """ 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): """Shows details for a flavor. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-flavor-details """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-flavor """ if 'ephemeral' in kwargs: kwargs['OS-FLV-EXT-DATA:ephemeral'] = kwargs.pop('ephemeral') if 'is_public' in kwargs: 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-extra-specs-for-a-flavor """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-extra-specs-for-a-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-an-extra-spec-for-a-flavor """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-an-extra-spec-for-a-flavor """ 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): # noqa # NOTE: This noqa is for passing T111 check and we cannot rename # to keep backwards compatibility. """Unset extra Specs from the mentioned flavor. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-an-extra-spec-for-a-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-flavor-access-information-for-given-flavor """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#add-flavor-access-to-tenant-addtenantaccess-action """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#remove-flavor-access-from-tenant-removetenantaccess-action """ 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) tempest-17.2.0/tempest/lib/services/compute/instance_usage_audit_log_client.py0000666000175100017510000000310213207044712027761 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class InstanceUsagesAuditLogClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/baremetal_nodes_client.py0000666000175100017510000000411313207044712026071 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class BaremetalNodesClient(base_compute_client.BaseComputeClient): """Tests Baremetal API""" def list_baremetal_nodes(self, **params): """List all baremetal nodes. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-bare-metal-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): """Show the details of a single baremetal node. For more information, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-bare-metal-node-details """ 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) tempest-17.2.0/tempest/lib/services/compute/quotas_client.py0000666000175100017510000000640513207044712024267 0ustar zuulzuul00000000000000# 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 six.moves.urllib import parse as urllib from tempest.lib.api_schema.response.compute.v2_1 import quotas as schema from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class QuotasClient(base_compute_client.BaseComputeClient): def show_quota_set(self, tenant_id, user_id=None, detail=False): """List the quota set for a tenant. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref-compute-v2.1.html/#show-a-quota http://developer.openstack.org/api-ref-compute-v2.1.html/#show-the-detail-of-quota """ params = {} url = 'os-quota-sets/%s' % tenant_id if detail: url += '/detail' if user_id: params.update({'user_id': user_id}) if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) if detail: self.validate_response(schema.get_quota_set_details, resp, body) else: 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-quotas """ 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) tempest-17.2.0/tempest/lib/services/compute/security_group_rules_client.py0000666000175100017510000000357513207044712027255 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class SecurityGroupRulesClient(base_compute_client.BaseComputeClient): def create_security_group_rule(self, **kwargs): """Create a new security group rule. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-security-group-rule """ 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) tempest-17.2.0/tempest/lib/services/compute/networks_client.py0000666000175100017510000000241613207044712024625 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class NetworksClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/extensions_client.py0000666000175100017510000000253713207044712025154 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class ExtensionsClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/base_compute_client.py0000666000175100017510000000736713207044712025431 0ustar zuulzuul00000000000000# 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 api_version_request from tempest.lib.common import api_version_utils from tempest.lib.common import rest_client from tempest.lib import exceptions COMPUTE_MICROVERSION = None class BaseComputeClient(rest_client.RestClient): """Base compute service clients class to support microversion. This class adds microversion to API request header if that is set and provides interface to select appropriate JSON schema file for response validation. :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 kwargs: kwargs required by rest_client.RestClient """ api_microversion_header_name = 'X-OpenStack-Nova-API-Version' 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, chunked=False): resp, resp_body = super(BaseComputeClient, self).request( method, url, extra_headers, headers, body, chunked) if (COMPUTE_MICROVERSION and COMPUTE_MICROVERSION != api_version_utils.LATEST_MICROVERSION): api_version_utils.assert_version_header_matches_request( self.api_microversion_header_name, COMPUTE_MICROVERSION, resp) return resp, resp_body def get_schema(self, schema_versions_info): """Get JSON schema This method provides the matching schema for requested microversion. :param schema_versions_info: List of dict which provides schema information with range of valid versions. Example:: schema_versions_info = [ {'min': None, 'max': '2.1', 'schema': schemav21}, {'min': '2.2', 'max': '2.9', 'schema': schemav22}, {'min': '2.10', 'max': None, 'schema': schemav210}] """ schema = None 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']) # This is case where COMPUTE_MICROVERSION is None, which means # request without microversion So select base v2.1 schema. if version.is_null() and items['min'] is None: schema = items['schema'] break # else select appropriate schema as per COMPUTE_MICROVERSION elif version.matches(min_version, max_version): schema = items['schema'] break if schema is None: raise exceptions.JSONSchemaNotFound( version=version.get_string(), schema_versions_info=schema_versions_info) return schema tempest-17.2.0/tempest/lib/services/compute/hypervisor_client.py0000666000175100017510000000617613207044712025172 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class HypervisorClient(base_compute_client.BaseComputeClient): 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): # noqa # NOTE: This noqa is for passing T110 check and we cannot rename # to keep backwards compatibility. """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) tempest-17.2.0/tempest/lib/services/compute/security_group_default_rules_client.py0000666000175100017510000000565513207044712030762 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class SecurityGroupDefaultRulesClient(base_compute_client.BaseComputeClient): def create_security_default_group_rule(self, **kwargs): """Create security group default rule. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-default-security-group-rule """ 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) tempest-17.2.0/tempest/lib/services/compute/fixed_ips_client.py0000666000175100017510000000326313207044712024724 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class FixedIPsClient(base_compute_client.BaseComputeClient): 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#reserve-or-release-a-fixed-ip """ 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) tempest-17.2.0/tempest/lib/services/compute/certificates_client.py0000666000175100017510000000274713207044712025425 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class CertificatesClient(base_compute_client.BaseComputeClient): 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) tempest-17.2.0/tempest/lib/services/compute/security_groups_client.py0000666000175100017510000001014313207044712026213 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class SecurityGroupsClient(base_compute_client.BaseComputeClient): def list_security_groups(self, **params): """List all security groups for a user. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-security-groups """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-security-group-details """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-security-group """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-security-group """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-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' tempest-17.2.0/tempest/lib/services/compute/images_client.py0000666000175100017510000001376713207044712024231 0ustar zuulzuul00000000000000# 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 from tempest.lib.services.compute import base_compute_client class ImagesClient(base_compute_client.BaseComputeClient): def create_image(self, server_id, **kwargs): """Create an image of the original server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-image-createimage-action """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-images https://developer.openstack.org/api-ref/compute/#list-images-with-details """ 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) 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-image-metadata """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-image-metadata """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-or-update-image-metadata-item """ 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): # Added status check for user with admin role try: if self.show_image(id)['image']['status'] == 'DELETED': return True 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' tempest-17.2.0/tempest/lib/services/compute/servers_client.py0000666000175100017510000010754013207044712024446 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2017 AT&T 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 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 security_groups_schema from tempest.lib.api_schema.response.compute.v2_1 import servers as schema from tempest.lib.api_schema.response.compute.v2_16 import servers as schemav216 from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219 from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226 from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23 from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247 from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26 from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client class ServersClient(base_compute_client.BaseComputeClient): """Service client for the resource /servers""" schema_versions_info = [ {'min': None, 'max': '2.2', 'schema': schema}, {'min': '2.3', 'max': '2.5', 'schema': schemav23}, {'min': '2.6', 'max': '2.8', 'schema': schemav26}, {'min': '2.9', 'max': '2.15', 'schema': schemav29}, {'min': '2.16', 'max': '2.18', 'schema': schemav216}, {'min': '2.19', 'max': '2.25', 'schema': schemav219}, {'min': '2.26', 'max': '2.46', 'schema': schemav226}, {'min': '2.47', 'max': '2.47', 'schema': schemav247}, {'min': '2.48', 'max': None, 'schema': schemav248}] 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. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref/compute/#create-server :param name: Server name :param imageRef: Image reference (UUID) :param flavorRef: Flavor reference (UUID or full URL) 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.update(hints) 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-server 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 'disk_config' in kwargs: 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) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.update_server, resp, body) return rest_client.ResponseBody(resp, body) def show_server(self, server_id): """Get server details. For a full list of available parameters, please refer to the official API reference: http://developer.openstack.org/api-ref-compute-v2.1.html#showServer """ resp, body = self.get("servers/%s" % server_id) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.get_server, resp, body) return rest_client.ResponseBody(resp, body) def delete_server(self, server_id): """Delete server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-servers https://developer.openstack.org/api-ref/compute/#list-servers-detailed """ url = 'servers' schema = self.get_schema(self.schema_versions_info) _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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-ips """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-server-back-up-createbackup-action """ return self.action(server_id, "createBackup", **kwargs) def change_password(self, server_id, **kwargs): """Change the root password for the server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#change-administrative-password-changepassword-action """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#reboot-server-reboot-action """ return self.action(server_id, 'reboot', **kwargs) def rebuild_server(self, server_id, image_ref, **kwargs): """Rebuild a server with a new image. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#rebuild-server-rebuild-action 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') schema = self.get_schema(self.schema_versions_info) 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#resize-server-resize-action 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#confirm-resized-server-confirmresize-action """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#revert-resized-server-revertresize-action """ return self.action(server_id, 'revertResize', **kwargs) def list_server_metadata(self, server_id): """Lists all metadata for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-all-metadata """ 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): """Sets one or more metadata items for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-or-replace-metadata-items """ 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): """Updates one or more metadata items for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-metadata-items """ 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): """Shows details for a metadata item, by key, for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-metadata-item-details """ 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): """Sets a metadata item, by key, for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#create-or-update-metadata-item """ 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): """Deletes a metadata item, by key, from a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-metadata-item """ 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): """Stops a running server and changes its status to SHUTOFF. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#stop-server-os-stop-action """ return self.action(server_id, 'os-stop', **kwargs) def start_server(self, server_id, **kwargs): """Starts a stopped server and changes its status to ACTIVE. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#start-server-os-start-action """ return self.action(server_id, 'os-start', **kwargs) def attach_volume(self, server_id, **kwargs): """Attaches a volume to a server instance. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#attach-a-volume-to-an-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#detach-a-volume-from-an-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-a-detail-of-a-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-volume-attachments-for-an-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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#add-security-group-to-a-server-addsecuritygroup-action """ return self.action(server_id, 'addSecurityGroup', **kwargs) def remove_security_group(self, server_id, **kwargs): """Remove a security group from the server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#remove-security-group-from-a-server-removesecuritygroup-action """ return self.action(server_id, 'removeSecurityGroup', **kwargs) def live_migrate_server(self, server_id, **kwargs): """This should be called with administrator privileges. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#live-migrate-server-os-migratelive-action """ return self.action(server_id, 'os-migrateLive', **kwargs) def migrate_server(self, server_id, **kwargs): """Migrate a server to a new host. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#migrate-server-migrate-action """ return self.action(server_id, 'migrate', **kwargs) def lock_server(self, server_id, **kwargs): """Lock the given server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#lock-server-lock-action """ return self.action(server_id, 'lock', **kwargs) def unlock_server(self, server_id, **kwargs): """UNlock the given server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#unlock-server-unlock-action """ return self.action(server_id, 'unlock', **kwargs) def suspend_server(self, server_id, **kwargs): """Suspend the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#suspend-server-suspend-action """ return self.action(server_id, 'suspend', **kwargs) def resume_server(self, server_id, **kwargs): """Un-suspend the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#resume-suspended-server-resume-action """ return self.action(server_id, 'resume', **kwargs) def pause_server(self, server_id, **kwargs): """Pause the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#pause-server-pause-action """ return self.action(server_id, 'pause', **kwargs) def unpause_server(self, server_id, **kwargs): """Un-pause the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#unpause-server-unpause-action """ return self.action(server_id, 'unpause', **kwargs) def reset_state(self, server_id, **kwargs): """Reset the state of a server to active/error. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#reset-server-state-os-resetstate-action """ return self.action(server_id, 'os-resetState', **kwargs) def shelve_server(self, server_id, **kwargs): """Shelve the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#shelve-server-shelve-action """ return self.action(server_id, 'shelve', **kwargs) def unshelve_server(self, server_id, **kwargs): """Un-shelve the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#unshelve-restore-shelved-server-unshelve-action """ return self.action(server_id, 'unshelve', **kwargs) def shelve_offload_server(self, server_id, **kwargs): """Shelve-offload the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#shelf-offload-remove-server-shelveoffload-action """ return self.action(server_id, 'shelveOffload', **kwargs) def get_console_output(self, server_id, **kwargs): """Get console output. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#show-console-output-os-getconsoleoutput-action """ return self.action(server_id, 'os-getConsoleOutput', schema.get_console_output, **kwargs) def get_remote_console(self, server_id, console_type, protocol, **kwargs): """Get a remote console. For a full list of available parameters, please refer to the official API reference: TODO (markus_z) The api-ref for that isn't yet available, update this here when the docs in Nova are updated. The old API is at http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action """ param = { 'remote_console': { 'type': console_type, 'protocol': protocol, } } post_body = json.dumps(param) resp, body = self.post("servers/%s/remote-consoles" % server_id, post_body) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.get_remote_consoles, resp, body) return rest_client.ResponseBody(resp, body) 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#rescue-server-rescue-action """ if self.enable_instance_password: rescue_schema = schema.rescue_server_with_admin_pass else: rescue_schema = schema.rescue_server return self.action(server_id, 'rescue', rescue_schema, **kwargs) def unrescue_server(self, server_id): """Unrescue the provided server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#unrescue-server-unrescue-action """ 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) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.show_server_diagnostics, resp, body) return rest_client.ResponseBody(resp, 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#force-delete-server-forcedelete-action """ return self.action(server_id, 'forceDelete', **kwargs) def restore_soft_deleted_server(self, server_id, **kwargs): """Restore a soft-deleted server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#restore-soft-deleted-instance-restore-action """ return self.action(server_id, 'restore', **kwargs) def reset_network(self, server_id, **kwargs): """Reset the Network of a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#reset-networking-on-a-server-resetnetwork-action """ return self.action(server_id, 'resetNetwork', **kwargs) def inject_network_info(self, server_id, **kwargs): """Inject the Network Info into server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#inject-network-information-injectnetworkinfo-action """ return self.action(server_id, 'injectNetworkInfo', **kwargs) def get_vnc_console(self, server_id, **kwargs): """Get URL of VNC console. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#get-vnc-console-os-getvncconsole-action """ 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. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#add-associate-fixed-ip-addfixedip-action """ return self.action(server_id, 'addFixedIp', **kwargs) def remove_fixed_ip(self, server_id, **kwargs): """Remove input fixed IP from input server instance. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#remove-disassociate-fixed-ip-removefixedip-action """ return self.action(server_id, 'removeFixedIp', **kwargs) def list_security_groups_by_server(self, server_id): """Lists security groups for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-security-groups-by-server """ resp, body = self.get("servers/%s/os-security-groups" % server_id) body = json.loads(body) self.validate_response(security_groups_schema.list_security_groups, resp, body) return rest_client.ResponseBody(resp, body) def list_tags(self, server_id): """Lists all tags for a server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#list-tags """ url = 'servers/%s/tags' % server_id resp, body = self.get(url) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.list_tags, resp, body) return rest_client.ResponseBody(resp, body) def update_all_tags(self, server_id, tags): """Replaces all tags on specified server with the new set of tags. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#replace-tags :param tags: List of tags to replace current server tags with. """ url = 'servers/%s/tags' % server_id put_body = {'tags': tags} resp, body = self.put(url, json.dumps(put_body)) body = json.loads(body) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.update_all_tags, resp, body) return rest_client.ResponseBody(resp, body) def delete_all_tags(self, server_id): """Deletes all tags from the specified server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-all-tags """ url = 'servers/%s/tags' % server_id resp, body = self.delete(url) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.delete_all_tags, resp, body) return rest_client.ResponseBody(resp, body) def check_tag_existence(self, server_id, tag): """Checks tag existence on the server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#check-tag-existence :param tag: Check for existence of tag on specified server. """ url = 'servers/%s/tags/%s' % (server_id, tag) resp, body = self.get(url) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.check_tag_existence, resp, body) return rest_client.ResponseBody(resp, body) def update_tag(self, server_id, tag): """Adds a single tag to the server if server has no specified tag. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#add-a-single-tag :param tag: Tag to be added to the specified server. """ url = 'servers/%s/tags/%s' % (server_id, tag) resp, body = self.put(url, None) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.update_tag, resp, body) return rest_client.ResponseBody(resp, body) def delete_tag(self, server_id, tag): """Deletes a single tag from the specified server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#delete-a-single-tag :param tag: Tag to be removed from the specified server. """ url = 'servers/%s/tags/%s' % (server_id, tag) resp, body = self.delete(url) schema = self.get_schema(self.schema_versions_info) self.validate_response(schema.delete_tag, resp, body) return rest_client.ResponseBody(resp, body) def evacuate_server(self, server_id, **kwargs): """Evacuate the given server. For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#evacuate-server-evacuate-action """ if self.enable_instance_password: evacuate_schema = schema.evacuate_server_with_admin_pass else: evacuate_schema = schema.evacuate_server return self.action(server_id, 'evacuate', evacuate_schema, **kwargs) tempest-17.2.0/tempest/lib/cmd/0000775000175100017510000000000013207045130016273 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/cmd/skip_tracker.py0000777000175100017510000001307513207044712021346 0ustar zuulzuul00000000000000#!/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 os import re from oslo_log import log as logging try: from launchpadlib import launchpad except ImportError: launchpad = None LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache') LOG = logging.getLogger(__name__) BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..')) TESTDIR = os.path.join(BASEDIR, 'tempest') def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('test_path', nargs='?', default=TESTDIR, help='Path of test dir') return parser.parse_args() def info(msg, *args, **kwargs): LOG.info(msg, *args, **kwargs) def debug(msg, *args, **kwargs): LOG.debug(msg, *args, **kwargs) def find_skips(start): """Find the entire list of skipped 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 = [] with open(path, 'rb') as content: lines = content.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: for method in result_dict[bug_no]: results.append((method, bug_no)) return results def main(): 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() tempest-17.2.0/tempest/lib/cmd/check_uuid.py0000777000175100017510000003310613207044712020765 0ustar zuulzuul00000000000000#!/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 from oslo_utils import uuidutils import six.moves.urllib.parse as urlparse DECORATOR_MODULE = 'decorators' DECORATOR_NAME = 'idempotent_id' DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE IMPORT_LINE = 'from tempest.lib 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 = uuidutils.generate_uuid() 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) @staticmethod def _save_changes(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 metadata from @decorators.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 @decorators.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("@decorators.idempotent_id existence and uniqueness checks " "failed\n" "Run 'tox -v -e uuidgen' to automatically fix tests with\n" "missing @decorators.idempotent_id decorators.") if __name__ == '__main__': run() tempest-17.2.0/tempest/lib/cmd/__init__.py0000666000175100017510000000000013207044712020401 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/lib/auth.py0000666000175100017510000010147713207044712017064 0ustar zuulzuul00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # Copyright 2016 Rackspace 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 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__) def replace_version(url, new_version): parts = urlparse.urlparse(url) version_path = '/%s' % new_version path, subs = re.subn(r'(^|/)+v\d+(?:\.\d+)?', version_path, parts.path, count=1) if not subs: path = '%s%s' % (parts.path.rstrip('/'), version_path) url = urlparse.urlunparse((parts.scheme, parts.netloc, path, parts.params, parts.query, parts.fragment)) return url def apply_url_filters(url, filters): if filters.get('api_version', None) is not None: url = replace_version(url, filters['api_version']) parts = urlparse.urlparse(url) if filters.get('skip_path', None) is not None and parts.path != '': url = urlparse.urlunparse((parts.scheme, parts.netloc, '/', parts.params, parts.query, parts.fragment)) return url @six.add_metaclass(abc.ABCMeta) class AuthProvider(object): """Provide authentication""" SCOPES = set(['project']) def __init__(self, credentials, scope='project'): """Auth provider __init__ :param credentials: credentials for authentication :param scope: the default scope to be used by the credential providers when requesting a token. Valid values depend on the AuthProvider class implementation, and are defined in the set SCOPES. Default value is 'project'. """ 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._scope = None self.scope = scope 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): """Auth data for set scope""" return self.get_auth() @property def scope(self): """Scope used in auth requests""" return self._scope @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 :return: 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 @scope.setter def scope(self, value): """Set the scope to be used in token requests :param scope: scope to be used. If the scope is different, clear caches """ if value not in self.SCOPES: raise exceptions.InvalidScope( scope=value, auth_provider=self.__class__.__name__) if value != self.scope: self.clear_auth() self._scope = value 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, scope='project', http_timeout=None, proxy_url=None): super(KeystoneAuthProvider, self).__init__(credentials, scope) self.dscv = disable_ssl_certificate_validation self.ca_certs = ca_certs self.trace_requests = trace_requests self.http_timeout = http_timeout self.proxy_url = proxy_url self.auth_url = auth_url 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.get_auth() 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): """Auth parameters to be passed to the token request By default all fields available in Credentials are passed to the token request. Scope may affect this. """ 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.get_auth()[0] class KeystoneV2AuthProvider(KeystoneAuthProvider): """Provides authentication based on the Identity V2 API The Keystone Identity V2 API defines both unscoped and project scoped tokens. This auth provider only implements 'project'. """ SCOPES = set(['project']) def _auth_client(self, auth_url): return json_v2id.TokenClient( auth_url, disable_ssl_certificate_validation=self.dscv, ca_certs=self.ca_certs, trace_requests=self.trace_requests, http_timeout=self.http_timeout, proxy_url=self.proxy_url) def _auth_params(self): """Auth parameters to be passed to the token request All fields available in Credentials are passed to the token request. """ 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 :param filters: Used to filter results Filters can be: - service: service type name such as compute, image, etc. - region: service region name - name: service name, only if service exists - endpoint_type: type of endpoint such as adminURL, publicURL, internalURL - api_version: the version of api used to replace catalog version - skip_path: skips the suffix path of the url and uses base URL :rtype: string :return: url with filters applied """ if auth_data is None: auth_data = self.get_auth() token, _auth_data = auth_data service = filters.get('service') region = filters.get('region') name = filters.get('name') 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: if name is not None and ep["name"] != name: continue 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 or name 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, name: %s" % (service, region, endpoint_type, name)) return apply_url_filters(_base_url, filters) 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): """Provides authentication based on the Identity V3 API""" SCOPES = set(['project', 'domain', 'unscoped', None]) def _auth_client(self, auth_url): return json_v3id.V3TokenClient( auth_url, disable_ssl_certificate_validation=self.dscv, ca_certs=self.ca_certs, trace_requests=self.trace_requests, http_timeout=self.http_timeout, proxy_url=self.proxy_url) def _auth_params(self): """Auth parameters to be passed to the token request Fields available in Credentials are passed to the token request, depending on the value of scope. Valid values for scope are: "project", "domain". Any other string (e.g. "unscoped") or None will lead to an unscoped token request. """ auth_params = dict( user_id=self.credentials.user_id, username=self.credentials.username, user_domain_id=self.credentials.user_domain_id, user_domain_name=self.credentials.user_domain_name, password=self.credentials.password, auth_data=True) if self.scope == 'project': auth_params.update( project_domain_id=self.credentials.project_domain_id, project_domain_name=self.credentials.project_domain_name, project_id=self.credentials.project_id, project_name=self.credentials.project_name) if self.scope == 'domain': auth_params.update( domain_id=self.credentials.domain_id, domain_name=self.credentials.domain_name) return auth_params 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 If scope is not 'project', it may be that there is not catalog in the auth_data. In such case, as long as the requested service is 'identity', we can use the original auth URL to build the base_url. :param filters: Used to filter results Filters can be: - service: service type name such as compute, image, etc. - region: service region name - name: service name, only if service exists - endpoint_type: type of endpoint such as adminURL, publicURL, internalURL - api_version: the version of api used to replace catalog version - skip_path: skips the suffix path of the url and uses base URL :rtype: string :return: url with filters applied """ if auth_data is None: auth_data = self.get_auth() token, _auth_data = auth_data service = filters.get('service') region = filters.get('region') name = filters.get('name') 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.get('catalog', []) # Select entries with matching service type service_catalog = [ep for ep in catalog if ep['type'] == service] if service_catalog: if name is not None: service_catalog = ( [ep for ep in service_catalog if ep['name'] == name]) if service_catalog: service_catalog = service_catalog[0]['endpoints'] else: raise exceptions.EndpointNotFound(name) else: service_catalog = service_catalog[0]['endpoints'] else: if not catalog and service == 'identity': # NOTE(andreaf) If there's no catalog at all and the service # is identity, it's a valid use case. Having a non-empty # catalog with no identity in it is not valid instead. msg = ('Got an empty catalog. Scope: %s. ' 'Falling back to configured URL for %s: %s') LOG.debug(msg, self.scope, service, self.auth_url) return apply_url_filters(self.auth_url, filters) else: # No matching service msg = ('No matching service found in the catalog.\n' 'Scope: %s, Credentials: %s\n' 'Auth data: %s\n' 'Service: %s, Region: %s, endpoint_type: %s\n' 'Catalog: %s') raise exceptions.EndpointNotFound(msg % ( self.scope, self.credentials, _auth_data, service, region, endpoint_type, catalog)) # Filter by endpoint type (interface) filtered_catalog = [ep for ep in service_catalog if ep['interface'] == endpoint_type] if not filtered_catalog: # 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 not filtered_catalog: # No matching region (or name), 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) return apply_url_filters(_base_url, filters) 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, http_timeout=None, proxy_url=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 http_timeout: timeout in seconds to wait for the http request to return :param proxy_url: URL of HTTP(s) proxy used when fill_in is True :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: dscv = disable_ssl_certificate_validation auth_provider = auth_provider_class( creds, auth_url, disable_ssl_certificate_validation=dscv, ca_certs=ca_certs, trace_requests=trace_requests, http_timeout=http_timeout, proxy_url=proxy_url) 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 = [] COLLISIONS = [] 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 (key1, key2) in self.COLLISIONS: val1 = attr.get(key1) val2 = attr.get(key2) if val1 and val2 and val1 != val2: msg = ('Cannot have conflicting values for %s and %s' % (key1, key2)) raise exceptions.InvalidCredentials(msg) for key in attr: 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 __ne__(self, other): """Contrary to the __eq__""" return not self.__eq__(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', 'project_id', 'project_name'] COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')] 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 __setattr__(self, key, value): # NOTE(andreaf) In order to ease the migration towards 'project' we # support v2 credentials configured with 'project' and translate it # to tenant on the fly. The original kwargs are stored for clients # that may rely on them. We also set project when tenant is defined # so clients can rely on project being part of credentials. parent = super(KeystoneV2Credentials, self) # for project_* set tenant only if key == 'project_id': parent.__setattr__('tenant_id', value) elif key == 'project_name': parent.__setattr__('tenant_name', value) if key == 'tenant_id': parent.__setattr__('project_id', value) elif key == 'tenant_name': parent.__setattr__('project_name', value) # trigger default behaviour for all attributes parent.__setattr__(key, value) 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'] COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_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': if self.user_domain_name is None: parent.__setattr__('user_domain_name', value) if self.project_domain_name is None: 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) - 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)} tempest-17.2.0/tempest/version.py0000666000175100017510000000126313207044712017032 0ustar zuulzuul00000000000000# 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') tempest-17.2.0/tempest/hacking/0000775000175100017510000000000013207045130016366 5ustar zuulzuul00000000000000tempest-17.2.0/tempest/hacking/ignored_list_T110.txt0000666000175100017510000000006313207044712022324 0ustar zuulzuul00000000000000./tempest/services/object_storage/object_client.py tempest-17.2.0/tempest/hacking/__init__.py0000666000175100017510000000000013207044712020474 0ustar zuulzuul00000000000000tempest-17.2.0/tempest/hacking/checks.py0000666000175100017510000002463013207044712020214 0ustar zuulzuul00000000000000# 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 os import re import pep8 PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron', 'ironic', 'heat', 'sahara'] PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS)) TEST_DEFINITION = re.compile(r'^\s*def test.*') SETUP_TEARDOWN_CLASS_DEFINITION = re.compile(r'^\s+def (setUp|tearDown)Class') SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)') VI_HEADER_RE = re.compile(r"^#\s+vim?:.+") RAND_NAME_HYPHEN_RE = re.compile(r".*rand_name\(.+[\-\_][\"\']\)") mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])") TESTTOOLS_SKIP_DECORATOR = re.compile(r'\s*@testtools\.skip\((.*)\)') METHOD = re.compile(r"^ def .+") METHOD_GET_RESOURCE = re.compile(r"^\s*def (list|show)\_.+") METHOD_DELETE_RESOURCE = re.compile(r"^\s*def delete_.+") CLASS = re.compile(r"^class .+") EX_ATTRIBUTE = re.compile(r'(\s+|\()(e|ex|exc|exception).message(\s+|\))') def import_no_clients_in_api_and_scenario_tests(physical_line, filename): """Check for client imports from tempest/api & tempest/scenario tests T102: Cannot import OpenStack python clients """ if "tempest/api" in filename or "tempest/scenario" in filename: res = PYTHON_CLIENT_RE.match(physical_line) if res: return (physical_line.find(res.group(1)), ("T102: python clients import not allowed" " in tempest/api/* or tempest/scenario/* tests")) def scenario_tests_need_service_tags(physical_line, filename, previous_logical): """Check that scenario tests have service tags T104: Scenario tests require a services decorator """ if 'tempest/scenario/' in filename and '/test_' in filename: if TEST_DEFINITION.match(physical_line): if not SCENARIO_DECORATOR.match(previous_logical): return (physical_line.find('def'), "T104: Scenario tests require a service decorator") def no_setup_teardown_class_for_tests(physical_line, filename): if pep8.noqa(physical_line): return 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): """Check for vi editor configuration in source files. By default vi modelines can only appear in the first or last 5 lines of a source file. T106 """ # NOTE(gilliard): line_number is 1-indexed if line_number <= 5 or line_number > len(lines) - 5: if VI_HEADER_RE.match(physical_line): return 0, "T106: Don't put vi configuration in source files" def service_tags_not_in_module_path(physical_line, filename): """Check that a service tag isn't in the module path A service tag should only be added if the service name isn't already in the module path. T107 """ # NOTE(mtreinish) Scenario tests always need service tags, but subdirs are # created for services like heat which would cause false negatives for # those tests, so just exclude the scenario tests. if 'tempest/scenario' not in filename: matches = SCENARIO_DECORATOR.match(physical_line) if matches: services = matches.group(1).split(',') for service in services: service_name = service.strip().strip("'") modulepath = os.path.split(filename)[0] if service_name in modulepath: return (physical_line.find(service_name), "T107: service tag should not be in path") def no_hyphen_at_end_of_rand_name(logical_line, filename): """Check no hyphen at the end of rand_name() argument T108 """ msg = "T108: hyphen should not be specified at the end of rand_name()" if RAND_NAME_HYPHEN_RE.match(logical_line): return 0, msg def no_mutable_default_args(logical_line): """Check that mutable object isn't used as default argument N322: Method's default argument shouldn't be mutable """ msg = "N322: Method's default argument shouldn't be mutable!" if mutable_default_args.match(logical_line): yield (0, msg) def no_testtools_skip_decorator(logical_line): """Check that methods do not have the testtools.skip decorator T109 """ if TESTTOOLS_SKIP_DECORATOR.match(logical_line): yield (0, "T109: Cannot use testtools.skip decorator; instead use " "decorators.skip_because from tempest.lib") def _common_service_clients_check(logical_line, physical_line, filename, ignored_list_file=None): if not re.match('tempest/(lib/)?services/.*', filename): return False if ignored_list_file is not None: ignored_list = [] with open('tempest/hacking/' + ignored_list_file) as f: for line in f: ignored_list.append(line.strip()) if filename in ignored_list: return False if not METHOD.match(physical_line): return False if pep8.noqa(physical_line): return False return True def get_resources_on_service_clients(logical_line, physical_line, filename, line_number, lines): """Check that service client names of GET should be consistent T110 """ if not _common_service_clients_check(logical_line, physical_line, filename, 'ignored_list_T110.txt'): return for line in lines[line_number:]: if METHOD.match(line) or CLASS.match(line): # the end of a method return if 'self.get(' not in line and ('self.show_resource(' not in line and 'self.list_resources(' not in line): continue if METHOD_GET_RESOURCE.match(logical_line): return msg = ("T110: [GET /resources] methods should be list_s" " or show_") yield (0, msg) def delete_resources_on_service_clients(logical_line, physical_line, filename, line_number, lines): """Check that service client names of DELETE should be consistent T111 """ if not _common_service_clients_check(logical_line, physical_line, filename, 'ignored_list_T111.txt'): return for line in lines[line_number:]: if METHOD.match(line) or CLASS.match(line): # the end of a method return if 'self.delete(' not in line and 'self.delete_resource(' not in line: continue if METHOD_DELETE_RESOURCE.match(logical_line): return msg = ("T111: [DELETE /resources/] methods should be " "delete_") yield (0, msg) def dont_import_local_tempest_into_lib(logical_line, filename): """Check that tempest.lib should not import local tempest code T112 """ if 'tempest/lib/' not in filename: return if not ('from tempest' in logical_line or 'import tempest' in logical_line): return if ('from tempest.lib' in logical_line or 'import tempest.lib' in logical_line): return msg = ("T112: tempest.lib should not import local tempest code to avoid " "circular dependency") yield (0, msg) def use_rand_uuid_instead_of_uuid4(logical_line, filename): """Check that tests use data_utils.rand_uuid() instead of uuid.uuid4() T113 """ if 'tempest/lib/' in filename: return if 'uuid.uuid4()' not in logical_line: return msg = ("T113: Tests should use data_utils.rand_uuid()/rand_uuid_hex() " "instead of uuid.uuid4()/uuid.uuid4().hex") yield (0, msg) def dont_use_config_in_tempest_lib(logical_line, filename): """Check that tempest.lib doesn't use tempest config T114 """ if 'tempest/lib/' not in filename: return if ('tempest.config' in logical_line or 'from tempest import config' in logical_line or 'oslo_config' in logical_line): msg = ('T114: tempest.lib can not have any dependency on tempest ' 'config.') yield(0, msg) def dont_put_admin_tests_on_nonadmin_path(logical_line, physical_line, filename): """Check admin tests should exist under admin path T115 """ if 'tempest/api/' not in filename: return if pep8.noqa(physical_line): return if not re.match('class .*Test.*\(.*Admin.*\):', logical_line): return if not re.match('.\/tempest\/api\/.*\/admin\/.*', filename): msg = 'T115: All admin tests should exist under admin path.' yield(0, msg) def unsupported_exception_attribute_PY3(logical_line): """Check Unsupported 'message' exception attribute in PY3 T116 """ result = EX_ATTRIBUTE.search(logical_line) msg = ("[T116] Unsupported 'message' Exception attribute in PY3") if result: yield(0, msg) def factory(register): register(import_no_clients_in_api_and_scenario_tests) register(scenario_tests_need_service_tags) register(no_setup_teardown_class_for_tests) register(no_vi_headers) register(service_tags_not_in_module_path) register(no_hyphen_at_end_of_rand_name) register(no_mutable_default_args) register(no_testtools_skip_decorator) register(get_resources_on_service_clients) register(delete_resources_on_service_clients) register(dont_import_local_tempest_into_lib) register(dont_use_config_in_tempest_lib) register(use_rand_uuid_instead_of_uuid4) register(dont_put_admin_tests_on_nonadmin_path) register(unsupported_exception_attribute_PY3) tempest-17.2.0/.stestr.conf0000666000175100017510000000010513207044712015555 0ustar zuulzuul00000000000000[DEFAULT] test_path=./tempest/test_discover group_regex=([^\.]*\.)* tempest-17.2.0/tempest.egg-info/0000775000175100017510000000000013207045130016454 5ustar zuulzuul00000000000000tempest-17.2.0/tempest.egg-info/SOURCES.txt0000664000175100017510000016030713207045130020347 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .testr.conf .zuul.yaml AUTHORS ChangeLog HACKING.rst LICENSE README.rst REVIEWING.rst bindep.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/HACKING.rst doc/source/REVIEWING.rst doc/source/account_generator.rst doc/source/cleanup.rst doc/source/conf.py doc/source/configuration.rst doc/source/index.rst doc/source/library.rst doc/source/microversion_testing.rst doc/source/overview.rst doc/source/plugin.rst doc/source/run.rst doc/source/sampleconf.rst doc/source/subunit_describe_calls.rst doc/source/test_removal.rst doc/source/workspace.rst doc/source/write_tests.rst doc/source/_extra/.htaccess doc/source/_static/.keep doc/source/data/tempest-plugins-registry.header doc/source/field_guide/api.rst doc/source/field_guide/index.rst doc/source/field_guide/scenario.rst doc/source/field_guide/unit_tests.rst doc/source/library/api_microversion_testing.rst doc/source/library/auth.rst doc/source/library/cli.rst doc/source/library/clients.rst doc/source/library/credential_providers.rst doc/source/library/decorators.rst doc/source/library/rest_client.rst doc/source/library/utils.rst doc/source/library/validation_resources.rst doc/source/library/service_clients/compute_clients.rst etc/accounts.yaml.sample etc/logging.conf.sample etc/whitelist.yaml playbooks/devstack-tempest.yaml playbooks/post-tempest.yaml releasenotes/notes/add-OAUTH-Token-Client-tempest-tests-6351eda451b95a86.yaml releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml releasenotes/notes/add-create-group-from-src-tempest-tests-9eb8b0b4b5c52055.yaml releasenotes/notes/add-domain-configuration-client-tempest-tests-e383efabdbb9ad03.yaml releasenotes/notes/add-domain-param-in-cliclient-a270fcf35c8f09e6.yaml releasenotes/notes/add-floating-ip-config-option-e5774bf77702ce9f.yaml releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-endpoint-groups-3518a90bbb731d0f.yaml releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml releasenotes/notes/add-ip-version-check-in-addresses-x491ac6d9abaxa12.yaml releasenotes/notes/add-is-resource-deleted-sg-client-f4a7a7a54ff024d7.yaml releasenotes/notes/add-kwargs-to-delete-vol-of-vol-client-1ecde75beb62933c.yaml releasenotes/notes/add-list-volume-transfers-with-detail-to-transfers-client-80169bf78cf4fa66.yaml releasenotes/notes/add-load-list-cmd-35a4a2e6ea0a36fd.yaml releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml releasenotes/notes/add-params-to-identity-v3-list-endpoints-958a155be4e17e5b.yaml releasenotes/notes/add-params-to-v2-list-backups-api-c088d2b4bfe90247.yaml releasenotes/notes/add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-248d41827daf2a0c.yaml releasenotes/notes/add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.yaml releasenotes/notes/add-return-value-to-retype-volume-a401aa619aaa2457.yaml releasenotes/notes/add-save-state-option-5ea67858cbaca969.yaml releasenotes/notes/add-server-diagnostics-validation-schema-b5a3c55b45aa718a.yaml releasenotes/notes/add-show-host-to-hosts-client-library-c60c4eb49d139480.yaml releasenotes/notes/add-show-snapshot-metadata-item-api-to-v2-snapshots-client-bd3cbab3c7f0e0b3.yaml releasenotes/notes/add-show-volume-image-metadata-api-to-v2-volumes-client-ee3c027f35276561.yaml releasenotes/notes/add-show-volume-metadata-item-api-to-v2-volumes-client-47d59ecd999ca9df.yaml releasenotes/notes/add-show-volume-summary-api-to-v3-volumes-client-96e7b01abdb5c9c3.yaml releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml releasenotes/notes/add-update-backup-api-to-v3-backups-client-e8465b2b66617dc0.yaml releasenotes/notes/add-update-group-tempest-tests-72f8ec19b2809849.yaml releasenotes/notes/add-validation-resources-to-lib-dc2600c4324ca4d7.yaml releasenotes/notes/add-volume-group-snapshots-tempest-tests-840df3da26590f5e.yaml releasenotes/notes/add-volume-group-types-tempest-tests-1298ab8cb4fe8b7b.yaml releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml releasenotes/notes/add_proxy_url_get_credentials-aef66b085450513f.yaml releasenotes/notes/api_v2_admin_flag-dea5ca9bc2ce63bc.yaml releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml releasenotes/notes/credentials-factory-stable-c8037bd9ae642482.yaml releasenotes/notes/deprecate-compute-images-client-in-volume-tests-92b6dd55fcaba620.yaml releasenotes/notes/deprecate-config-forbid_global_implied_dsr-e64cfa66e6e3ded5.yaml releasenotes/notes/deprecate-default-value-for-v3_endpoint_type-fb9e47c5ba1c719d.yaml releasenotes/notes/disable-identity-v2-testing-4ef1565d1a5aedcf.yaml releasenotes/notes/drop-DEFAULT_PARAMS-bfcc2e7b74ef880b.yaml releasenotes/notes/extra-compute-services-tests-92b6c0618972e02f.yaml releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml releasenotes/notes/fix-remoteclient-default-ssh-shell-prologue-33e99343d086f601.yaml releasenotes/notes/http_proxy_config-cb39b55520e84db5.yaml releasenotes/notes/identity-tests-domain-drivers-76235f6672221e45.yaml releasenotes/notes/identity-token-client-8aaef74b1d61090a.yaml releasenotes/notes/identity_client-635275d43abbb807.yaml releasenotes/notes/intermediate-pike-release-2ce492432ff8f012.yaml releasenotes/notes/intermediate-queens-release-2f9f305775fca454.yaml releasenotes/notes/list-auth-domains-v3-endpoint-9ec60c7d3011c397.yaml releasenotes/notes/make-object-storage-client-as-stable-interface-d1b07c7e8f17bef6.yaml releasenotes/notes/migrate-dynamic-creds-ecebb47528080761.yaml releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml releasenotes/notes/migrate-preprov-creds-ef61a046ee1ec604.yaml releasenotes/notes/move-attr-decorator-to-lib-a1e80c42ba9c5392.yaml releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml releasenotes/notes/move-volume-v3-base_client-to-volume-1edbz0f207c3b283.yaml releasenotes/notes/network-tag-client-f4614029af7927f0.yaml releasenotes/notes/pause_teardown-45c9d60ffa889f7f.yaml releasenotes/notes/plugin-client-registration-enhancements-e09131742391225b.yaml releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml releasenotes/notes/raise-exception-when-error-deleting-on-volume-18d0d0c5886212dd.yaml releasenotes/notes/remove-deprecated-apis-from-v2-volumes-client-3ca4a5db5fea518f.yaml releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml releasenotes/notes/remove-deprecated-volume-apis-from-v2-volumes-client-cf35e5b4cca89860.yaml releasenotes/notes/remove-get-ipv6-addr-by-EUI64-c79972d799c7a430.yaml releasenotes/notes/remove-heat-tests-9efb42cac3e0b306.yaml releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml releasenotes/notes/start-of-pike-support-f2a1b7ea8e8b0311.yaml releasenotes/notes/tempest-identity-catalog-client-f5c8589a9d7c1eb5.yaml releasenotes/notes/tempest-workspace-delete-directory-feature-74d6d157a5a05561.yaml releasenotes/notes/test-clients-stable-for-plugin-90b1e7dc83f28ccd.yaml releasenotes/notes/use-cinder-v3-client-for-verify_tempest_config-2bf3d817b0070064.yaml releasenotes/notes/10/10.0-supported-openstack-releases-b88db468695348f6.yaml releasenotes/notes/10/10.0.0-Tempest-library-interface-0eb680b810139a50.yaml releasenotes/notes/10/10.0.0-start-using-reno-ed9518126fd0e1a3.yaml releasenotes/notes/11/11.0.0-api-microversion-testing-support-2ceddd2255670932.yaml releasenotes/notes/11/11.0.0-compute-microversion-support-e0b23f960f894b9b.yaml releasenotes/notes/11/11.0.0-supported-openstack-releases-1e5d7295d939d439.yaml releasenotes/notes/12/12.0.0-supported-openstack-releases-f10aac381d933dd1.yaml releasenotes/notes/12/12.1.0-add-network-versions-client-d90e8334e1443f5c.yaml releasenotes/notes/12/12.1.0-add-scope-to-auth-b5a82493ea89f41e.yaml releasenotes/notes/12/12.1.0-add-tempest-run-3d0aaf69c2ca4115.yaml releasenotes/notes/12/12.1.0-add-tempest-workspaces-228a2ba4690b5589.yaml releasenotes/notes/12/12.1.0-add_subunit_describe_calls-5498a37e6cd66c4b.yaml releasenotes/notes/12/12.1.0-bug-1486834-7ebca15836ae27a9.yaml releasenotes/notes/12/12.1.0-identity-clients-as-library-e663c6132fcac6c2.yaml releasenotes/notes/12/12.1.0-image-clients-as-library-86d17caa26ce3961.yaml releasenotes/notes/12/12.1.0-new-test-utils-module-adf34468c4d52719.yaml releasenotes/notes/12/12.1.0-remove-input-scenarios-functionality-01308e6d4307f580.yaml releasenotes/notes/12/12.1.0-remove-integrated-horizon-bb57551c1e5f5be3.yaml releasenotes/notes/12/12.1.0-remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml releasenotes/notes/12/12.1.0-remove-trove-tests-666522e9113549f9.yaml releasenotes/notes/12/12.1.0-routers-client-as-library-25a363379da351f6.yaml releasenotes/notes/12/12.1.0-support-chunked-encoding-d71f53225f68edf3.yaml releasenotes/notes/12/12.1.0-tempest-init-global-config-dir-location-changes-12260255871d3a2b.yaml releasenotes/notes/12/12.2.0-add-httptimeout-in-restclient-ax78061900e3f3d7.yaml releasenotes/notes/12/12.2.0-add-new-identity-clients-3c3afd674a395bde.yaml releasenotes/notes/12/12.2.0-clients_module-16f3025f515bf9ec.yaml releasenotes/notes/12/12.2.0-nova_cert_default-90eb7c1e3cde624a.yaml releasenotes/notes/12/12.2.0-plugin-service-client-registration-00b19a2dd4935ba0.yaml releasenotes/notes/12/12.2.0-remove-javelin-276f62d04f7e4a1d.yaml releasenotes/notes/12/12.2.0-service_client_config-8a1d7b4de769c633.yaml releasenotes/notes/12/12.2.0-volume-clients-as-library-9a3444dd63c134b3.yaml releasenotes/notes/13/13.0.0-add-new-identity-clients-as-library-5f7ndha733nwdsn9.yaml releasenotes/notes/13/13.0.0-add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml releasenotes/notes/13/13.0.0-deprecate-get_ipv6_addr_by_EUI64-4673f07677289cf6.yaml releasenotes/notes/13/13.0.0-move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml releasenotes/notes/13/13.0.0-start-of-newton-support-3ebb274f300f28eb.yaml releasenotes/notes/13/13.0.0-tempest-cleanup-nostandalone-39df2aafb2545d35.yaml releasenotes/notes/13/13.0.0-volume-clients-as-library-660811011be29d1a.yaml releasenotes/notes/14/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml releasenotes/notes/14/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml releasenotes/notes/14/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml releasenotes/notes/14/14.0.0-add-image-clients-af94564fb34ddca6.yaml releasenotes/notes/14/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml releasenotes/notes/14/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml releasenotes/notes/14/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml releasenotes/notes/14/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml releasenotes/notes/14/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml releasenotes/notes/14/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml releasenotes/notes/14/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml releasenotes/notes/14/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml releasenotes/notes/14/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml releasenotes/notes/14/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml releasenotes/notes/14/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml releasenotes/notes/14/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml releasenotes/notes/15/15.0.0-add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml releasenotes/notes/15/15.0.0-add-image-clients-tests-49dbc0a0a4281a77.yaml releasenotes/notes/15/15.0.0-add-implied-roles-to-roles-client-library-edf96408ad9ba82e.yaml releasenotes/notes/15/15.0.0-add-snapshot-manage-client-as-library-a76ffdba9d8d01cb.yaml releasenotes/notes/15/15.0.0-deprecate-allow_port_security_disabled-option-2d3d87f6bd11d03a.yaml releasenotes/notes/15/15.0.0-deprecate-identity-feature-enabled.reseller-84800a8232fe217f.yaml releasenotes/notes/15/15.0.0-deprecate-volume_feature_enabled.volume_services-dbe024ea067d5ab2.yaml releasenotes/notes/15/15.0.0-jsonschema-validator-2377ba131e12d3c7.yaml releasenotes/notes/15/15.0.0-remove-deprecated-compute-microversion-config-options-eaee6a7d2f8390a8.yaml releasenotes/notes/15/15.0.0-remove-deprecated-compute-validation-config-options-e3d1b89ce074d71c.yaml releasenotes/notes/15/15.0.0-remove-deprecated-input-scenario-config-options-414e0c5442e967e9.yaml releasenotes/notes/15/15.0.0-remove-deprecated-network-config-options-f9ce276231578fe6.yaml releasenotes/notes/16/16.0.0-add-OAUTH-Consumer-Client-tempest-tests-db1df7aae4a9fd4e.yaml releasenotes/notes/16/16.0.0-add-additional-methods-to-roles-client-library-178d4a6000dec72d.yaml releasenotes/notes/16/16.0.0-add-cascade-parameter-to-volumes-client-ff4f7f12795003a4.yaml releasenotes/notes/16/16.0.0-add-compute-server-evaculate-client-as-a-library-ed76baf25f02c3ca.yaml releasenotes/notes/16/16.0.0-add-content-type-without-spaces-b2c9b91b257814f3.yaml releasenotes/notes/16/16.0.0-add-list-auth-project-client-5905076d914a3943.yaml releasenotes/notes/16/16.0.0-add-list-glance-api-versions-ec5fc8081fc8a0ae.yaml releasenotes/notes/16/16.0.0-add-list-security-groups-by-servers-to-servers-client-library-088df48f6d81f4be.yaml releasenotes/notes/16/16.0.0-add-list-version-to-identity-client-944cb7396088a575.yaml releasenotes/notes/16/16.0.0-add-list-version-to-volume-client-4769dd1bd4ab9c5e.yaml releasenotes/notes/16/16.0.0-add-quota-sets-detail-kwarg-74b72183295b3ce7.yaml releasenotes/notes/16/16.0.0-add-tempest-lib-remote-client-adbeb3f42a36910b.yaml releasenotes/notes/16/16.0.0-add-tempest-run-combine-option-e94c1049ba8985d5.yaml releasenotes/notes/16/16.0.0-add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml releasenotes/notes/16/16.0.0-add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml releasenotes/notes/16/16.0.0-create-server-tags-client-8c0042a77e859af6.yaml releasenotes/notes/16/16.0.0-deprecate-deactivate_image-config-7a282c471937bbcb.yaml releasenotes/notes/16/16.0.0-deprecate-dvr_extra_resources-config-8c319d6dab7f7e5c.yaml releasenotes/notes/16/16.0.0-deprecate-glance-api-version-config-options-8370b63aea8e14cf.yaml releasenotes/notes/16/16.0.0-deprecate-resources-prefix-option-ad490c0a30a0266b.yaml releasenotes/notes/16/16.0.0-deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml releasenotes/notes/16/16.0.0-deprecate-skip_unless_config-decorator-64c32d588043ab12.yaml releasenotes/notes/16/16.0.0-deprecated-cinder-api-v1-option-df7d5a54d93db5cf.yaml releasenotes/notes/16/16.0.0-dreprecate_client_parameters-cb8d069e62957f7e.yaml releasenotes/notes/16/16.0.0-fix-volume-v2-service-clients-bugfix-1667354-73d2c3c8fedc08bf.yaml releasenotes/notes/16/16.0.0-mitaka-eol-88ff8355fff81b55.yaml releasenotes/notes/16/16.0.0-remove-call_until_true-of-test-de9c13bc8f969921.yaml releasenotes/notes/16/16.0.0-remove-cinder-v1-api-tests-71e266b8d55d475f.yaml releasenotes/notes/16/16.0.0-remove-deprecated-allow_port_security_disabled-option-d0ffaeb2e7817707.yaml releasenotes/notes/16/16.0.0-remove-deprecated-compute-validation-config-options-part-2-5cd17b6e0e6cb8a3.yaml releasenotes/notes/16/16.0.0-remove-deprecated-dvr_extra_resources-option-e8c441c38eab7ddd.yaml releasenotes/notes/16/16.0.0-remove-deprecated-identity-reseller-option-4411c7e3951f1094.yaml releasenotes/notes/16/16.0.0-remove-sahara-service-available-44a642aa9c634ab4.yaml releasenotes/notes/16/16.0.0-remove-volume_feature_enabled.volume_services-c6aa142cc1021297.yaml releasenotes/notes/16/16.0.0-use-keystone-v3-api-935860d30ddbb8e9.yaml releasenotes/notes/16/16.0.0-volume-transfers-client-e5ed3f5464c0cdc0.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/unreleased.rst releasenotes/source/v10.0.0.rst releasenotes/source/v11.0.0.rst releasenotes/source/v12.0.0.rst releasenotes/source/v13.0.0.rst releasenotes/source/v14.0.0.rst releasenotes/source/v15.0.0.rst releasenotes/source/v16.0.0.rst releasenotes/source/v16.1.0.rst releasenotes/source/v17.0.0.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder roles/acl-devstack-files/README.rst roles/acl-devstack-files/defaults/main.yaml roles/acl-devstack-files/tasks/main.yaml roles/process-stackviz/README.rst roles/process-stackviz/defaults/main.yaml roles/process-stackviz/tasks/main.yaml roles/run-tempest/README.rst roles/run-tempest/defaults/main.yaml roles/run-tempest/tasks/main.yaml roles/setup-tempest-data-dir/README.rst roles/setup-tempest-data-dir/defaults/main.yaml roles/setup-tempest-data-dir/tasks/main.yaml roles/setup-tempest-run-dir/README.rst roles/setup-tempest-run-dir/defaults/main.yaml roles/setup-tempest-run-dir/tasks/main.yaml tempest/README.rst tempest/__init__.py tempest/clients.py tempest/config.py tempest/exceptions.py tempest/manager.py tempest/test.py tempest/version.py tempest.egg-info/PKG-INFO tempest.egg-info/SOURCES.txt tempest.egg-info/dependency_links.txt tempest.egg-info/entry_points.txt tempest.egg-info/not-zip-safe tempest.egg-info/pbr.json tempest.egg-info/requires.txt tempest.egg-info/top_level.txt tempest/api/README.rst tempest/api/__init__.py tempest/api/compute/__init__.py tempest/api/compute/api_microversion_fixture.py tempest/api/compute/base.py tempest/api/compute/test_extensions.py tempest/api/compute/test_networks.py tempest/api/compute/test_quotas.py tempest/api/compute/test_tenant_networks.py tempest/api/compute/test_versions.py tempest/api/compute/admin/__init__.py tempest/api/compute/admin/test_agents.py tempest/api/compute/admin/test_aggregates.py tempest/api/compute/admin/test_aggregates_negative.py tempest/api/compute/admin/test_auto_allocate_network.py tempest/api/compute/admin/test_availability_zone.py tempest/api/compute/admin/test_availability_zone_negative.py tempest/api/compute/admin/test_create_server.py tempest/api/compute/admin/test_delete_server.py tempest/api/compute/admin/test_fixed_ips.py tempest/api/compute/admin/test_fixed_ips_negative.py tempest/api/compute/admin/test_flavors.py tempest/api/compute/admin/test_flavors_access.py tempest/api/compute/admin/test_flavors_access_negative.py tempest/api/compute/admin/test_flavors_extra_specs.py tempest/api/compute/admin/test_flavors_extra_specs_negative.py tempest/api/compute/admin/test_floating_ips_bulk.py tempest/api/compute/admin/test_hosts.py tempest/api/compute/admin/test_hosts_negative.py tempest/api/compute/admin/test_hypervisor.py tempest/api/compute/admin/test_hypervisor_negative.py tempest/api/compute/admin/test_instance_usage_audit_log.py tempest/api/compute/admin/test_instance_usage_audit_log_negative.py tempest/api/compute/admin/test_keypairs_v210.py tempest/api/compute/admin/test_live_migration.py tempest/api/compute/admin/test_live_migration_negative.py tempest/api/compute/admin/test_migrations.py tempest/api/compute/admin/test_networks.py tempest/api/compute/admin/test_quotas.py tempest/api/compute/admin/test_quotas_negative.py tempest/api/compute/admin/test_security_group_default_rules.py tempest/api/compute/admin/test_security_groups.py tempest/api/compute/admin/test_server_diagnostics.py tempest/api/compute/admin/test_server_diagnostics_negative.py tempest/api/compute/admin/test_servers.py tempest/api/compute/admin/test_servers_negative.py tempest/api/compute/admin/test_servers_on_multinodes.py tempest/api/compute/admin/test_services.py tempest/api/compute/admin/test_services_negative.py tempest/api/compute/admin/test_simple_tenant_usage.py tempest/api/compute/admin/test_simple_tenant_usage_negative.py tempest/api/compute/admin/test_volume_swap.py tempest/api/compute/admin/test_volumes_negative.py tempest/api/compute/certificates/__init__.py tempest/api/compute/certificates/test_certificates.py tempest/api/compute/flavors/__init__.py tempest/api/compute/flavors/test_flavors.py tempest/api/compute/flavors/test_flavors_negative.py tempest/api/compute/floating_ips/__init__.py tempest/api/compute/floating_ips/base.py tempest/api/compute/floating_ips/test_floating_ips_actions.py tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py tempest/api/compute/floating_ips/test_list_floating_ips.py tempest/api/compute/floating_ips/test_list_floating_ips_negative.py tempest/api/compute/images/__init__.py tempest/api/compute/images/test_image_metadata.py tempest/api/compute/images/test_image_metadata_negative.py tempest/api/compute/images/test_images.py tempest/api/compute/images/test_images_negative.py tempest/api/compute/images/test_images_oneserver.py tempest/api/compute/images/test_images_oneserver_negative.py tempest/api/compute/images/test_list_image_filters.py tempest/api/compute/images/test_list_image_filters_negative.py tempest/api/compute/images/test_list_images.py tempest/api/compute/keypairs/__init__.py tempest/api/compute/keypairs/base.py tempest/api/compute/keypairs/test_keypairs.py tempest/api/compute/keypairs/test_keypairs_negative.py tempest/api/compute/keypairs/test_keypairs_v22.py tempest/api/compute/limits/__init__.py tempest/api/compute/limits/test_absolute_limits.py tempest/api/compute/limits/test_absolute_limits_negative.py tempest/api/compute/security_groups/__init__.py tempest/api/compute/security_groups/base.py tempest/api/compute/security_groups/test_security_group_rules.py tempest/api/compute/security_groups/test_security_group_rules_negative.py tempest/api/compute/security_groups/test_security_groups.py tempest/api/compute/security_groups/test_security_groups_negative.py tempest/api/compute/servers/__init__.py tempest/api/compute/servers/test_attach_interfaces.py tempest/api/compute/servers/test_availability_zone.py tempest/api/compute/servers/test_create_server.py tempest/api/compute/servers/test_create_server_multi_nic.py tempest/api/compute/servers/test_delete_server.py tempest/api/compute/servers/test_device_tagging.py tempest/api/compute/servers/test_disk_config.py tempest/api/compute/servers/test_instance_actions.py tempest/api/compute/servers/test_instance_actions_negative.py tempest/api/compute/servers/test_list_server_filters.py tempest/api/compute/servers/test_list_servers_negative.py tempest/api/compute/servers/test_multiple_create.py tempest/api/compute/servers/test_multiple_create_negative.py tempest/api/compute/servers/test_novnc.py tempest/api/compute/servers/test_server_actions.py tempest/api/compute/servers/test_server_addresses.py tempest/api/compute/servers/test_server_addresses_negative.py tempest/api/compute/servers/test_server_group.py tempest/api/compute/servers/test_server_metadata.py tempest/api/compute/servers/test_server_metadata_negative.py tempest/api/compute/servers/test_server_password.py tempest/api/compute/servers/test_server_personality.py tempest/api/compute/servers/test_server_rescue.py tempest/api/compute/servers/test_server_rescue_negative.py tempest/api/compute/servers/test_server_tags.py tempest/api/compute/servers/test_servers.py tempest/api/compute/servers/test_servers_negative.py tempest/api/compute/servers/test_virtual_interfaces.py tempest/api/compute/servers/test_virtual_interfaces_negative.py tempest/api/compute/volumes/__init__.py tempest/api/compute/volumes/test_attach_volume.py tempest/api/compute/volumes/test_attach_volume_negative.py tempest/api/compute/volumes/test_volume_snapshots.py tempest/api/compute/volumes/test_volumes_get.py tempest/api/compute/volumes/test_volumes_list.py tempest/api/compute/volumes/test_volumes_negative.py tempest/api/identity/__init__.py tempest/api/identity/base.py tempest/api/identity/admin/__init__.py tempest/api/identity/admin/v2/__init__.py tempest/api/identity/admin/v2/test_endpoints.py tempest/api/identity/admin/v2/test_roles.py tempest/api/identity/admin/v2/test_roles_negative.py tempest/api/identity/admin/v2/test_services.py tempest/api/identity/admin/v2/test_tenant_negative.py tempest/api/identity/admin/v2/test_tenants.py tempest/api/identity/admin/v2/test_tokens.py tempest/api/identity/admin/v2/test_tokens_negative.py tempest/api/identity/admin/v2/test_users.py tempest/api/identity/admin/v2/test_users_negative.py tempest/api/identity/admin/v3/__init__.py tempest/api/identity/admin/v3/test_credentials.py tempest/api/identity/admin/v3/test_default_project_id.py tempest/api/identity/admin/v3/test_domain_configuration.py tempest/api/identity/admin/v3/test_domains.py tempest/api/identity/admin/v3/test_domains_negative.py tempest/api/identity/admin/v3/test_endpoint_groups.py tempest/api/identity/admin/v3/test_endpoints.py tempest/api/identity/admin/v3/test_endpoints_negative.py tempest/api/identity/admin/v3/test_groups.py tempest/api/identity/admin/v3/test_inherits.py tempest/api/identity/admin/v3/test_list_projects.py tempest/api/identity/admin/v3/test_list_users.py tempest/api/identity/admin/v3/test_oauth_consumers.py tempest/api/identity/admin/v3/test_policies.py tempest/api/identity/admin/v3/test_projects.py tempest/api/identity/admin/v3/test_projects_negative.py tempest/api/identity/admin/v3/test_regions.py tempest/api/identity/admin/v3/test_roles.py tempest/api/identity/admin/v3/test_services.py tempest/api/identity/admin/v3/test_tokens.py tempest/api/identity/admin/v3/test_trusts.py tempest/api/identity/admin/v3/test_users.py tempest/api/identity/admin/v3/test_users_negative.py tempest/api/identity/v2/__init__.py tempest/api/identity/v2/test_api_discovery.py tempest/api/identity/v2/test_ec2_credentials.py tempest/api/identity/v2/test_extension.py tempest/api/identity/v2/test_tenants.py tempest/api/identity/v2/test_tokens.py tempest/api/identity/v2/test_users.py tempest/api/identity/v3/__init__.py tempest/api/identity/v3/test_api_discovery.py tempest/api/identity/v3/test_catalog.py tempest/api/identity/v3/test_projects.py tempest/api/identity/v3/test_tokens.py tempest/api/identity/v3/test_users.py tempest/api/image/__init__.py tempest/api/image/base.py tempest/api/image/v1/__init__.py tempest/api/image/v1/test_image_members.py tempest/api/image/v1/test_image_members_negative.py tempest/api/image/v1/test_images.py tempest/api/image/v1/test_images_negative.py tempest/api/image/v2/__init__.py tempest/api/image/v2/test_images.py tempest/api/image/v2/test_images_member.py tempest/api/image/v2/test_images_member_negative.py tempest/api/image/v2/test_images_metadefs_namespace_objects.py tempest/api/image/v2/test_images_metadefs_namespace_properties.py tempest/api/image/v2/test_images_metadefs_namespace_tags.py tempest/api/image/v2/test_images_metadefs_namespaces.py tempest/api/image/v2/test_images_metadefs_resource_types.py tempest/api/image/v2/test_images_metadefs_schema.py tempest/api/image/v2/test_images_negative.py tempest/api/image/v2/test_images_tags.py tempest/api/image/v2/test_images_tags_negative.py tempest/api/image/v2/test_versions.py tempest/api/network/__init__.py tempest/api/network/base.py tempest/api/network/base_security_groups.py tempest/api/network/test_allowed_address_pair.py tempest/api/network/test_dhcp_ipv6.py tempest/api/network/test_extensions.py tempest/api/network/test_extra_dhcp_options.py tempest/api/network/test_floating_ips.py tempest/api/network/test_floating_ips_negative.py tempest/api/network/test_networks.py tempest/api/network/test_networks_negative.py tempest/api/network/test_ports.py tempest/api/network/test_routers.py tempest/api/network/test_routers_negative.py tempest/api/network/test_security_groups.py tempest/api/network/test_security_groups_negative.py tempest/api/network/test_service_providers.py tempest/api/network/test_subnetpools_extensions.py tempest/api/network/test_tags.py tempest/api/network/test_versions.py tempest/api/network/admin/__init__.py tempest/api/network/admin/test_agent_management.py tempest/api/network/admin/test_dhcp_agent_scheduler.py tempest/api/network/admin/test_external_network_extension.py tempest/api/network/admin/test_external_networks_negative.py tempest/api/network/admin/test_floating_ips_admin_actions.py tempest/api/network/admin/test_l3_agent_scheduler.py tempest/api/network/admin/test_metering_extensions.py tempest/api/network/admin/test_negative_quotas.py tempest/api/network/admin/test_ports.py tempest/api/network/admin/test_quotas.py tempest/api/network/admin/test_routers.py tempest/api/network/admin/test_routers_dvr.py tempest/api/network/admin/test_routers_negative.py tempest/api/object_storage/__init__.py tempest/api/object_storage/base.py tempest/api/object_storage/test_account_bulk.py tempest/api/object_storage/test_account_quotas.py tempest/api/object_storage/test_account_quotas_negative.py tempest/api/object_storage/test_account_services.py tempest/api/object_storage/test_account_services_negative.py tempest/api/object_storage/test_container_acl.py tempest/api/object_storage/test_container_acl_negative.py tempest/api/object_storage/test_container_quotas.py tempest/api/object_storage/test_container_services.py tempest/api/object_storage/test_container_services_negative.py tempest/api/object_storage/test_container_staticweb.py tempest/api/object_storage/test_container_sync.py tempest/api/object_storage/test_container_sync_middleware.py tempest/api/object_storage/test_crossdomain.py tempest/api/object_storage/test_healthcheck.py tempest/api/object_storage/test_object_expiry.py tempest/api/object_storage/test_object_formpost.py tempest/api/object_storage/test_object_formpost_negative.py tempest/api/object_storage/test_object_services.py tempest/api/object_storage/test_object_slo.py tempest/api/object_storage/test_object_temp_url.py tempest/api/object_storage/test_object_temp_url_negative.py tempest/api/object_storage/test_object_version.py tempest/api/volume/__init__.py tempest/api/volume/api_microversion_fixture.py tempest/api/volume/base.py tempest/api/volume/test_availability_zone.py tempest/api/volume/test_extensions.py tempest/api/volume/test_image_metadata.py tempest/api/volume/test_snapshot_metadata.py tempest/api/volume/test_versions.py tempest/api/volume/test_volume_absolute_limits.py tempest/api/volume/test_volume_delete_cascade.py tempest/api/volume/test_volume_metadata.py tempest/api/volume/test_volume_transfers.py tempest/api/volume/test_volumes_actions.py tempest/api/volume/test_volumes_backup.py tempest/api/volume/test_volumes_clone.py tempest/api/volume/test_volumes_clone_negative.py tempest/api/volume/test_volumes_extend.py tempest/api/volume/test_volumes_get.py tempest/api/volume/test_volumes_list.py tempest/api/volume/test_volumes_negative.py tempest/api/volume/test_volumes_snapshots.py tempest/api/volume/test_volumes_snapshots_list.py tempest/api/volume/test_volumes_snapshots_negative.py tempest/api/volume/admin/__init__.py tempest/api/volume/admin/test_backends_capabilities.py tempest/api/volume/admin/test_group_types.py tempest/api/volume/admin/test_groups.py tempest/api/volume/admin/test_multi_backend.py tempest/api/volume/admin/test_qos.py tempest/api/volume/admin/test_snapshot_manage.py tempest/api/volume/admin/test_snapshots_actions.py tempest/api/volume/admin/test_user_messages.py tempest/api/volume/admin/test_volume_hosts.py tempest/api/volume/admin/test_volume_manage.py tempest/api/volume/admin/test_volume_pools.py tempest/api/volume/admin/test_volume_quota_classes.py tempest/api/volume/admin/test_volume_quotas.py tempest/api/volume/admin/test_volume_quotas_negative.py tempest/api/volume/admin/test_volume_retype_with_migration.py tempest/api/volume/admin/test_volume_services.py tempest/api/volume/admin/test_volume_snapshot_quotas_negative.py tempest/api/volume/admin/test_volume_type_access.py tempest/api/volume/admin/test_volume_types.py tempest/api/volume/admin/test_volume_types_extra_specs.py tempest/api/volume/admin/test_volume_types_extra_specs_negative.py tempest/api/volume/admin/test_volume_types_negative.py tempest/api/volume/admin/test_volumes_actions.py tempest/api/volume/admin/test_volumes_backup.py tempest/api/volume/admin/test_volumes_list.py tempest/cmd/__init__.py tempest/cmd/account_generator.py tempest/cmd/cleanup.py tempest/cmd/cleanup_service.py tempest/cmd/config-generator.tempest.conf tempest/cmd/init.py tempest/cmd/list_plugins.py tempest/cmd/main.py tempest/cmd/run.py tempest/cmd/subunit_describe_calls.py tempest/cmd/verify_tempest_config.py tempest/cmd/workspace.py tempest/common/__init__.py tempest/common/compute.py tempest/common/credentials_factory.py tempest/common/custom_matchers.py tempest/common/identity.py tempest/common/image.py tempest/common/tempest_fixtures.py tempest/common/waiters.py tempest/common/utils/__init__.py tempest/common/utils/net_info.py tempest/common/utils/net_utils.py tempest/common/utils/linux/__init__.py tempest/common/utils/linux/remote_client.py tempest/hacking/__init__.py tempest/hacking/checks.py tempest/hacking/ignored_list_T110.txt tempest/lib/__init__.py tempest/lib/auth.py tempest/lib/base.py tempest/lib/decorators.py tempest/lib/exceptions.py tempest/lib/api_schema/__init__.py tempest/lib/api_schema/response/__init__.py tempest/lib/api_schema/response/compute/__init__.py tempest/lib/api_schema/response/compute/v2_1/__init__.py tempest/lib/api_schema/response/compute/v2_1/agents.py tempest/lib/api_schema/response/compute/v2_1/aggregates.py tempest/lib/api_schema/response/compute/v2_1/availability_zone.py tempest/lib/api_schema/response/compute/v2_1/baremetal_nodes.py tempest/lib/api_schema/response/compute/v2_1/certificates.py tempest/lib/api_schema/response/compute/v2_1/extensions.py tempest/lib/api_schema/response/compute/v2_1/fixed_ips.py tempest/lib/api_schema/response/compute/v2_1/flavors.py tempest/lib/api_schema/response/compute/v2_1/flavors_access.py tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py tempest/lib/api_schema/response/compute/v2_1/floating_ips.py tempest/lib/api_schema/response/compute/v2_1/hosts.py tempest/lib/api_schema/response/compute/v2_1/hypervisors.py tempest/lib/api_schema/response/compute/v2_1/images.py tempest/lib/api_schema/response/compute/v2_1/instance_usage_audit_logs.py tempest/lib/api_schema/response/compute/v2_1/interfaces.py tempest/lib/api_schema/response/compute/v2_1/keypairs.py tempest/lib/api_schema/response/compute/v2_1/limits.py tempest/lib/api_schema/response/compute/v2_1/migrations.py tempest/lib/api_schema/response/compute/v2_1/parameter_types.py tempest/lib/api_schema/response/compute/v2_1/quota_classes.py tempest/lib/api_schema/response/compute/v2_1/quotas.py tempest/lib/api_schema/response/compute/v2_1/security_group_default_rule.py tempest/lib/api_schema/response/compute/v2_1/security_groups.py tempest/lib/api_schema/response/compute/v2_1/servers.py tempest/lib/api_schema/response/compute/v2_1/services.py tempest/lib/api_schema/response/compute/v2_1/snapshots.py tempest/lib/api_schema/response/compute/v2_1/tenant_networks.py tempest/lib/api_schema/response/compute/v2_1/tenant_usages.py tempest/lib/api_schema/response/compute/v2_1/versions.py tempest/lib/api_schema/response/compute/v2_1/volumes.py tempest/lib/api_schema/response/compute/v2_11/__init__.py tempest/lib/api_schema/response/compute/v2_11/services.py tempest/lib/api_schema/response/compute/v2_13/__init__.py tempest/lib/api_schema/response/compute/v2_13/servers.py tempest/lib/api_schema/response/compute/v2_16/__init__.py tempest/lib/api_schema/response/compute/v2_16/servers.py tempest/lib/api_schema/response/compute/v2_19/__init__.py tempest/lib/api_schema/response/compute/v2_19/servers.py tempest/lib/api_schema/response/compute/v2_2/__init__.py tempest/lib/api_schema/response/compute/v2_2/keypairs.py tempest/lib/api_schema/response/compute/v2_23/__init__.py tempest/lib/api_schema/response/compute/v2_23/migrations.py tempest/lib/api_schema/response/compute/v2_26/__init__.py tempest/lib/api_schema/response/compute/v2_26/servers.py tempest/lib/api_schema/response/compute/v2_3/__init__.py tempest/lib/api_schema/response/compute/v2_3/servers.py tempest/lib/api_schema/response/compute/v2_47/__init__.py tempest/lib/api_schema/response/compute/v2_47/servers.py tempest/lib/api_schema/response/compute/v2_48/__init__.py tempest/lib/api_schema/response/compute/v2_48/servers.py tempest/lib/api_schema/response/compute/v2_6/__init__.py tempest/lib/api_schema/response/compute/v2_6/servers.py tempest/lib/api_schema/response/compute/v2_9/__init__.py tempest/lib/api_schema/response/compute/v2_9/servers.py tempest/lib/api_schema/response/volume/__init__.py tempest/lib/api_schema/response/volume/versions.py tempest/lib/cli/__init__.py tempest/lib/cli/base.py tempest/lib/cli/output_parser.py tempest/lib/cmd/__init__.py tempest/lib/cmd/check_uuid.py tempest/lib/cmd/skip_tracker.py tempest/lib/common/__init__.py tempest/lib/common/api_version_request.py tempest/lib/common/api_version_utils.py tempest/lib/common/cred_client.py tempest/lib/common/cred_provider.py tempest/lib/common/dynamic_creds.py tempest/lib/common/fixed_network.py tempest/lib/common/http.py tempest/lib/common/jsonschema_validator.py tempest/lib/common/preprov_creds.py tempest/lib/common/rest_client.py tempest/lib/common/ssh.py tempest/lib/common/validation_resources.py tempest/lib/common/utils/__init__.py tempest/lib/common/utils/data_utils.py tempest/lib/common/utils/misc.py tempest/lib/common/utils/test_utils.py tempest/lib/common/utils/linux/__init__.py tempest/lib/common/utils/linux/remote_client.py tempest/lib/services/__init__.py tempest/lib/services/clients.py tempest/lib/services/compute/__init__.py tempest/lib/services/compute/agents_client.py tempest/lib/services/compute/aggregates_client.py tempest/lib/services/compute/availability_zone_client.py tempest/lib/services/compute/baremetal_nodes_client.py tempest/lib/services/compute/base_compute_client.py tempest/lib/services/compute/certificates_client.py tempest/lib/services/compute/extensions_client.py tempest/lib/services/compute/fixed_ips_client.py tempest/lib/services/compute/flavors_client.py tempest/lib/services/compute/floating_ip_pools_client.py tempest/lib/services/compute/floating_ips_bulk_client.py tempest/lib/services/compute/floating_ips_client.py tempest/lib/services/compute/hosts_client.py tempest/lib/services/compute/hypervisor_client.py tempest/lib/services/compute/images_client.py tempest/lib/services/compute/instance_usage_audit_log_client.py tempest/lib/services/compute/interfaces_client.py tempest/lib/services/compute/keypairs_client.py tempest/lib/services/compute/limits_client.py tempest/lib/services/compute/migrations_client.py tempest/lib/services/compute/networks_client.py tempest/lib/services/compute/quota_classes_client.py tempest/lib/services/compute/quotas_client.py tempest/lib/services/compute/security_group_default_rules_client.py tempest/lib/services/compute/security_group_rules_client.py tempest/lib/services/compute/security_groups_client.py tempest/lib/services/compute/server_groups_client.py tempest/lib/services/compute/servers_client.py tempest/lib/services/compute/services_client.py tempest/lib/services/compute/snapshots_client.py tempest/lib/services/compute/tenant_networks_client.py tempest/lib/services/compute/tenant_usages_client.py tempest/lib/services/compute/versions_client.py tempest/lib/services/compute/volumes_client.py tempest/lib/services/identity/__init__.py tempest/lib/services/identity/v2/__init__.py tempest/lib/services/identity/v2/endpoints_client.py tempest/lib/services/identity/v2/identity_client.py tempest/lib/services/identity/v2/roles_client.py tempest/lib/services/identity/v2/services_client.py tempest/lib/services/identity/v2/tenants_client.py tempest/lib/services/identity/v2/token_client.py tempest/lib/services/identity/v2/users_client.py tempest/lib/services/identity/v3/__init__.py tempest/lib/services/identity/v3/catalog_client.py tempest/lib/services/identity/v3/credentials_client.py tempest/lib/services/identity/v3/domain_configuration_client.py tempest/lib/services/identity/v3/domains_client.py tempest/lib/services/identity/v3/endpoint_filter_client.py tempest/lib/services/identity/v3/endpoint_groups_client.py tempest/lib/services/identity/v3/endpoints_client.py tempest/lib/services/identity/v3/groups_client.py tempest/lib/services/identity/v3/identity_client.py tempest/lib/services/identity/v3/inherited_roles_client.py tempest/lib/services/identity/v3/oauth_consumers_client.py tempest/lib/services/identity/v3/oauth_token_client.py tempest/lib/services/identity/v3/policies_client.py tempest/lib/services/identity/v3/projects_client.py tempest/lib/services/identity/v3/regions_client.py tempest/lib/services/identity/v3/role_assignments_client.py tempest/lib/services/identity/v3/roles_client.py tempest/lib/services/identity/v3/services_client.py tempest/lib/services/identity/v3/token_client.py tempest/lib/services/identity/v3/trusts_client.py tempest/lib/services/identity/v3/users_client.py tempest/lib/services/identity/v3/versions_client.py tempest/lib/services/image/__init__.py tempest/lib/services/image/v1/__init__.py tempest/lib/services/image/v1/image_members_client.py tempest/lib/services/image/v1/images_client.py tempest/lib/services/image/v2/__init__.py tempest/lib/services/image/v2/image_members_client.py tempest/lib/services/image/v2/images_client.py tempest/lib/services/image/v2/namespace_objects_client.py tempest/lib/services/image/v2/namespace_properties_client.py tempest/lib/services/image/v2/namespace_tags_client.py tempest/lib/services/image/v2/namespaces_client.py tempest/lib/services/image/v2/resource_types_client.py tempest/lib/services/image/v2/schemas_client.py tempest/lib/services/image/v2/versions_client.py tempest/lib/services/network/__init__.py tempest/lib/services/network/agents_client.py tempest/lib/services/network/base.py tempest/lib/services/network/extensions_client.py tempest/lib/services/network/floating_ips_client.py tempest/lib/services/network/metering_label_rules_client.py tempest/lib/services/network/metering_labels_client.py tempest/lib/services/network/networks_client.py tempest/lib/services/network/ports_client.py tempest/lib/services/network/quotas_client.py tempest/lib/services/network/routers_client.py tempest/lib/services/network/security_group_rules_client.py tempest/lib/services/network/security_groups_client.py tempest/lib/services/network/service_providers_client.py tempest/lib/services/network/subnetpools_client.py tempest/lib/services/network/subnets_client.py tempest/lib/services/network/tags_client.py tempest/lib/services/network/versions_client.py tempest/lib/services/object_storage/__init__.py tempest/lib/services/object_storage/account_client.py tempest/lib/services/object_storage/bulk_middleware_client.py tempest/lib/services/object_storage/capabilities_client.py tempest/lib/services/object_storage/container_client.py tempest/lib/services/object_storage/object_client.py tempest/lib/services/volume/__init__.py tempest/lib/services/volume/base_client.py tempest/lib/services/volume/v1/__init__.py tempest/lib/services/volume/v1/availability_zone_client.py tempest/lib/services/volume/v1/backups_client.py tempest/lib/services/volume/v1/encryption_types_client.py tempest/lib/services/volume/v1/extensions_client.py tempest/lib/services/volume/v1/hosts_client.py tempest/lib/services/volume/v1/limits_client.py tempest/lib/services/volume/v1/qos_client.py tempest/lib/services/volume/v1/quotas_client.py tempest/lib/services/volume/v1/services_client.py tempest/lib/services/volume/v1/snapshots_client.py tempest/lib/services/volume/v1/types_client.py tempest/lib/services/volume/v1/volumes_client.py tempest/lib/services/volume/v2/__init__.py tempest/lib/services/volume/v2/availability_zone_client.py tempest/lib/services/volume/v2/backups_client.py tempest/lib/services/volume/v2/capabilities_client.py tempest/lib/services/volume/v2/encryption_types_client.py tempest/lib/services/volume/v2/extensions_client.py tempest/lib/services/volume/v2/hosts_client.py tempest/lib/services/volume/v2/limits_client.py tempest/lib/services/volume/v2/qos_client.py tempest/lib/services/volume/v2/quota_classes_client.py tempest/lib/services/volume/v2/quotas_client.py tempest/lib/services/volume/v2/scheduler_stats_client.py tempest/lib/services/volume/v2/services_client.py tempest/lib/services/volume/v2/snapshot_manage_client.py tempest/lib/services/volume/v2/snapshots_client.py tempest/lib/services/volume/v2/transfers_client.py tempest/lib/services/volume/v2/types_client.py tempest/lib/services/volume/v2/volume_manage_client.py tempest/lib/services/volume/v2/volumes_client.py tempest/lib/services/volume/v3/__init__.py tempest/lib/services/volume/v3/backups_client.py tempest/lib/services/volume/v3/base_client.py tempest/lib/services/volume/v3/group_snapshots_client.py tempest/lib/services/volume/v3/group_types_client.py tempest/lib/services/volume/v3/groups_client.py tempest/lib/services/volume/v3/messages_client.py tempest/lib/services/volume/v3/snapshots_client.py tempest/lib/services/volume/v3/versions_client.py tempest/lib/services/volume/v3/volumes_client.py tempest/scenario/README.rst tempest/scenario/__init__.py tempest/scenario/manager.py tempest/scenario/test_aggregates_basic_ops.py tempest/scenario/test_encrypted_cinder_volumes.py tempest/scenario/test_minimum_basic.py tempest/scenario/test_network_advanced_server_ops.py tempest/scenario/test_network_basic_ops.py tempest/scenario/test_network_v6.py tempest/scenario/test_object_storage_basic_ops.py tempest/scenario/test_security_groups_basic_ops.py tempest/scenario/test_server_advanced_ops.py tempest/scenario/test_server_basic_ops.py tempest/scenario/test_server_multinode.py tempest/scenario/test_shelve_instance.py tempest/scenario/test_snapshot_pattern.py tempest/scenario/test_stamp_pattern.py tempest/scenario/test_volume_boot_pattern.py tempest/scenario/test_volume_migrate_attached.py tempest/services/__init__.py tempest/services/orchestration/__init__.py tempest/services/orchestration/json/__init__.py tempest/services/orchestration/json/orchestration_client.py tempest/test_discover/__init__.py tempest/test_discover/plugins.py tempest/test_discover/test_discover.py tempest/tests/README.rst tempest/tests/__init__.py tempest/tests/base.py tempest/tests/fake_config.py tempest/tests/fake_tempest_plugin.py tempest/tests/test_base_test.py tempest/tests/test_config.py tempest/tests/test_decorators.py tempest/tests/test_hacking.py tempest/tests/test_imports.py tempest/tests/test_list_tests.py tempest/tests/test_microversions.py tempest/tests/test_tempest_plugin.py tempest/tests/test_test.py tempest/tests/utils.py tempest/tests/api/__init__.py tempest/tests/api/compute/__init__.py tempest/tests/api/compute/test_base.py tempest/tests/cmd/__init__.py tempest/tests/cmd/test_account_generator.py tempest/tests/cmd/test_list_plugins.py tempest/tests/cmd/test_run.py tempest/tests/cmd/test_subunit_describe_calls.py tempest/tests/cmd/test_tempest_init.py tempest/tests/cmd/test_verify_tempest_config.py tempest/tests/cmd/test_workspace.py tempest/tests/cmd/sample_streams/calls.subunit tempest/tests/common/__init__.py tempest/tests/common/test_admin_available.py tempest/tests/common/test_alt_available.py tempest/tests/common/test_compute.py tempest/tests/common/test_credentials_factory.py tempest/tests/common/test_custom_matchers.py tempest/tests/common/test_image.py tempest/tests/common/test_waiters.py tempest/tests/common/utils/__init__.py tempest/tests/common/utils/test_net_utils.py tempest/tests/common/utils/linux/__init__.py tempest/tests/common/utils/linux/test_remote_client.py tempest/tests/files/__init__.py tempest/tests/files/failing-tests tempest/tests/files/passing-tests tempest/tests/files/setup.cfg tempest/tests/files/testr-conf tempest/tests/lib/__init__.py tempest/tests/lib/fake_auth_provider.py tempest/tests/lib/fake_credentials.py tempest/tests/lib/fake_http.py tempest/tests/lib/fake_identity.py tempest/tests/lib/test_auth.py tempest/tests/lib/test_base.py tempest/tests/lib/test_credentials.py tempest/tests/lib/test_decorators.py tempest/tests/lib/test_ssh.py tempest/tests/lib/test_tempest_lib.py tempest/tests/lib/cli/__init__.py tempest/tests/lib/cli/test_command_failed.py tempest/tests/lib/cli/test_execute.py tempest/tests/lib/cli/test_output_parser.py tempest/tests/lib/common/__init__.py tempest/tests/lib/common/test_api_version_request.py tempest/tests/lib/common/test_api_version_utils.py tempest/tests/lib/common/test_cred_client.py tempest/tests/lib/common/test_dynamic_creds.py tempest/tests/lib/common/test_http.py tempest/tests/lib/common/test_jsonschema_validator.py tempest/tests/lib/common/test_preprov_creds.py tempest/tests/lib/common/test_rest_client.py tempest/tests/lib/common/test_validation_resources.py tempest/tests/lib/common/utils/__init__.py tempest/tests/lib/common/utils/test_data_utils.py tempest/tests/lib/common/utils/test_misc.py tempest/tests/lib/common/utils/test_test_utils.py tempest/tests/lib/common/utils/linux/__init__.py tempest/tests/lib/common/utils/linux/test_remote_client.py tempest/tests/lib/services/__init__.py tempest/tests/lib/services/base.py tempest/tests/lib/services/registry_fixture.py tempest/tests/lib/services/test_clients.py tempest/tests/lib/services/compute/__init__.py tempest/tests/lib/services/compute/test_agents_client.py tempest/tests/lib/services/compute/test_aggregates_client.py tempest/tests/lib/services/compute/test_availability_zone_client.py tempest/tests/lib/services/compute/test_baremetal_nodes_client.py tempest/tests/lib/services/compute/test_base_compute_client.py tempest/tests/lib/services/compute/test_certificates_client.py tempest/tests/lib/services/compute/test_extensions_client.py tempest/tests/lib/services/compute/test_fixedIPs_client.py tempest/tests/lib/services/compute/test_flavors_client.py tempest/tests/lib/services/compute/test_floating_ip_pools_client.py tempest/tests/lib/services/compute/test_floating_ips_bulk_client.py tempest/tests/lib/services/compute/test_floating_ips_client.py tempest/tests/lib/services/compute/test_hosts_client.py tempest/tests/lib/services/compute/test_hypervisor_client.py tempest/tests/lib/services/compute/test_images_client.py tempest/tests/lib/services/compute/test_instance_usage_audit_log_client.py tempest/tests/lib/services/compute/test_interfaces_client.py tempest/tests/lib/services/compute/test_keypairs_client.py tempest/tests/lib/services/compute/test_limits_client.py tempest/tests/lib/services/compute/test_migrations_client.py tempest/tests/lib/services/compute/test_networks_client.py tempest/tests/lib/services/compute/test_quota_classes_client.py tempest/tests/lib/services/compute/test_quotas_client.py tempest/tests/lib/services/compute/test_security_group_default_rules_client.py tempest/tests/lib/services/compute/test_security_group_rules_client.py tempest/tests/lib/services/compute/test_security_groups_client.py tempest/tests/lib/services/compute/test_server_groups_client.py tempest/tests/lib/services/compute/test_servers_client.py tempest/tests/lib/services/compute/test_services_client.py tempest/tests/lib/services/compute/test_snapshots_client.py tempest/tests/lib/services/compute/test_tenant_networks_client.py tempest/tests/lib/services/compute/test_tenant_usages_client.py tempest/tests/lib/services/compute/test_versions_client.py tempest/tests/lib/services/compute/test_volumes_client.py tempest/tests/lib/services/identity/__init__.py tempest/tests/lib/services/identity/v2/__init__.py tempest/tests/lib/services/identity/v2/test_endpoints_client.py tempest/tests/lib/services/identity/v2/test_identity_client.py tempest/tests/lib/services/identity/v2/test_roles_client.py tempest/tests/lib/services/identity/v2/test_services_client.py tempest/tests/lib/services/identity/v2/test_tenants_client.py tempest/tests/lib/services/identity/v2/test_token_client.py tempest/tests/lib/services/identity/v2/test_users_client.py tempest/tests/lib/services/identity/v3/__init__.py tempest/tests/lib/services/identity/v3/test_catalog_client.py tempest/tests/lib/services/identity/v3/test_credentials_client.py tempest/tests/lib/services/identity/v3/test_domain_configuration_client.py tempest/tests/lib/services/identity/v3/test_domains_client.py tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py tempest/tests/lib/services/identity/v3/test_endpoint_groups_client.py tempest/tests/lib/services/identity/v3/test_endpoints_client.py tempest/tests/lib/services/identity/v3/test_groups_client.py tempest/tests/lib/services/identity/v3/test_identity_client.py tempest/tests/lib/services/identity/v3/test_inherited_roles_client.py tempest/tests/lib/services/identity/v3/test_oauth_consumers_client.py tempest/tests/lib/services/identity/v3/test_oauth_token_client.py tempest/tests/lib/services/identity/v3/test_policies_client.py tempest/tests/lib/services/identity/v3/test_projects_client.py tempest/tests/lib/services/identity/v3/test_regions_client.py tempest/tests/lib/services/identity/v3/test_role_assignments_client.py tempest/tests/lib/services/identity/v3/test_roles_client.py tempest/tests/lib/services/identity/v3/test_services_client.py tempest/tests/lib/services/identity/v3/test_token_client.py tempest/tests/lib/services/identity/v3/test_trusts_client.py tempest/tests/lib/services/identity/v3/test_users_client.py tempest/tests/lib/services/identity/v3/test_versions_client.py tempest/tests/lib/services/image/__init__.py tempest/tests/lib/services/image/v1/__init__.py tempest/tests/lib/services/image/v1/test_image_members_client.py tempest/tests/lib/services/image/v2/__init__.py tempest/tests/lib/services/image/v2/test_image_members_client.py tempest/tests/lib/services/image/v2/test_images_client.py tempest/tests/lib/services/image/v2/test_namespace_object_client.py tempest/tests/lib/services/image/v2/test_namespace_properties_client.py tempest/tests/lib/services/image/v2/test_namespace_tags_client.py tempest/tests/lib/services/image/v2/test_namespaces_client.py tempest/tests/lib/services/image/v2/test_resource_types_client.py tempest/tests/lib/services/image/v2/test_schemas_client.py tempest/tests/lib/services/image/v2/test_versions_client.py tempest/tests/lib/services/network/__init__.py tempest/tests/lib/services/network/test_base_network_client.py tempest/tests/lib/services/network/test_extensions_client.py tempest/tests/lib/services/network/test_floating_ips_client.py tempest/tests/lib/services/network/test_metering_label_rules_client.py tempest/tests/lib/services/network/test_metering_labels_client.py tempest/tests/lib/services/network/test_ports_client.py tempest/tests/lib/services/network/test_quotas_client.py tempest/tests/lib/services/network/test_routers_client.py tempest/tests/lib/services/network/test_security_group_rules_client.py tempest/tests/lib/services/network/test_security_groups_client.py tempest/tests/lib/services/network/test_service_providers_client.py tempest/tests/lib/services/network/test_subnetpools_client.py tempest/tests/lib/services/network/test_subnets_client.py tempest/tests/lib/services/network/test_tags_client.py tempest/tests/lib/services/network/test_versions_client.py tempest/tests/lib/services/object_storage/__init__.py tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py tempest/tests/lib/services/object_storage/test_capabilities_client.py tempest/tests/lib/services/object_storage/test_object_client.py tempest/tests/lib/services/volume/__init__.py tempest/tests/lib/services/volume/v1/__init__.py tempest/tests/lib/services/volume/v1/test_encryption_types_client.py tempest/tests/lib/services/volume/v1/test_quotas_client.py tempest/tests/lib/services/volume/v1/test_snapshots_client.py tempest/tests/lib/services/volume/v2/__init__.py tempest/tests/lib/services/volume/v2/test_availability_zone_client.py tempest/tests/lib/services/volume/v2/test_backups_client.py tempest/tests/lib/services/volume/v2/test_capabilities_client.py tempest/tests/lib/services/volume/v2/test_encryption_types_client.py tempest/tests/lib/services/volume/v2/test_extensions_client.py tempest/tests/lib/services/volume/v2/test_hosts_client.py tempest/tests/lib/services/volume/v2/test_limits_client.py tempest/tests/lib/services/volume/v2/test_quota_classes_client.py tempest/tests/lib/services/volume/v2/test_quotas_client.py tempest/tests/lib/services/volume/v2/test_scheduler_stats_client.py tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py tempest/tests/lib/services/volume/v2/test_snapshots_client.py tempest/tests/lib/services/volume/v2/test_transfers_client.py tempest/tests/lib/services/volume/v2/test_volume_manage_client.py tempest/tests/lib/services/volume/v2/test_volumes_client.py tempest/tests/lib/services/volume/v3/__init__.py tempest/tests/lib/services/volume/v3/test_backups_client.py tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py tempest/tests/lib/services/volume/v3/test_group_types_client.py tempest/tests/lib/services/volume/v3/test_groups_client.py tempest/tests/lib/services/volume/v3/test_user_messages_client.py tempest/tests/lib/services/volume/v3/test_versions_client.py tempest/tests/lib/services/volume/v3/test_volumes_client.py tempest/tests/services/object_storage/test_object_client.py tools/check_logs.py tools/find_stack_traces.py tools/generate-tempest-plugins-list.py tools/generate-tempest-plugins-list.sh tools/skip_tracker.py tools/tempest-plugin-sanity.sh tools/tox_install.sh tools/with_venv.shtempest-17.2.0/tempest.egg-info/PKG-INFO0000664000175100017510000003364413207045127017571 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: tempest Version: 17.2.0 Summary: OpenStack Integration Testing Home-page: https://docs.openstack.org/tempest/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/tempest.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Tempest - The OpenStack Integration Test Suite ============================================== The documentation for Tempest is officially hosted at: https://docs.openstack.org/tempest/latest/ This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ----------------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node DevStack install, a 20 node LXC cloud, or a 1000 node KVM cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public OpenStack APIs. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there are some features of OpenStack that are not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self-testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. Where the configuration file lives and how you interact with it depends on how you'll be running Tempest. There are 2 methods of using Tempest. The first, which is a newer and recommended workflow treats Tempest as a system installed program. The second older method is to run Tempest assuming your working dir is the actually Tempest source repo, and there are a number of assumptions related to that. For this section we'll only cover the newer method as it is simpler, and quicker to work with. #. You first need to install Tempest. This is done with pip after you check out the Tempest repo:: $ git clone https://git.openstack.org/openstack/tempest $ pip install tempest/ This can be done within a venv, but the assumption for this guide is that the Tempest CLI entry point will be in your shell's PATH. #. Installing Tempest may create a ``/etc/tempest dir``, however if one isn't created you can create one or use ``~/.tempest/etc`` or ``~/.config/tempest`` in place of ``/etc/tempest``. If none of these dirs are created Tempest will create ``~/.tempest/etc`` when it's needed. The contents of this dir will always automatically be copied to all ``etc/`` dirs in local workspaces as an initial setup step. So if there is any common configuration you'd like to be shared between local Tempest workspaces it's recommended that you pre-populate it before running ``tempest init``. #. Setup a local Tempest workspace. This is done by using the tempest init command:: $ tempest init cloud-01 which also works the same as:: $ mkdir cloud-01 && cd cloud-01 && tempest init This will create a new directory for running a single Tempest configuration. If you'd like to run Tempest against multiple OpenStack deployments the idea is that you'll create a new working directory for each to maintain separate configuration files and local artifact storage for each. #. Then ``cd`` into the newly created working dir and also modify the local config files located in the ``etc/`` subdir created by the ``tempest init`` command. Tempest is expecting a ``tempest.conf`` file in etc/ so if only a sample exists you must rename or copy it to tempest.conf before making any changes to it otherwise Tempest will not know how to load it. For details on configuring Tempest refer to the :ref:`tempest-configuration`. #. Once the configuration is done you're now ready to run Tempest. This can be done using the :ref:`tempest_run` command. This can be done by either running:: $ tempest run from the Tempest workspace directory. Or you can use the ``--workspace`` argument to run in the workspace you created regardless of your current working directory. For example:: $ tempest run --workspace cloud-01 There is also the option to use testr directly, or any `testr`_ based test runner, like `ostestr`_. For example, from the workspace dir run:: $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' will run the same set of tests as the default gate jobs. .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html .. _ostestr: https://docs.openstack.org/os-testr/latest/ 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 Release Notes `_ shows what changes have been released on each version. 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 ------------- Detailed configuration of Tempest is beyond the scope of this document see :ref:`tempest-configuration` for more details on configuring Tempest. The ``etc/tempest.conf.sample`` attempts to be a self-documenting version of the configuration. You can generate a new sample tempest.conf file, run the following command from the top level of the Tempest directory:: $ tox -e genconfig The most important pieces that are needed are the user ids, OpenStack endpoints, and basic flavors and images needed to run tests. Unit Tests ---------- Tempest also has a set of unit tests which test the Tempest code itself. These tests can be run by specifying the test discovery path:: $ stestr --test-path ./tempest/tests run By setting ``--test-path`` option to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of ``test_path`` is ``test_path=./tempest/test_discover`` which will only run test discover on the Tempest suite. Alternatively, there are the py27 and py35 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Starting in the Kilo release the OpenStack services dropped all support for python 2.6. This change has been mirrored in Tempest, starting after the tempest-2 tag. This means that proposed changes to Tempest which only fix python 2.6 compatibility will be rejected, and moving forward more features not present in python 2.6 will be used. If you're running your OpenStack services on an earlier release with python 2.6 you can easily run Tempest against it from a remote system running python 2.7. (or deploy a cloud guest in your cloud that has python 2.7) Python 3.x ---------- Starting during the Pike cycle Tempest has a gating CI job that runs Tempest with Python 3. Any Tempest release after 15.0.0 should fully support running under Python 3 as well as Python 2.7. Legacy run method ----------------- The legacy method of running Tempest is to just treat the Tempest source code as a python unittest repository and run directly from the source repo. When running in this way you still start with a Tempest config file and the steps are basically the same except that it expects you know where the Tempest code lives on your system and requires a bit more manual interaction to get Tempest running. For example, when running Tempest this way things like a lock file directory do not get generated automatically and the burden is on the user to create and configure that. To start you need to create a configuration file. The easiest way to create a configuration file is to generate a sample in the ``etc/`` directory :: $ cd $TEMPEST_ROOT_DIR $ oslo-config-generator --config-file \ tempest/cmd/config-generator.tempest.conf \ --output-file etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running DevStack environment, Tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your DevStack installation. Tempest is not tied to any single test runner, but `testr`_ is the most commonly used tool. Also, the nosetests test runner is **not** recommended to run Tempest. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $ testr run --parallel To run one single test serially :: $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Tox also contains several existing job configurations. For example:: $ tox -e full which will run the same set of tests as the OpenStack gate. (it's exactly how the gate invokes Tempest) Or:: $ tox -e smoke to run the tests tagged as smoke. Platform: UNKNOWN Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 tempest-17.2.0/tempest.egg-info/requires.txt0000664000175100017510000000065413207045127021067 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 cliff!=2.9.0,>=2.8.0 jsonschema<3.0.0,>=2.6.0 testtools>=2.2.0 paramiko>=2.0.0 netaddr>=0.7.18 testrepository>=0.0.18 oslo.concurrency>=3.20.0 oslo.config>=4.6.0 oslo.log>=3.30.0 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.31.0 six>=1.10.0 fixtures>=3.0.0 PyYAML>=3.10 python-subunit>=1.0.0 stevedore>=1.20.0 PrettyTable<0.8,>=0.7.1 os-testr>=1.0.0 urllib3>=1.21.1 debtcollector>=1.2.0 unittest2>=1.1.0 tempest-17.2.0/tempest.egg-info/not-zip-safe0000664000175100017510000000000113207045121020702 0ustar zuulzuul00000000000000 tempest-17.2.0/tempest.egg-info/dependency_links.txt0000664000175100017510000000000113207045127022530 0ustar zuulzuul00000000000000 tempest-17.2.0/tempest.egg-info/pbr.json0000664000175100017510000000005613207045127020141 0ustar zuulzuul00000000000000{"git_version": "9bd78bc", "is_release": true}tempest-17.2.0/tempest.egg-info/entry_points.txt0000664000175100017510000000203313207045127021756 0ustar zuulzuul00000000000000[console_scripts] check-uuid = tempest.lib.cmd.check_uuid:run skip-tracker = tempest.lib.cmd.skip_tracker:main subunit-describe-calls = tempest.cmd.subunit_describe_calls:entry_point tempest = tempest.cmd.main:main tempest-account-generator = tempest.cmd.account_generator:main verify-tempest-config = tempest.cmd.verify_tempest_config:main [oslo.config.opts] tempest.config = tempest.config:list_opts [tempest.cm] account-generator = tempest.cmd.account_generator:TempestAccountGenerator cleanup = tempest.cmd.cleanup:TempestCleanup init = tempest.cmd.init:TempestInit list-plugins = tempest.cmd.list_plugins:TempestListPlugins run = tempest.cmd.run:TempestRun verify-config = tempest.cmd.verify_tempest_config:TempestVerifyConfig workspace_list = tempest.cmd.workspace:TempestWorkspaceList workspace_move = tempest.cmd.workspace:TempestWorkspaceMove workspace_register = tempest.cmd.workspace:TempestWorkspaceRegister workspace_remove = tempest.cmd.workspace:TempestWorkspaceRemove workspace_rename = tempest.cmd.workspace:TempestWorkspaceRename tempest-17.2.0/tempest.egg-info/top_level.txt0000664000175100017510000000001013207045127021203 0ustar zuulzuul00000000000000tempest